diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c75e875 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly diff --git a/.gitignore b/.gitignore index ca52bfe..eaa5e18 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ .codex/ .copilot/ .devin/ +copilot-session-*.md tests/fixtures/tmp-bin/ # Generated context (regenerated by test suite at runtime) diff --git a/.rabbit/context.yaml b/.rabbit/context.yaml index 09c1ccc..d5719ae 100644 --- a/.rabbit/context.yaml +++ b/.rabbit/context.yaml @@ -2,23 +2,34 @@ # Run `dev.kit repo` to refresh. kind: repoContext version: udx.dev/dev.kit/v1 -generated: 2026-05-04 +generator: + tool: dev.kit + repo: https://github.com/udx/dev.kit + version: 0.10.0 + generated_at: 2026-05-13T00:55:54Z repo: name: dev.kit - archetype: package-cli + archetype: manifest-repo # Refs — Direct-read files and paths that define the repo contract. -# Note: Include only files or directories an agent should read before code exploration. +# Note: Include only files or directories a repo consumer should read before code exploration. # Note: Prefer README, focused docs, workflows, manifests, and explicit operational files. # Note: Exclude broad implementation directories unless they are the contract themselves. refs: - ./README.md - ./changes.md + - ./Makefile + - ./docs/references/command-surfaces.md + - ./docs/references/repo-design.md + - ./src/configs/archetypes.yaml + - ./src/configs/audit-rules.yaml + - ./src/configs/context-config.yaml + - ./src/configs/detection-patterns.yaml + - ./src/configs/detection-signals.yaml - ./deploy.yml - ./.github/workflows - - ./Makefile - ./docs # Commands — Canonical repo entrypoints detected from strong repo signals. @@ -30,6 +41,12 @@ commands: verify: run: make test source: Makefile + build: + run: make build + source: docs/references/command-surfaces.md + run: + run: make run + source: docs/references/command-surfaces.md # Gaps — Factors that are missing or only partially supported by current repo signals. # Note: Base the result on explicit factor rules, not free-form judgment. @@ -39,55 +56,47 @@ commands: gaps: - factor: config status: partial - message: Config signals exist, but no explicit env contract file was detected. Add .env.example, .env.sample, or .env.template when runtime configuration is required. + message: Found config-bearing repo assets in deploy.yml, but no canonical checked-in config contract is declared yet. + repair_target: deploy.yml or .env.example + reference: docs/references/config-contract-surfaces.md evidence: - runtime config: deploy.yml -# Dependencies — External repos, actions, images, or versioned manifests this repo relies on. -# Note: Capture behavior defined outside the current checkout. +# Dependencies — Meaningful dependency-repo contracts such as reusable workflows, images, or versioned manifests this repo relies on. +# Note: Capture execution-shaping behavior defined outside the current checkout. +# Note: Avoid promoting standard package inventory or ordinary GitHub action refs into top-level context. # Note: Normalize same-org versioned refs into repo slugs when possible. -# Note: Keep where-used tracing so the dependency can be followed back to its source. dependencies: - repo: udx/reusable-workflows kind: reusable workflow - resolved: true - archetype: workflow-repo - description: Reusable GitHub Actions workflow templates for CI/CD + resolved: false used_by: - .github/workflows/context7-ops.yml - .github/workflows/npm-release-ops.yml - - repo: udx/dev.kit - kind: manifest contract (v1) - resolved: true - declared_as: udx.dev/dev.kit/v1 - archetype: package-cli - used_by: - - src/configs/archetypes.yaml - - src/configs/audit-rules.yaml - - src/configs/context-config.yaml - - src/configs/detection-patterns.yaml - - src/configs/detection-signals.yaml - repo: udx/worker kind: manifest contract (deploy) - resolved: true + resolved: false declared_as: udx.io/worker-v1/deploy - archetype: runtime-image - description: UDX Worker Docker image used_by: - deploy.yml -# Manifests — YAML files that define detection rules, workflows, evaluation, deploy, or runtime behavior. -# Note: Include manifests that materially shape repo behavior or agent understanding. +# Manifests — YAML files that define repo-specific workflow, deploy, or contract behavior. +# Note: Include custom config/manifests that materially shape repo behavior or contract understanding. +# Note: Do not include workflow YAML only because it lives under .github/workflows. +# Note: Promote workflow files only when they declare reusable workflow refs or other repo-specific execution contracts. # Note: Prefer structured kind and description metadata from the manifest itself. -# Note: Include eval and workflow manifests, not only deploy manifests. +# Note: Include hidden or nested contract dirs when they contain repo-owned manifests with meaningful metadata. manifests: + - path: .github/workflows/context7-ops.yml + kind: githubWorkflow + - path: .github/workflows/npm-release-ops.yml + kind: githubWorkflow - path: src/configs/archetypes.yaml kind: repoArchetypes - description: Repo archetype definitions and matching rules + description: Minimal repo-level archetype definitions declared_as: udx.dev/dev.kit/v1 - source_repo: udx/dev.kit used_by: - lib/modules/config_catalog.sh evidence: @@ -97,7 +106,6 @@ manifests: kind: auditRules description: Gap messages and guidance for missing or partial repo factors declared_as: udx.dev/dev.kit/v1 - source_repo: udx/dev.kit used_by: - lib/modules/config_catalog.sh evidence: @@ -105,9 +113,8 @@ manifests: - path reference: lib/modules/config_catalog.sh - path: src/configs/context-config.yaml kind: contextConfig - description: Repo root markers, direct-read refs, and documentation priority order + description: Minimal repo root markers, direct-read refs, and documentation priority order declared_as: udx.dev/dev.kit/v1 - source_repo: udx/dev.kit used_by: - lib/modules/config_catalog.sh evidence: @@ -117,7 +124,6 @@ manifests: kind: detectionPatterns description: Regex patterns for command, workflow, and env detection declared_as: udx.dev/dev.kit/v1 - source_repo: udx/dev.kit used_by: - lib/modules/repo_signals.sh evidence: @@ -127,51 +133,34 @@ manifests: kind: detectionSignals description: File, directory, and glob signals for factor and dependency detection declared_as: udx.dev/dev.kit/v1 - source_repo: udx/dev.kit used_by: - - tests/suite.sh - lib/modules/repo_signals.sh + - tests/suite.sh evidence: - version: udx.dev/dev.kit/v1 - - path reference: tests/suite.sh - path reference: lib/modules/repo_signals.sh - - path: .github/workflows/context7-ops.yml - kind: githubWorkflow - - path: .github/workflows/npm-release-ops.yml - kind: githubWorkflow - used_by: - - .claude/settings.local.json - evidence: - - path reference: .claude/settings.local.json + - path reference: tests/suite.sh - path: deploy.yml kind: workerDeployConfig declared_as: udx.io/worker-v1/deploy source_repo: udx/worker used_by: - Makefile - - tests/suite.sh - - tests/fixtures/docker-repo/.dev-kit/manifest.json - - tests/fixtures/docker-repo/.rabbit/context.yaml - - .claude/settings.local.json - - lib/modules/repo_scaffold.sh - - src/configs/detection-signals.yaml + - docs/references/command-surfaces.md + - docs/references/config-contract-surfaces.md + - docs/repo-contract-boundary.md + - lib/modules/repo_factors.sh - src/configs/context-config.yaml + - src/configs/detection-signals.yaml + - tests/suite.sh evidence: - version: udx.io/worker-v1/deploy - path reference: Makefile - - path reference: tests/suite.sh - - path reference: tests/fixtures/docker-repo/.dev-kit/manifest.json - - path reference: tests/fixtures/docker-repo/.rabbit/context.yaml - - path reference: .claude/settings.local.json - - path reference: lib/modules/repo_scaffold.sh - - path reference: src/configs/detection-signals.yaml + - path reference: docs/references/command-surfaces.md + - path reference: docs/references/config-contract-surfaces.md + - path reference: docs/repo-contract-boundary.md + - path reference: lib/modules/repo_factors.sh - path reference: src/configs/context-config.yaml - - path: evals/promptfooconfig.yaml - description: "dev.kit context impact — with vs without AGENTS.md" - used_by: - - Makefile - - src/configs/detection-signals.yaml - evidence: - - path reference: Makefile - path reference: src/configs/detection-signals.yaml + - path reference: tests/suite.sh diff --git a/AGENTS.md b/AGENTS.md index d93a309..ed5733d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,38 +1,33 @@ # AGENTS.md -_Auto-generated by `dev.kit agent`. Source: `.rabbit/context.yaml`._ +_Normalized repo-owned guidance for agents. Keep it aligned with `docs/references/agent-dev-workflow.md` and `.rabbit/context.yaml`._ ## Repo: dev.kit -- archetype: package-cli - context: ./.rabbit/context.yaml +- workflow_ref: ./docs/references/agent-dev-workflow.md -## Operating contract +## Start here -1. Before each session, make sure `dev.kit` itself is up to date, then run `dev.kit`. Refresh focused layers with `dev.kit repo` or `dev.kit agent` after repo changes. -2. Read `.rabbit/context.yaml` first. It is the machine contract for refs, commands, dependencies, manifests, and gaps. -3. Read only the refs, manifests, dependency context, and explicitly referenced paths from `context.yaml`. Avoid broad filesystem scans. -4. Prefer manifests and repo-declared commands over implementation guesses. Do not edit generated `.rabbit/context.yaml` directly. -5. Fetch dynamic GitHub state with `gh` only when the current task needs issues, PRs, reviews, workflow runs, or alerts. +1. Make sure `dev.kit` itself is current, then run `dev.kit`. +2. Read `.rabbit/context.yaml` first when it exists. +3. Read the highest-priority refs and manifests that `context.yaml` points to. +4. Run `dev.kit repo` after repo changes or when context is missing or stale. -### Dependency context +## Operating rules -When a dependency entry points to another repo, treat that repo as its own context boundary. Prefer the dependency repo’s `.rabbit/context.yaml`; if it is missing in a local checkout, run `dev.kit repo` from that dependency repo before reading its implementation files. - -For manifest backend traces, read the manifest first, then the traced backend path or docs listed under that manifest entry. Use live GitHub lookups only when local dependency context is unavailable or stale. - -### Gap repair loop - -For each gap, read its evidence in `context.yaml`, identify the repo-owned source asset that should declare the missing contract, patch that source asset, then rerun `dev.kit repo`. Repeat until the gap is resolved or clearly document why the repo intentionally cannot cover it. - -Do not patch generated context to hide a gap. Fix docs, example env files, manifests, workflows, package scripts, Dockerfiles, or other primary repo assets so the next context refresh can detect the improvement. - - - config (partial): Config signals exist, but no explicit env contract file was detected. Add .env.example, .env.sample, or .env.template when runtime configuration is required. +- treat `.rabbit/context.yaml` as generated repo evidence, not hand-authored guidance +- prefer repo-declared commands and manifests over guessed behavior +- keep durable workflow guidance in repo-owned docs, especially `docs/references/agent-dev-workflow.md` +- when a gap appears, repair the owning repo asset and rerun `dev.kit repo` +- use live GitHub data only when the task needs issues, PRs, reviews, workflow runs, or alerts ## Workflow -Use these repo-derived steps as the default operating path. Adapt them to the current agent role instead of forcing a single development lifecycle onto every task. +- read: `README.md`, `changes.md`, `deploy.yml`, `.github/workflows/`, `docs/` +- verify: `make test` - - Read the highest-priority repo refs first: ./README.md, ./changes.md, ./deploy.yml, ./.github/workflows, ./Makefile, ./docs - - Run the canonical verification command: make test +## Notes +- `AGENTS.md` is optional and repo-owned in this repo +- the reference doc is the fuller source of examples, Q&A, and best practices diff --git a/Makefile b/Makefile index 21677f3..d26b307 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ WORKER_IMAGE := usabilitydynamics/udx-worker:latest -.PHONY: test test-real test-docker test-docker-pull test-shell eval eval-view +.PHONY: test test-real test-docker test-docker-pull test-shell # Run tests locally test: @@ -21,12 +21,4 @@ test-shell: # Pull the worker image explicitly test-docker-pull: - docker pull $(WORKER_IMAGE) - -# Run promptfoo eval — measures agent accuracy with vs without dev.kit context -eval: - promptfoo eval -c evals/promptfooconfig.yaml - -# View eval results in browser -eval-view: - promptfoo view + docker pull $(WORKER_IMAGE) \ No newline at end of file diff --git a/README.md b/README.md index adfc788..57379bd 100644 --- a/README.md +++ b/README.md @@ -2,114 +2,202 @@ -**Repository context coverage and agent operating guidance.** +`dev.kit` turns repositories into self-explaining repo contracts for humans, scripts, and CI/CD. -`dev.kit` turns repo design into a usable contract for agents. +It helps teams and repositories: -It does three things: +1. understand how the repo actually works +2. keep repo standards, manifests, refs, and dependency contracts traceable +3. regenerate reliable context from repo signals instead of tribal knowledge -1. inspect what the current environment can really support -2. detect and serialize repo context into `.rabbit/context.yaml` -3. generate `AGENTS.md` so each new session starts from current repo reality instead of prompt memory +The model is: -The model is repo-first, gap-aware, and regeneration-friendly. `dev.kit` should describe what the repo declares, note what it cannot confirm yet, and make the next repair step obvious. +- repo-first +- gap-aware +- regeneration-friendly +- standard-driven +- manifest-driven + +## Install ```bash npm install -g @udx/dev-kit ``` +```bash +curl -fsSL https://raw.githubusercontent.com/udx/dev.kit/latest/bin/scripts/install.sh | bash +``` + ## Quick start ```bash -# first make sure your dev.kit install is current -# npm install -g @udx/dev-kit -# or: curl -fsSL https://raw.githubusercontent.com/udx/dev.kit/latest/bin/scripts/install.sh | bash - +# make sure your dev.kit install is current first cd my-repo -dev.kit # happy path: env + repo context + AGENTS.md -dev.kit env # inspect tools, auth, and capability controls -dev.kit env --config -dev.kit repo # refresh only .rabbit/context.yaml -dev.kit agent # refresh only AGENTS.md + +dev.kit # inspect environment and repo context status +dev.kit repo # generate or refresh .rabbit/context.yaml +``` + +Optional capability refresh: + +```bash +dev.kit env ``` ## Operating loop -The intended loop is simple: +`dev.kit` uses staged repo-driven context generation: -1. make sure the local `dev.kit` install is current -2. run `dev.kit` at the start of a session -3. let `dev.kit env` shape what capabilities are actually available -4. let `dev.kit repo` write the current repo contract into `.rabbit/context.yaml` -5. let `dev.kit agent` generate operating guidance from that contract -6. if gaps are detected, fix the repo-owned source assets, rerun `dev.kit repo`, then validate the regenerated context +```text +dev.kit + → inspect environment + → summarize repo-local context status + → warn when existing context is stale + → point to the next safe step -That keeps context dynamic, grounded in repo signals, and resistant to drift. +dev.kit env + → detect tools, auth, and resolution capabilities -## Commands +dev.kit repo + → resolve repo standards, manifests, refs, dependency contracts, and gaps + → ensure a minimal repo-owned structure when needed + → generate .rabbit/context.yaml +``` -| Command | Role | -|---------|------| -| `dev.kit` | Start here. Refresh environment awareness, repo context, and agent guidance together. | -| `dev.kit env` | Detect tools, auth state, and local capability controls so later steps stay honest. | -| `dev.kit env --config` | Create or update env config for disabling specific tools or credentials. | -| `dev.kit repo` | Detect refs, commands, gaps, manifests, and dependencies, then write `.rabbit/context.yaml`. | -| `dev.kit repo --force` | Re-resolve dependency context from scratch. | -| `dev.kit agent` | Generate `AGENTS.md` from the current repo contract and its gaps. | +Each step produces metadata that guides the next safe repo repair or regeneration step. -All commands support `--json` for machine-readable output and should guide the next step in human- and agent-friendly terms. +## `.rabbit/context.yaml` -## Generated artifacts +`dev.kit repo` generates `.rabbit/context.yaml` as the repo operational contract. -`dev.kit` produces two main artifacts: +It may include: -- `.rabbit/context.yaml` — the machine-readable repo contract -- `AGENTS.md` — the generated operating layer for agents +- generator metadata +- manifests +- refs +- workflows +- commands +- dependencies +- dependency repo contracts +- coverage gaps +- repair hints -Keep the boundary strict: +The repo should remain usable even when `dev.kit` is unavailable. -- `context.yaml` is for repo facts, traces, commands, manifests, dependencies, and gaps -- `AGENTS.md` is for how an agent should operate from that contract, including gap-repair behavior +Committed repo-local context should stay useful for maintenance and automation. -## Install +## Agent instruction files -```bash -# npm (recommended) -npm install -g @udx/dev-kit +Agent instruction files such as `AGENTS.md` or `CLAUDE.md` are optional repo-owned policy surfaces. -# no npm? -curl -fsSL https://raw.githubusercontent.com/udx/dev.kit/latest/bin/scripts/install.sh | bash +`dev.kit` can point maintainers toward the kind of guidance those files should contain. A repo may keep that guidance in docs and, if preferred, normalize it into `AGENTS.md` as a maintained repo-owned surface. + +Use those files for consumer behavior rules and team-specific operating guidance. Keep `.rabbit/context.yaml` focused on generated repo facts. + +## Repo surfaces + +This repo is intentionally split by responsibility: + +- `src/configs/` configures `dev.kit` detection, gap rules, context sections, and other module inputs +- scripts, workflows, and YAML manifests are the programmatic execution layer +- `docs/` documents `dev.kit` behavior, repo-context boundaries, workflow, and outputs +- `docs/references/` is a small knowledgebase for developers and agents when gaps or contract surfaces need interpretation +- `tests/` validates command flow, generated context, and user-facing outputs + +## Empty repos + +`dev.kit repo` is safe to run on nearly empty repos. + +It can establish a minimal default structure so the repo becomes regeneration-friendly immediately: + +- `README.md` +- `.github/dependabot.yml` +- `.github/workflows/` +- `.rabbit/` +- `docs/` + +That gives the repo clear places for context coverage, workflow traceability, docs, and future repair loops without inventing app-specific structure. + +## Refs and traceability + +`dev.kit` resolves refs using: + +1. manifest metadata/header refs +2. repo-local usage +3. dependency-repo usage and workflow relationships + +If refs cannot be resolved: + +- gaps are emitted +- repair hints are suggested +- unresolved state is preserved instead of guessed + +Example: + +```bash +dev.kit repo ``` -Use one install path at a time. Installing with npm removes the curl-managed home and shim. Installing with curl removes the global npm package first. More detail: [Installation](docs/installation.md). +Then read: + +- `README.md` +- `docs/` +- `.rabbit/context.yaml` + +## Commands + +| Command | Purpose | +| ---------------------- | -------------------------------------------------- | +| `dev.kit` | Inspect repo context status and suggest next steps | +| `dev.kit env` | Detect tools, auth, and resolution capabilities | +| `dev.kit env --config` | Configure capability restrictions | +| `dev.kit repo` | Generate `.rabbit/context.yaml` | +| `dev.kit repo --force` | Re-resolve repo and dependency context | + +All commands support `--json`. + +## Recommended tooling repos + +`dev.kit repo` also points to a small set of supporting UDX repos when you need shared worker, workflow, or repo-contract tooling: + +- +- +- + +## Principles + +- automate what can be verified +- expose unresolved gaps instead of guessing +- prefer refs over duplicated docs +- stay app-agnostic and language-agnostic +- keep repo-owned contracts compact and markdown-friendly +- degrade gracefully when tooling, auth, or external context is unavailable +- optimize for operational clarity over automation complexity ## Docs -- [How It Works](docs/how-it-works.md) — command flow, generated artifacts, and regeneration loop -- [Environment Config](docs/environment-config.md) — capability detection and env controls -- [Context Coverage](docs/context-coverage.md) — what `context.yaml` should contain and what gaps mean -- [Experience Guidance](docs/experience-guidance.md) — what `AGENTS.md` should instruct agents to do -- [Smart Dependency Detection](docs/smart-dependency-detection.md) — deterministic cross-repo and manifest tracing -- [Installation](docs/installation.md) — npm and curl installs, cleanup, uninstall, and verification +- [How It Works](docs/how-it-works.md) +- [Repo Contract Boundary](docs/repo-contract-boundary.md) +- [Environment Config](docs/environment-config.md) +- [Context Coverage](docs/context-coverage.md) +- [Integration](docs/integration.md) +- [Smart Dependency Detection](docs/smart-dependency-detection.md) +- [Reference Docs](docs/references/README.md) +- [Reference: Command and Workflow Surfaces](docs/references/command-surfaces.md) +- [Reference: Agent and Developer Workflow](docs/references/agent-dev-workflow.md) +- [Reference: Repo Design](docs/references/repo-design.md) +- [Installation](docs/installation.md) ## Testing -For fast local checks: - ```bash bash tests/suite.sh --only core ``` -For installed-CLI testing in a real worker environment: - ```bash bash tests/worker-smoke.sh ``` -For opt-in validation against real local repos: - ```bash bash tests/real-repos.sh /path/to/repo1 /path/to/repo2 ``` - -The worker runner is the main integration path for heavier scenarios such as gap repair, env toggles, and real-repo mutation. Real-repo testing is local-only and can include both public and private repos without baking those assumptions into CI. diff --git a/bin/dev-kit b/bin/dev-kit index 791a28b..004bfa4 100755 --- a/bin/dev-kit +++ b/bin/dev-kit @@ -39,7 +39,7 @@ usage() { local description="" cat <<'EOF' Usage: dev.kit [--json] - dev.kit + dev.kit [--json] [options] Commands: EOF @@ -62,7 +62,251 @@ EOF home_usage() { echo "Usage: dev.kit [--json]" echo - echo "Checks environment, refreshes repo context, and generates agent guidance when a repo is detected." + echo "Checks environment, then summarizes repo context when a repo is detected." +} + +dev_kit_home_artifact_status() { + local artifact_path="$1" + if [ -f "$artifact_path" ]; then + printf '%s' "existing" + return 0 + fi + printf '%s' "missing" +} + +dev_kit_context_top_level_field() { + local context_yaml_path="$1" + local field_name="$2" + + [ -f "$context_yaml_path" ] || return 0 + + awk -v field_name="$field_name" ' + $0 ~ ("^" field_name ":[[:space:]]*") { + sub("^[^:]+:[[:space:]]*", "", $0) + print + exit + } + ' "$context_yaml_path" +} + +dev_kit_context_section_item_count() { + local context_yaml_path="$1" + local section_name="$2" + local item_pattern="$3" + + [ -f "$context_yaml_path" ] || { printf '0'; return 0; } + + awk -v section_name="$section_name" -v item_pattern="$item_pattern" ' + $0 == section_name ":" { in_section = 1; next } + in_section && /^[^[:space:]#]/ { exit } + in_section && $0 ~ item_pattern { count += 1 } + END { print count + 0 } + ' "$context_yaml_path" +} + +dev_kit_context_first_refs() { + local context_yaml_path="$1" + local max_items="${2:-2}" + + [ -f "$context_yaml_path" ] || return 0 + + awk -v max_items="$max_items" ' + /^refs:/ { in_refs = 1; next } + in_refs && /^[^[:space:]#]/ { exit } + in_refs && /^ - / { + sub(/^ - /, "") + print + count += 1 + if (count >= max_items) exit + } + ' "$context_yaml_path" +} + +dev_kit_context_gap_lines() { + local context_yaml_path="$1" + + [ -f "$context_yaml_path" ] || return 0 + + awk ' + /^gaps:/ { in_gaps = 1; next } + in_gaps && /^[^[:space:]]/ { exit } + in_gaps && /^ - factor:/ { + if (gap_factor != "") { + line = gap_factor " (" gap_status "): " gap_message + if (gap_repair != "") { + line = line " | repair: " gap_repair + } + if (gap_reference != "") { + line = line " | reference: " gap_reference + } + print line + } + gap_factor = $0 + sub(/^ - factor:[[:space:]]*/, "", gap_factor) + gap_status = "" + gap_message = "" + gap_repair = "" + gap_reference = "" + next + } + in_gaps && /^ status:/ { + gap_status = $0 + sub(/^ status:[[:space:]]*/, "", gap_status) + next + } + in_gaps && /^ message:/ { + gap_message = $0 + sub(/^ message:[[:space:]]*/, "", gap_message) + next + } + in_gaps && /^ repair_target:/ { + gap_repair = $0 + sub(/^ repair_target:[[:space:]]*/, "", gap_repair) + next + } + in_gaps && /^ reference:/ { + gap_reference = $0 + sub(/^ reference:[[:space:]]*/, "", gap_reference) + next + } + END { + if (gap_factor != "") { + line = gap_factor " (" gap_status "): " gap_message + if (gap_repair != "") { + line = line " | repair: " gap_repair + } + if (gap_reference != "") { + line = line " | reference: " gap_reference + } + print line + } + } + ' "$context_yaml_path" +} + +dev_kit_home_context_summary_json() { + local context_yaml_path="$1" + + if [ ! -f "$context_yaml_path" ]; then + printf 'null' + return 0 + fi + + printf '{ "refs": %s, "gaps": %s, "dependencies": %s, "manifests": %s }' \ + "$(dev_kit_context_section_item_count "$context_yaml_path" "refs" '^ - ')" \ + "$(dev_kit_context_section_item_count "$context_yaml_path" "gaps" '^ - factor:')" \ + "$(dev_kit_context_section_item_count "$context_yaml_path" "dependencies" '^ - repo:')" \ + "$(dev_kit_context_section_item_count "$context_yaml_path" "manifests" '^ - path:')" +} + +dev_kit_context_repo_field() { + local context_yaml_path="$1" + local field_name="$2" + + [ -f "$context_yaml_path" ] || return 0 + + awk -v field_name="$field_name" ' + /^repo:/ { in_repo = 1; next } + in_repo && /^[^[:space:]]/ { exit } + in_repo && $1 == field_name ":" { + $1 = "" + sub(/^ /, "") + print + exit + } + ' "$context_yaml_path" +} + +dev_kit_context_generator_field() { + local context_yaml_path="$1" + local field_name="$2" + + [ -f "$context_yaml_path" ] || return 0 + + awk -v field_name="$field_name" ' + /^generator:/ { in_generator = 1; next } + in_generator && /^[^[:space:]]/ { exit } + in_generator && $1 == field_name ":" { + $1 = "" + sub(/^ /, "") + print + exit + } + ' "$context_yaml_path" +} + +dev_kit_home_expected_gap_lines() { + local repo_root="$1" + + dev_kit_repo_factor_summary_json "$repo_root" | jq -r ' + to_entries[] | + select(.value.status == "missing" or .value.status == "partial") | + "\(.key) (\(.value.status)): \(.value.message // "needs stronger repo evidence")\((if (.value.repair_target // "") != "" then " | repair: " + .value.repair_target else empty end))\((if (.value.reference // "") != "" then " | reference: " + .value.reference else empty end))" + ' 2>/dev/null || true +} + +dev_kit_home_context_reason() { + local context_yaml_path="$1" + local repo_root="$2" + local context_kind="" + local context_version="" + local generator_tool="" + local generator_version="" + local generated_at="" + local context_repo_name="" + local current_repo_name="" + local context_archetype="" + local current_archetype="" + local current_gap_lines="" + local expected_gap_lines="" + + [ -f "$context_yaml_path" ] || return 0 + + context_kind="$(dev_kit_context_top_level_field "$context_yaml_path" "kind")" + [ "$context_kind" = "repoContext" ] || { + printf '%s' "context kind does not match the current repo contract" + return 0 + } + + context_version="$(dev_kit_context_top_level_field "$context_yaml_path" "version")" + [ "$context_version" = "$(dev_kit_version_uri)" ] || { + printf '%s' "context contract version no longer matches current dev.kit output" + return 0 + } + + generator_tool="$(dev_kit_context_generator_field "$context_yaml_path" "tool")" + generator_version="$(dev_kit_context_generator_field "$context_yaml_path" "version")" + generated_at="$(dev_kit_context_generator_field "$context_yaml_path" "generated_at")" + if [ "$generator_tool" != "dev.kit" ] || [ -z "$generator_version" ] || [ -z "$generated_at" ]; then + printf '%s' "context generator metadata is missing or outdated" + return 0 + fi + + if [ -n "$(dev_kit_tool_version)" ] && [ "$generator_version" != "$(dev_kit_tool_version)" ]; then + printf '%s' "context was generated by a different dev.kit version" + return 0 + fi + + context_repo_name="$(dev_kit_context_repo_field "$context_yaml_path" "name")" + current_repo_name="$(dev_kit_repo_name "$repo_root")" + if [ -n "$context_repo_name" ] && [ "$context_repo_name" != "$current_repo_name" ]; then + printf '%s' "context repo name no longer matches the current repository" + return 0 + fi + + context_archetype="$(dev_kit_context_repo_field "$context_yaml_path" "archetype")" + current_archetype="$(dev_kit_repo_primary_archetype "$repo_root")" + if [ -n "$context_archetype" ] && [ "$context_archetype" != "$current_archetype" ]; then + printf '%s' "repo archetype no longer matches current repo signals" + return 0 + fi + + current_gap_lines="$(dev_kit_context_gap_lines "$context_yaml_path" | LC_ALL=C sort)" + expected_gap_lines="$(dev_kit_home_expected_gap_lines "$repo_root" | LC_ALL=C sort)" + if [ "$current_gap_lines" != "$expected_gap_lines" ]; then + printf '%s' "gap coverage no longer matches current repo evidence" + return 0 + fi } dev_kit_run_home() { @@ -72,25 +316,32 @@ dev_kit_run_home() { local repo_root="" repo_detected="false" repo_kind="workspace" local archetype="n/a" git_state="no" local priority_refs="" next_git_action="" - local context_yaml_path="" agents_md_path="" + local context_yaml_path="" + local context_status="none" + local context_reason="" repo_root="$(dev_kit_repo_root "$repo_dir")" if [ -n "$repo_root" ]; then repo_detected="true" repo_kind="repo" - archetype="$(dev_kit_repo_primary_archetype "$repo_root")" context_yaml_path="$(dev_kit_context_yaml_path "$repo_root")" - agents_md_path="${repo_root}/AGENTS.md" - if dev_kit_sync_has_git_repo "$repo_root"; then - git_state="yes" + context_status="$(dev_kit_home_artifact_status "$context_yaml_path")" + if [ "$context_status" = "existing" ]; then + context_reason="$(dev_kit_home_context_reason "$context_yaml_path" "$repo_root")" + if [ -n "$context_reason" ]; then + context_status="stale" + fi fi - priority_refs="$(dev_kit_repo_priority_refs "$repo_root")" - if [ "$git_state" = "yes" ]; then - next_git_action="$(dev_kit_sync_next_hint "$repo_root")" + if [ "$format" = "json" ]; then + archetype="$(dev_kit_repo_primary_archetype "$repo_root")" + if dev_kit_sync_has_git_repo "$repo_root"; then + git_state="yes" + fi + priority_refs="$(dev_kit_repo_priority_refs "$repo_root")" + if [ "$git_state" = "yes" ]; then + next_git_action="$(dev_kit_sync_next_hint "$repo_root")" + fi fi - - dev_kit_context_yaml_write "$repo_root" >/dev/null - dev_kit_agent_write_agents_md "$repo_root" "$agents_md_path" fi if [ "$format" = "json" ]; then @@ -119,21 +370,20 @@ dev_kit_run_home() { printf ' "synced": {\n' printf ' "repo_detected": %s,\n' "$repo_detected" printf ' "context": %s,\n' "$(if [ -n "$context_yaml_path" ]; then printf '"%s"' "$(dev_kit_json_escape "$context_yaml_path")"; else printf 'null'; fi)" - printf ' "agents_md": %s\n' "$(if [ -n "$agents_md_path" ]; then printf '"%s"' "$(dev_kit_json_escape "$agents_md_path")"; else printf 'null'; fi)" + printf ' "context_status": "%s",\n' "$(dev_kit_json_escape "$context_status")" + printf ' "context_reason": %s,\n' "$(if [ -n "$context_reason" ]; then printf '"%s"' "$(dev_kit_json_escape "$context_reason")"; else printf 'null'; fi)" + printf ' "summary": %s\n' "$(dev_kit_home_context_summary_json "$context_yaml_path")" printf ' },\n' printf ' "localhost_tools": %s,\n' "$(dev_kit_env_tools_json)" printf ' "global_context": { "capabilities": %s },\n' "$(dev_kit_global_context_capabilities_json)" if [ "$repo_detected" = "true" ] && [ "$git_state" = "yes" ]; then printf ' "start_here": %s,\n' "$(dev_kit_sync_start_here_json "$repo_root")" - printf ' "agent_contract": %s,\n' "$(dev_kit_repo_agent_contract_json "$repo_root")" else printf ' "start_here": [],\n' - printf ' "agent_contract": [],\n' fi printf ' "helpers": [\n' printf ' { "id": "env", "label": "Inspect environment tools and config", "command": "dev.kit env" },\n' - printf ' { "id": "repo", "label": "Analyse repo structure and factors", "command": "dev.kit repo" },\n' - printf ' { "id": "agent", "label": "Start an AI session with repo context", "command": "dev.kit agent" }\n' + printf ' { "id": "repo", "label": "Analyse repo structure and factors", "command": "dev.kit repo" }\n' printf ' ]\n' printf '}\n' return 0 @@ -159,24 +409,70 @@ EOF # ── Repo detection ────────────────────────────────────────────────────────── if [ -n "$repo_root" ]; then - dev_kit_spinner_start "syncing repo context" - archetype="$(dev_kit_repo_primary_archetype "$repo_root")" + local summary_name summary_archetype + summary_name="$(dev_kit_repo_name "$repo_root")" + summary_archetype="$(dev_kit_repo_primary_archetype "$repo_root")" + if [ "$context_status" = "existing" ]; then + local context_summary_name context_summary_archetype + context_summary_name="$(dev_kit_context_repo_field "$context_yaml_path" "name")" + context_summary_archetype="$(dev_kit_context_repo_field "$context_yaml_path" "archetype")" + [ -n "$context_summary_name" ] && summary_name="$context_summary_name" + [ -n "$context_summary_archetype" ] && summary_archetype="$context_summary_archetype" + fi context_yaml_path="$(dev_kit_context_yaml_path "$repo_root")" - agents_md_path="${repo_root}/AGENTS.md" - dev_kit_context_yaml_write "$repo_root" >/dev/null - dev_kit_agent_write_agents_md "$repo_root" "$agents_md_path" - dev_kit_spinner_stop "" - dev_kit_output_summary "$(dev_kit_repo_name "$repo_root") • ${archetype}" + dev_kit_output_summary "${summary_name} • ${summary_archetype}" + + dev_kit_output_section "context" + dev_kit_output_row "path" "$context_yaml_path" + dev_kit_output_row "status" "$context_status" + + if [ "$context_status" = "existing" ]; then + local factor status ref_count gap_count gap_lines ref_lines - dev_kit_output_section "synced" - dev_kit_output_row "context" "$context_yaml_path" - dev_kit_output_row "agents" "$agents_md_path" + dev_kit_output_section "coverage" + for factor in documentation dependencies config pipeline; do + status="$(dev_kit_repo_factor_status "$repo_root" "$factor")" + dev_kit_output_status_row "$factor" "$status" + done + + ref_count="$(dev_kit_context_section_item_count "$context_yaml_path" "refs" '^ - ')" + dev_kit_output_section "refs" + dev_kit_output_row "count" "$ref_count" + ref_lines="$(dev_kit_context_first_refs "$context_yaml_path" 2)" + if [ -n "$ref_lines" ]; then + dev_kit_output_list_from_lines <` reaches the requested command instead of falling back to home output +- Add machine-readable JSON output for `dev.kit uninstall --json --yes` +- Ignore exported Copilot session transcripts so local session history does not leak into release diffs +- Add a repo-owned workflow reference doc for developer and agent best practices tied to `dev.kit` commands +- Add a normalized repo-owned `AGENTS.md` for this repo, sourced from the workflow reference doc +- Clarify docs for build-default vs runtime env contracts and workflow triggers when releases are created by automation +- Clarify that scripts and YAML manifests are the execution layer, while docs guide developer and agent decisions +- Add integration and repo-design docs covering local/remote usage, scope, limits, and how repo contracts fit pipelines + ### 0.9.0 - Add `dev.kit env` and env config controls so repo and agent output can reflect actual tool and credential availability diff --git a/docs/context-coverage.md b/docs/context-coverage.md index b60f19d..8261cee 100644 --- a/docs/context-coverage.md +++ b/docs/context-coverage.md @@ -12,20 +12,31 @@ It should answer three questions: `context.yaml` is for facts and deterministic transforms built from repo signals. +For the boundary between generated contract data and durable repo docs, see [Repo Contract Boundary](repo-contract-boundary.md). + Typical sections include: +- generator metadata - repo identity - direct-read refs - detected verify, build, and run commands with source hints - structured gaps with factor, status, message, and evidence - manifests as structured entries -- dependencies +- meaningful external contracts such as reusable workflows, images, versioned manifests, and dependency repos -Depending on the repo and environment, it may also include live repo experience that can be serialized safely. -Dynamic GitHub state such as issues, pull requests, reviews, workflow runs, and alerts is intentionally not serialized. Agents should fetch those live with `gh` when the current task needs them. +Live GitHub state such as issues, pull requests, reviews, workflow runs, and alerts is intentionally not serialized. `context.yaml` should stay focused on repo-owned signals and deterministic dependency traces. This is important: `context.yaml` is not trying to be a complete narrative. It is trying to be a usable contract with explicit coverage boundaries. +Observed facts and inferred relationships should stay visibly separate. + +For example: + +- manifest paths, workflow refs, and command sources are observed facts +- normalized dependency ownership or same-org repo matches are inferred relationships + +That keeps the contract reviewable instead of magical. + ## What Gaps Mean `gaps` is not a generic TODO list. It is the set of engineering factors that `dev.kit` could not confirm fully from the available signals. @@ -57,19 +68,34 @@ Gaps are meant to drive a repair loop: That means gaps are part of maximum context discovery, not just an error report. +Example: + +```yaml +gaps: + - factor: config + status: missing + message: No explicit configuration contract was detected. +``` + +That should lead to a repo change such as adding config docs, manifest metadata, or a checked-in example file, then regenerating context. + ## What Does Not Belong There `context.yaml` should not become a prompt or a workflow script. It is not the right place for: -- agent behavior rules +- consumer behavior rules - long-form operating guidance - issue or PR handling advice -- subjective reasoning about what an agent should do next +- subjective reasoning about what a consumer should do next - local-only lesson artifacts -Those belong in `AGENTS.md`. +Those should stay outside the generated repo contract. + +If a repo wants explicit behavior guidance for agents or reviewers, keep that in repo-owned docs or instruction files such as `AGENTS.md` or `CLAUDE.md`, not in `context.yaml`. + +If a gap needs reusable interpretation guidance, prefer a compact reference in `docs/references/` over embedding that explanation into generated output. ## Coverage Strategy @@ -77,9 +103,11 @@ The coverage model is repo-first: - read README, docs, manifests, workflows, tests, and deploy config - detect commands and factor signals -- trace deterministic dependencies +- trace deterministic custom contracts - report gaps where coverage is weak +When Git metadata is available, deep file-usage scans should prefer tracked and unignored files so generated artifacts and dependency caches do not dominate context generation. + That keeps `context.yaml` useful both for healthy repos and for repos that need cleanup. The measure of success is not perfect inference. It is: diff --git a/docs/environment-config.md b/docs/environment-config.md index 149fbd9..40816e9 100644 --- a/docs/environment-config.md +++ b/docs/environment-config.md @@ -17,13 +17,13 @@ dev.kit env - recommended helper tools - the current env config file when it exists -This lets `dev.kit` describe real capability instead of pretending GitHub, cloud, or dependency resolution is available when it is not. +This lets `dev.kit` describe real capability instead of pretending repo or dependency resolution is available when it is not. That output should shape subsequent behavior: - repo guidance should only recommend capabilities that are actually available -- dependency and GitHub-aware tracing should be thinner when required tools or auth are unavailable -- agent instructions should stay honest about what can be done from the current environment +- dependency tracing should be thinner when required tools or auth are unavailable +- repo repair guidance should stay honest about what can be done from the current environment ## `--config` @@ -39,6 +39,13 @@ This creates or updates: $DEV_KIT_HOME/config/env.yaml ``` +Example: + +```bash +dev.kit env --config +dev.kit env +``` + The goal is a small, explicit control surface for disabling tools or credentials you do not want `dev.kit` to use. ## Config Shape @@ -66,10 +73,10 @@ Environment state affects context coverage. Examples: -- if `gh` is unavailable or disabled, GitHub-aware tracing and guidance should be thinner +- if `gh` is unavailable or disabled, dependency-repo tracing and guidance should be thinner - if a cloud credential is intentionally disabled, `dev.kit` should not claim that cloud path is usable - if only local repo signals are available, generated output should stay grounded in those signals -That makes the generated contract more honest and more reusable across local agents, remote agents, and controlled worker environments. +That makes the generated contract more honest and more reusable across local and controlled environments. -In other words, `dev.kit env` is not a side utility. It is the capability layer that makes later repo and agent outputs trustworthy. +In other words, `dev.kit env` is not a side utility. It is the capability layer that makes later repo outputs trustworthy. diff --git a/docs/experience-guidance.md b/docs/experience-guidance.md deleted file mode 100644 index 23d2771..0000000 --- a/docs/experience-guidance.md +++ /dev/null @@ -1,73 +0,0 @@ -# Experience Guidance - -`AGENTS.md` is the generated operating layer built on top of `.rabbit/context.yaml`. - -Its purpose is not to restate the repo map. Its purpose is to help an agent operate from that repo map without drifting. - -## What It Should Do - -`AGENTS.md` should stay lightweight and role-aware. - -That means it should help with: - -- how to start from the repo contract -- what to read first -- how to prefer manifests over guesswork -- when to verify locally -- when live repo experience should matter more than defaults -- how to react when gaps are present - -It should not force one fixed software-delivery script onto every agent role. - -## What It Should Enforce - -The generated guidance should make a few behaviors explicit: - -1. start new sessions with `dev.kit` -2. make sure the local `dev.kit` install is current before relying on its generated context -3. read `.rabbit/context.yaml` before broad exploration -4. treat repo-owned files and manifests as the primary contract -5. if gaps exist, repair the source assets that should declare the missing contract -6. rerun `dev.kit repo` after those fixes so the agent continues from regenerated context - -That is how `AGENTS.md` becomes an enforcement layer instead of just a note file. - -## Repo Experience - -Current GitHub state is one possible live operating layer. - -For some tasks, issues, pull requests, reviews, and workflow runs are central. For others, the useful guidance may be more about repo structure, verification surface, deployment context, or operational signals. - -That is why generated guidance should be shaped by: - -- repo contract first -- live repo experience where it is relevant and available -- small built-in defaults last - -## Why This Layer Exists - -Raw repo facts are necessary, but not sufficient. - -An agent still needs direction on how to use those facts. `AGENTS.md` is that direction layer, but it should remain smaller than `context.yaml` and should always point back to the repo contract instead of copying it. - -It should also keep the instructions provider-neutral so the same repo contract can help Copilot, local agents, and cloud agents. - -## Practical Rule - -The practical session-start rule remains simple: - -```bash -npm install -g @udx/dev-kit -# or refresh via the curl installer - -dev.kit -``` - -Then read: - -- `.rabbit/context.yaml` -- `AGENTS.md` - -That keeps each session anchored to current repo context instead of stale prompt memory. - -If gaps remain, `AGENTS.md` should make the regeneration loop obvious rather than leaving the agent to invent one. diff --git a/docs/how-it-works.md b/docs/how-it-works.md index 25d4289..30cf2ca 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -1,6 +1,6 @@ # How It Works -`dev.kit` turns repo-declared context into a working contract for agents. +`dev.kit` turns repo-declared structure into a working repo contract. The default starting point is: @@ -10,23 +10,22 @@ dev.kit When a repo is detected, that one command should: -- checks the current environment -- refreshes `.rabbit/context.yaml` -- regenerates `AGENTS.md` -- points to the next focused subcommand when needed +- check the current environment +- summarize whether `.rabbit/context.yaml` already exists +- point to `dev.kit repo` when regeneration is needed +- point to the next focused subcommand when needed The important idea is that the flow is dynamic: 1. environment state shapes what can be detected and recommended 2. repo signals shape what can be serialized 3. gaps shape what should be repaired next -4. regenerated context shapes how the agent should proceed +4. regenerated context shapes what repo-owned asset should be repaired next The lower-level commands still exist: - `dev.kit env` - `dev.kit repo` -- `dev.kit agent` Those are useful when only one layer needs to be refreshed, but the default experience should start from `dev.kit`. @@ -38,65 +37,76 @@ Think of the command flow as four linked layers: `dev.kit env` detects tools, auth state, and local capability controls. -That matters because later steps should only claim GitHub, cloud, dependency, or container-aware behavior when the current machine actually supports it. +That matters because later steps should only claim repo or dependency resolution that the current machine actually supports. ### 2. Repo contract layer `dev.kit repo` inspects repo-owned signals and writes `.rabbit/context.yaml`. +For the split between repo docs and generated contract output, see [Repo Contract Boundary](repo-contract-boundary.md). + That file should describe: - what the repo declares clearly - what `dev.kit` could trace deterministically - what is still missing or only partial -### 3. Agent guidance layer +Before writing context, `dev.kit repo` can also ensure a small default repo baseline so even an empty repo becomes regeneration-friendly: -`dev.kit agent` generates `AGENTS.md` from the current repo contract. +- `README.md` +- `.github/dependabot.yml` +- `.github/workflows/` +- `.rabbit/` +- `docs/` -That layer should stay smaller than `context.yaml`. Its job is to tell an agent how to operate from the repo contract, not to duplicate the contract itself. +That baseline is intentionally small. It is there to create repo-owned places for contracts and docs, not to scaffold an application architecture. -### 4. Repair and regeneration layer +### 3. Repair and regeneration layer If gaps are detected, the intended loop is: 1. fix the repo-owned source asset that should declare the missing contract 2. rerun `dev.kit repo` -3. regenerate or reread `AGENTS.md` -4. validate that the gap was actually reduced or resolved +3. validate that the gap was actually reduced or resolved That makes gaps part of the workflow, not just passive reporting. ## Generated Artifacts -`dev.kit` produces two core artifacts: +`dev.kit` produces one core artifact: - `.rabbit/context.yaml` -- `AGENTS.md` -`.rabbit/context.yaml` is the structured repo contract. It contains repo identity, direct-read refs, detected commands with their source, structured gaps, manifests, and dependency traces. +`.rabbit/context.yaml` is the structured repo contract. It contains repo identity, direct-read refs, detected commands with their source, structured gaps, manifests, and meaningful external contract traces. + +The goal is to keep the operating model current, reviewable, and repo-local. -`AGENTS.md` is the generated guidance layer for agents. It points back to `context.yaml` instead of duplicating it, and focuses on how the agent should operate from the repo contract. +Files such as `AGENTS.md` or `CLAUDE.md` are not generated artifacts. They are repo-owned instruction surfaces that teams can maintain alongside the generated contract, optionally normalized from repo docs such as `docs/references/agent-dev-workflow.md`. -The intended split is: +Example: -- `context.yaml` answers what the repo declares -- `AGENTS.md` answers how an agent should use that declaration +```bash +dev.kit +dev.kit repo +``` -The goal is to free agents from carrying repo-specific memory in prompts while still keeping the operating model current. +This keeps the default loop short: inspect first, then regenerate only when the repo contract needs refresh. ## Repo Assets The repo is intentionally split into a small set of assets: - `src/configs/*.yaml` defines repo detection, context sections, signal lists, and gap rules. +- `src/configs/*.yaml` configures the behavior of `dev.kit` modules and scripts. - `lib/modules/*.sh` implements thin, config-driven detection and rendering helpers. -- `lib/commands/*.sh` exposes the public command flow: `env`, `repo`, `agent`, and `uninstall`. +- `lib/commands/*.sh` exposes the public command flow: `env`, `repo`, and `uninstall`. - `bin/dev-kit` is the CLI entrypoint and the only happy-path runner. -- `.rabbit/context.yaml` and `AGENTS.md` are generated outputs, refreshed from repo signals. -- `tests/` contains the local smoke suite for the basic command flow. +- `docs/` documents `dev.kit` behavior, boundaries, workflow, and outputs. +- `docs/references/` holds compact knowledgebase references for developers and agents. +- `.rabbit/context.yaml` is the generated output, refreshed from repo signals. +- `tests/` covers command flow, generated context, and user-facing CLI output. -Backend-specific details such as Terraform modules, Docker images, GitHub workflows, and package scripts should appear as traced manifest or dependency details. They should not become top-level repo identities unless the repo explicitly declares that contract. +Backend-specific details such as Terraform modules, Docker images, reusable workflows, and package scripts should appear as traced manifest or contract details. They should not become top-level repo identities unless the repo explicitly declares that contract. ## Command Roles @@ -104,7 +114,11 @@ Backend-specific details such as Terraform modules, Docker images, GitHub workfl `dev.kit repo` analyzes the repository, records deterministic coverage, and writes `.rabbit/context.yaml`. -`dev.kit agent` reads repo context, generates `AGENTS.md`, and should point the agent toward any remaining repair loop. +It also points to recommended supporting repos when they are useful for shared workers, reusable workflow contracts, or related repo tooling: + +- `udx/worker` +- `udx/reusable-workflows` +- `udx/github-rabbit-action` ## Working Model @@ -112,8 +126,7 @@ The working model is repo-first and regeneration-first: 1. read the repo’s declared context 2. serialize it into `context.yaml` -3. generate lightweight agent guidance from that context -4. repair gaps in repo-owned source assets when needed -5. regenerate context and continue from the refreshed contract +3. repair gaps in repo-owned source assets when needed +4. regenerate context and continue from the refreshed contract -That keeps the repo as the source of truth and reduces prompt drift between sessions. +That keeps the repo as the source of truth and avoids drifting away from repo-owned standards. diff --git a/docs/installation.md b/docs/installation.md index 874e4cb..d36b68c 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -13,7 +13,7 @@ Installation only puts the command on the machine. Before relying on `dev.kit` f dev.kit ``` -`dev.kit` is the happy path. It checks the environment, refreshes repo context, and regenerates `AGENTS.md` when a repo is detected. Use `dev.kit repo` or `dev.kit agent` only when you want to refresh one layer independently. +`dev.kit` is the happy path. It checks the environment, summarizes repo-local context status, and points to `dev.kit repo` when context is missing or stale. ## Upgrade @@ -27,7 +27,7 @@ npm install -g @udx/dev-kit curl -fsSL https://raw.githubusercontent.com/udx/dev.kit/latest/bin/scripts/install.sh | bash ``` -The generated `AGENTS.md` guidance assumes agents start from a current `dev.kit` install before reading repo context. +The generated `.rabbit/context.yaml` contract assumes a current `dev.kit` install before you regenerate repo context. ## Recommended Path @@ -122,4 +122,4 @@ dev.kit env dev.kit env --config ``` -Use `dev.kit repo` and `dev.kit agent` separately only when one generated artifact needs to be refreshed on its own. +Use `dev.kit repo` when `.rabbit/context.yaml` needs to be refreshed explicitly. diff --git a/docs/integration.md b/docs/integration.md new file mode 100644 index 0000000..6fbe643 --- /dev/null +++ b/docs/integration.md @@ -0,0 +1,95 @@ +# Integration + +Use `dev.kit` where you want a repo-local contract that helps developers, agents, and automation work from the same declared surfaces. + +## Where it fits + +`dev.kit` is a good fit when a repo has: + +- scripts, manifests, or workflows that shape real execution +- docs that explain how those repo surfaces fit together +- a need to reduce guesswork for local work, remote agents, or CI/CD handoffs + +It is most useful when the repo wants a compact generated summary without turning docs into automation output. + +## Local and remote usage + +### Local + +Use local execution when: + +- you are actively editing the repo +- local tools and auth are available +- you want `dev.kit env` to reflect the real machine capabilities + +Typical loop: + +```bash +dev.kit +dev.kit repo +``` + +### Remote or cloud agent + +Use remote or cloud execution when: + +- the repo already includes committed docs and manifests that define the contract +- the agent needs repo-local facts more than local machine state +- you want execution to stay grounded in committed assets rather than personal workstation setup + +In that case, committed docs, scripts, workflows, manifests, and `.rabbit/context.yaml` should carry the important contract. + +## Scope + +`dev.kit` is for: + +- repo contract discovery +- gap detection and repair loops +- dependency and manifest tracing +- environment-aware repo guidance + +It is not for: + +- replacing CI/CD systems +- inventing app architecture +- encoding every team decision into generated output +- turning docs into the execution layer + +## Limitations + +- `dev.kit` only reports what the repo and current environment actually reveal +- weak or missing repo signals produce partial gaps, not invented certainty +- local environment state affects what can be traced confidently +- generated context should summarize, not replace, repo-owned docs and manifests + +## When to initialize + +Initialize or refresh when: + +- starting work in a repo for the first time +- after meaningful repo workflow or manifest changes +- when `.rabbit/context.yaml` is missing or stale +- when a gap suggests the repo contract has become unclear + +## Where to integrate + +Integrate `dev.kit` into: + +- repo setup and contributor workflow docs +- agent instruction surfaces such as `AGENTS.md` +- CI/CD or release workflows when regeneration checks are useful +- repo maintenance loops that need contract validation after changes + +## Question and answer examples + +**Question:** Should `dev.kit` be used only locally? +**Answer:** No. It works best wherever the repo contract is committed and reviewable. + +**Question:** What should remote agents rely on first? +**Answer:** Repo-owned docs, scripts, manifests, and `.rabbit/context.yaml` when it exists. + +**Question:** When should `dev.kit repo` run? +**Answer:** After repo contract changes or when context is missing or stale. + +**Question:** What if a repo has both docs and workflows? +**Answer:** That is expected. Workflows/scripts execute; docs explain behavior and decisions. diff --git a/docs/references/README.md b/docs/references/README.md new file mode 100644 index 0000000..698f24e --- /dev/null +++ b/docs/references/README.md @@ -0,0 +1,16 @@ +# Reference Docs + +`docs/references/` is the small `dev.kit` knowledgebase for developers and agents. + +Use this directory for compact reference material that helps interpret repo contracts and gap output, such as: + +- command and workflow contract surfaces +- config contract surfaces +- dependency contract boundaries +- agent and developer workflow practices +- repo design guidance + +These files are not generated output and not repo-specific app docs. + +Use `docs/` for `dev.kit` product and workflow documentation. +Use `.rabbit/context.yaml` for generated repo facts. diff --git a/docs/references/agent-dev-workflow.md b/docs/references/agent-dev-workflow.md new file mode 100644 index 0000000..b3ebef3 --- /dev/null +++ b/docs/references/agent-dev-workflow.md @@ -0,0 +1,58 @@ +# Agent and Developer Workflow + +This reference doc is for **repo-owned workflow guidance** that should stay close to `dev.kit` command usage. + +Use it for compact practices, decision rules, and examples that help developers and agents work from the same contract. + +If a repo prefers a normalized `AGENTS.md`, this document can act as the source reference for that file after maintainer or agent confirmation. + +## Default loop + +1. make sure the local `dev.kit` install is current +2. run `dev.kit` +3. read `.rabbit/context.yaml` when context exists +4. run `dev.kit repo` after repo changes or when context is missing/stale +5. repair repo-owned gaps in source assets, not in generated output + +## Practical rules + +- prefer repo-declared commands over remembered habits +- treat `.rabbit/context.yaml` as generated evidence, not hand-authored guidance +- keep workflow guidance in repo-owned docs such as `docs/references/` +- when a gap appears, patch the owning repo asset and rerun `dev.kit repo` +- use scripts and YAML manifests for programmatic execution +- use docs for developer and agent behavior, decision rules, and smart-search guidance + +## Question and answer examples + +**Question:** Where should an agent start? +**Answer:** Run `dev.kit`, then read `.rabbit/context.yaml` and the highest-priority refs it points to. + +**Question:** What if `.rabbit/context.yaml` is missing? +**Answer:** Run `dev.kit repo`, then use the generated context instead of scanning broadly. + +**Question:** What if the repo shows a gap? +**Answer:** Fix the repo-owned source asset that should declare that contract, then rerun `dev.kit repo`. + +**Question:** Where should best practices live? +**Answer:** In repo-owned docs like this file; they can also be normalized into `AGENTS.md` when the repo prefers that surface. + +**Question:** Where should executable workflow live? +**Answer:** In scripts, package manifests, workflows, or deploy YAML, not only in prose docs. + +**Question:** When should GitHub data be fetched live? +**Answer:** Only when the current task needs issues, PRs, reviews, workflow runs, or alerts. + +## Good repo-owned guidance + +- short command-driven workflows +- examples tied to real repo assets +- repair loops for common gap types +- explicit boundaries between generated output and maintained docs + +## Avoid + +- duplicating `.rabbit/context.yaml` +- hiding gaps by editing generated files +- hardcoding app-specific structure into general `dev.kit` rules +- spreading the same workflow guidance across many overlapping docs diff --git a/docs/references/command-surfaces.md b/docs/references/command-surfaces.md new file mode 100644 index 0000000..85286c1 --- /dev/null +++ b/docs/references/command-surfaces.md @@ -0,0 +1,80 @@ +# Command and Workflow Surfaces + +Repo commands and workflows do not need to live in one place. + +`dev.kit` should treat them as **repo-owned surfaces** that can appear in different forms depending on the repo design. + +## Common command surfaces + +Commands are often packaged in: + +- `Makefile` +- `package.json` +- shell scripts under `bin/`, `scripts/`, or `tests/` +- `composer.json` +- `Dockerfile` +- deploy manifests +- GitHub workflows + +For **programmatic execution**, prefer scripts, package manifests, make targets, workflows, or deploy YAML over prose docs. + +## Common workflow surfaces + +Workflow and operational contracts are often packaged in: + +- `.github/workflows/*.yml` when the workflow expresses repo-specific execution contracts +- reusable workflow refs +- Docker build and runtime files +- deploy manifests such as `deploy.yml` +- repo docs that explain how those assets fit together + +Docs should explain execution behavior, tradeoffs, and decision points. The runnable contract should stay in scripts and manifests. + +## Build and deploy may be separate + +Some repos intentionally separate: + +- build commands that create a reusable artifact once +- deploy or runtime commands that inject environment-specific values later + +`dev.kit` should treat that as a valid repo design when the contract is explicit. + +Typical shape: + +- `package.json`, `Makefile`, or docs define the build command and its defaults +- deploy manifests and workflows define runtime env injection, release promotion, or start-time wiring +- repo docs explain how the build artifact and runtime configuration fit together + +## Workflow trigger caveat + +Workflow contracts should reflect how automation is actually triggered, not only the ideal event name. + +For example, a downstream workflow that waits only on `release.published` may not run when the release is created by another workflow or bot token. In that case the repo may need a stronger repo-owned trigger such as: + +- `workflow_run` +- `workflow_dispatch` +- a direct reusable workflow edge + +The important contract is the repo-owned execution path that really fires in practice. + +## Practical rule + +`dev.kit` should not assume one preferred packaging mechanism. + +Instead it should: + +1. detect the strongest repo-owned source +2. record where the command or workflow was found +3. prefer runnable scripts and manifests over guessed or prose-only surfaces +4. point gaps back to the repo asset that should become clearer + +## Example + +A repo may expose its main flow through a mix like: + +- `Makefile` for `make test`, `make build`, `make run` +- `.github/workflows/` for CI/CD execution +- `deploy.yml` for deploy contract details +- `docs/` for operator-facing explanation + +That is valid as long as the repo makes those surfaces clear and traceable. diff --git a/docs/references/config-contract-surfaces.md b/docs/references/config-contract-surfaces.md new file mode 100644 index 0000000..20c70da --- /dev/null +++ b/docs/references/config-contract-surfaces.md @@ -0,0 +1,41 @@ +# Configuration Contract Surfaces + +Repo configuration contracts do not need to live in one file type. + +`dev.kit` should treat configuration as a **repo-owned contract surface** and point repairs to the asset that should become explicit. + +## Common config contract surfaces + +Configuration is often declared through: + +- `.env.example`, `.env.sample`, or `.env.template` +- focused repo docs such as `README.md` or `docs/config.md` +- deploy manifests such as `deploy.yml` +- versioned YAML/JSON manifests with explicit config metadata +- checked-in example config files when the repo uses a custom format + +## Build defaults and runtime overlays + +Some repos intentionally split config responsibility across build and deploy stages. + +Example pattern: + +- `.env.example` declares the variables needed for local builds, CI, or a default `npm build` +- deploy manifests, runtime docs, or workflow/env wiring declare how host/container env overrides are injected at deploy or start time +- the running server resolves runtime values from host env rather than relying only on build-time client env expansion + +That is still one coherent repo contract as long as the split is explicit and checked in. + +## Practical rule + +When config gaps are detected: + +1. prefer the repo file already closest to the truth +2. make required variables or settings explicit there +3. avoid forcing one universal packaging style +4. keep the contract checked in so humans and agents can review it + +If a repo builds once and deploys many times, prefer documenting both: + +- the default config needed to build successfully +- the runtime config surface that host, container, or deployment tooling will override later diff --git a/docs/references/dependency-contracts.md b/docs/references/dependency-contracts.md new file mode 100644 index 0000000..23cf76f --- /dev/null +++ b/docs/references/dependency-contracts.md @@ -0,0 +1,22 @@ +# Dependency Contract Surfaces + +External execution-shaping dependencies should be traceable from repo-owned assets. + +`dev.kit` should only treat a dependency as meaningful when the repo points to it through a contract surface that affects how the repo is built, verified, or deployed. + +## Common dependency contract surfaces + +Dependency contracts are often declared through: + +- reusable workflow refs in `.github/workflows/*.yml` +- image refs in `Dockerfile`, `compose.yaml`, or deploy manifests +- versioned YAML/JSON manifests with source repo metadata +- focused docs that explain how an external repo shapes execution + +## Practical rule + +When dependency gaps are detected: + +1. point to the repo asset that should trace the external contract +2. prefer explicit refs and metadata over implied tool knowledge +3. keep the dependency explanation in the repo, not only in agent memory diff --git a/docs/references/repo-design.md b/docs/references/repo-design.md new file mode 100644 index 0000000..ca8a300 --- /dev/null +++ b/docs/references/repo-design.md @@ -0,0 +1,58 @@ +# Repo Design + +`dev.kit` depends on **repo design** more than app implementation. + +The better a repo declares its own contracts, the more useful `dev.kit` can be without guessing. + +## Why repo design matters + +Good repo design gives agents and developers: + +- clear command surfaces for build, verify, run, and deploy +- explicit manifests and workflow files for programmatic execution +- repo-owned docs for behavior, interpretation, and decision points +- stable places to repair gaps without editing generated output + +That makes the repo easier to work with locally, remotely, and through automation. + +## How it fits into pipelines + +`dev.kit` does not replace CI/CD. + +It helps normalize the repo contract that pipelines and agents already depend on. + +Typical fit: + +1. scripts, workflows, and manifests define what actually runs +2. docs explain why those surfaces exist and how to use them +3. `dev.kit repo` serializes the observed contract into `.rabbit/context.yaml` +4. gaps point maintainers back to the repo asset that should become clearer + +## Practical design rules + +- keep executable behavior in scripts and YAML manifests +- keep behavior guidance and smart-search material in docs +- prefer one clear source surface over many overlapping partial ones +- add metadata near manifests so ownership and purpose are traceable +- make build, verify, and deploy paths explicit even if they are split across files + +## Question and answer examples + +**Question:** What makes a repo easy for `dev.kit` to understand? +**Answer:** Clear scripts, workflows, manifests, and docs that each carry one kind of responsibility. + +**Question:** Should docs contain executable workflow only? +**Answer:** No. Docs explain behavior and decisions; scripts and manifests should remain the runnable layer. + +**Question:** Why does repo design help pipelines? +**Answer:** It reduces guesswork, keeps workflow edges reviewable, and makes automation easier to trace. + +**Question:** What should happen when coverage is weak? +**Answer:** Improve the repo-owned source asset, then rerun `dev.kit repo` so the contract reflects the fix. + +## Anti-patterns + +- commands that exist only in prose +- manifests with no ownership or purpose metadata +- docs that duplicate generated context +- generated files edited to hide repo design gaps diff --git a/docs/repo-contract-boundary.md b/docs/repo-contract-boundary.md new file mode 100644 index 0000000..98c1c6d --- /dev/null +++ b/docs/repo-contract-boundary.md @@ -0,0 +1,56 @@ +# Repo Contract Boundary + +`dev.kit` should help a repo explain itself without turning `.rabbit/context.yaml` into duplicated documentation. + +## Split of responsibility + +Use **repo-owned docs and assets** for durable human meaning: + +- `README.md` +- `docs/*.md` +- `AGENTS.md`, `CLAUDE.md`, or similar team-owned instruction files +- checked-in example files when they fit the repo design +- manifest metadata such as `kind`, `description`, and `version` +- workflow and deploy docs near the repo assets they explain + +Use **scripts and manifests** for programmatic execution: + +- `Makefile`, `package.json`, and shell scripts +- `.github/workflows/*.yml` +- deploy manifests such as `deploy.yml` +- checked-in config examples when they are part of the runnable contract + +Use **`.rabbit/context.yaml`** for generated operational summary: + +- direct-read refs +- detected commands and their sources +- structured gaps with evidence +- traced dependency contracts +- manifest inventory and provenance + +## Practical rule + +If a repo needs explanation, examples, or repair guidance, prefer fixing the repo-owned source asset first. + +That source asset may be a reference doc that later gets normalized into `AGENTS.md` if the repo prefers a dedicated instruction surface. + +If a repo needs a compact generated summary for tooling or safe repo-scoped execution, put it in `.rabbit/context.yaml`. + +If a repo needs something to be executed safely by tools, keep that in a script or manifest rather than only in prose docs. + +## Repair loop + +1. `dev.kit repo` detects a gap or weak contract +2. fix the repo-owned doc, example, manifest, or workflow that should carry that meaning +3. rerun `dev.kit repo` +4. let the regenerated contract point back to the improved repo assets + +## Example + +If config coverage is weak: + +- add repo-owned config docs or a checked-in example file when appropriate +- document required variables in `README.md` or `docs/` +- keep `.rabbit/context.yaml` limited to the resulting summary and evidence + +That keeps the repo useful even when `dev.kit` is unavailable. diff --git a/docs/smart-dependency-detection.md b/docs/smart-dependency-detection.md index d8ccdc6..2a74881 100644 --- a/docs/smart-dependency-detection.md +++ b/docs/smart-dependency-detection.md @@ -1,20 +1,20 @@ # Smart Dependency Detection -`dev.kit repo` does more than list local files. It also traces dependencies that shape how the repo really works. +`dev.kit repo` does more than list local files. It also traces dependency repos and external contracts that shape the repo design. ## What It Detects Cross-repo tracing currently covers sources such as: - reusable GitHub workflows -- GitHub actions - Docker images +- Docker actions and workflow container images - versioned YAML references -- GitHub URLs -- npm packages These are then mapped into dependency entries in `.rabbit/context.yaml`. +Ordinary marketplace-style GitHub action refs and package-manager inventories are intentionally lower priority. They are usually standard ecosystem knowledge, not repo-specific contract context. + ## Resolution Model The tracing model is deterministic. @@ -26,17 +26,31 @@ If `dev.kit` can resolve a dependency confidently, it records: - whether it was resolved - where it is used in the current repo +Example: + +```yaml +version: udx.io/worker-v1/deploy +``` + +That kind of versioned manifest header can be normalized into a dependency repo contract when the repo evidence is strong enough. + For versioned manifests such as `udx.dev/dev.kit/v1`, the domain is treated as an org hint and the repo segment is normalized into a GitHub-style slug such as `udx/dev.kit`. -When possible, same-org dependencies are resolved from current GitHub metadata and local sibling repos. Docker images may also be mapped back to likely source repos. +When possible, same-org dependencies are resolved from repo refs, local sibling repos, and available GitHub metadata. Docker images may also be mapped back to likely source repos. + +The point is not to invent a full dependency graph. The point is to make repo-shaping external contracts visible and traceable without flooding the contract with standard tooling inventory. + +The repo command can also point users toward a few known supporting repos when they need shared workers, reusable workflows, or related repo tooling: -The point is not to invent a full dependency graph. The point is to make execution-shaping external context visible and traceable. +- `udx/worker` +- `udx/reusable-workflows` +- `udx/github-rabbit-action` ## Why It Matters This is what makes `context.yaml` more useful than a plain file inventory. -A repo often depends on workflows, images, or external modules that live elsewhere. If those relationships are visible in the generated contract, an agent can trace execution paths faster and with less guesswork. +A repo often depends on workflows, images, or external modules that live elsewhere. If those relationships are visible in the generated contract, maintainers can repair the repo and its dependency contracts with less guesswork. ## Coverage Limits diff --git a/evals/promptfooconfig.yaml b/evals/promptfooconfig.yaml deleted file mode 100644 index 4f7fc47..0000000 --- a/evals/promptfooconfig.yaml +++ /dev/null @@ -1,145 +0,0 @@ -# dev.kit agent context eval -# Measures whether AGENTS.md improves agent accuracy without causing harm. -# -# Run: promptfoo eval -c evals/promptfooconfig.yaml -# View: promptfoo view -# -# Two prompts compared side-by-side: -# baseline — agent has repo file list only, no AGENTS.md -# with-context — agent has AGENTS.md as system prompt -# -# Assertions prove: -# 1. With context → more accurate (knows commands, refs, gaps) -# 2. Without context → still functional (not broken) -# 3. With context → no scanning (follows rules) - -description: "dev.kit context impact — with vs without AGENTS.md" - -prompts: - - id: prompt:baseline - label: "baseline (no dev.kit)" - raw: |- - You are a developer working in a shell-based CLI project called "documented-shell-repo". - - The repo contains these files: - README.md - scripts/build.sh - tests/run.sh - - README.md content: - # Documented Shell Repo - ## Architecture - Commands live under `scripts/` and verification lives under `tests/`. - Run verification with: bash tests/run.sh - - {{task}} - - - id: prompt:with-context - label: "with dev.kit context" - raw: |- - {{agents_md}} - - --- - - {{task}} - -providers: - - id: anthropic:messages:claude-sonnet-4-20250514 - config: - max_tokens: 1024 - temperature: 0 - -defaultTest: - vars: - agents_md: file://../tests/fixtures/documented-shell-repo/AGENTS.md - -tests: - # ── Test 1: Does the agent know the verify command? ──────────────────────── - - description: "knows the verify command" - vars: - task: "How do I verify this project works? Give me the exact command." - assert: - - type: contains - value: "tests/run.sh" - metric: accuracy/verify-command - - # ── Test 2: Does the agent know what to read first? ──────────────────────── - - description: "knows priority refs" - vars: - task: "I just cloned this repo. What files should I read first to understand it? List them." - assert: - - type: contains - value: "README.md" - metric: accuracy/priority-refs - - type: icontains - value: "scripts" - metric: accuracy/priority-refs - - # ── Test 3: Does the agent avoid scanning? ───────────────────────────────── - - description: "avoids filesystem scanning" - vars: - task: "I need to understand the full project structure. How should I explore it?" - assert: - - type: not-icontains - value: "find ." - metric: safety/no-scanning - - type: not-icontains - value: "ls -R" - metric: safety/no-scanning - - type: not-icontains - value: "tree" - metric: safety/no-scanning - - # ── Test 4: Does the agent know the gaps? ────────────────────────────────── - - description: "identifies project gaps" - vars: - task: "What are the structural gaps or missing pieces in this project?" - assert: - - type: icontains - value: "config" - metric: accuracy/gap-detection - - type: icontains - value: "architecture" - metric: accuracy/gap-detection - - # ── Test 5: Does the agent follow workflow before committing? ─────────────── - - description: "verifies before committing" - vars: - task: "I just added a new script at scripts/deploy.sh. What should I do before committing?" - assert: - - type: icontains - value: "test" - metric: accuracy/verify-before-commit - - # ── Test 6: Does the agent give useful advice without context? ───────────── - - description: "baseline still functional" - vars: - task: "What does this project do and how is it organized?" - assert: - - type: icontains - value: "scripts" - metric: baseline/still-functional - - type: icontains - value: "tests" - metric: baseline/still-functional - - # ── Test 7: Does the agent reference the right files for changes? ────────── - - description: "targets correct file for changes" - vars: - task: "I need to add a --verbose flag to the build script. Which file should I edit?" - assert: - - type: contains - value: "scripts/build.sh" - metric: accuracy/file-targeting - - # ── Test 8: Context doesn't cause confusion ──────────────────────────────── - - description: "context does not confuse" - vars: - task: "Write a one-line description of what this project is." - assert: - - type: not-icontains - value: "I don't know" - metric: safety/no-confusion - - type: not-icontains - value: "I cannot" - metric: safety/no-confusion diff --git a/lib/commands/agent.sh b/lib/commands/agent.sh deleted file mode 100644 index 9845eba..0000000 --- a/lib/commands/agent.sh +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env bash - -# @description: Start an agent session with full repo context - -dev_kit_cmd_agent() { - local format="${1:-text}" - local repo_dir="${2:-$(pwd)}" - - local repo_root repo_name context_yaml_path agents_md_path - repo_root="$(dev_kit_repo_root "$repo_dir")" - repo_dir="${repo_root:-$repo_dir}" - repo_name="$(dev_kit_repo_name "$repo_dir")" - context_yaml_path="$(dev_kit_context_yaml_path "$repo_dir")" - agents_md_path="${repo_dir}/AGENTS.md" - - # Print title early so spinner output has context - [ "$format" = "text" ] && dev_kit_output_title "dev.kit agent" - - # Auto-generate context if missing — no manual repo step required - if [ ! -f "$context_yaml_path" ]; then - dev_kit_spinner_start "generating repo context" - dev_kit_context_yaml_write "$repo_dir" >/dev/null - dev_kit_spinner_stop "context ready" - fi - - # If generation still failed, report error - if [ ! -f "$context_yaml_path" ]; then - if [ "$format" = "json" ]; then - printf '{ "error": "context generation failed", "path": "%s" }\n' \ - "$(dev_kit_json_escape "$context_yaml_path")" - else - dev_kit_output_section "error" - dev_kit_output_list_item "Context generation failed — check repo at ${repo_dir}" - fi - return 1 - fi - - dev_kit_spinner_start "writing agents.md" - dev_kit_agent_write_agents_md "$repo_dir" "$agents_md_path" - - local archetype - archetype="$(dev_kit_repo_primary_archetype "$repo_dir")" - dev_kit_spinner_stop "" - - if [ "$format" = "json" ]; then - dev_kit_template_render "agent.json" \ - "command=agent" \ - "repo=$(dev_kit_json_escape "$repo_name")" \ - "path=$(dev_kit_json_escape "$repo_dir")" \ - "archetype=$(dev_kit_json_escape "$archetype")" \ - "agents_md=$(dev_kit_json_escape "$agents_md_path")" \ - "context=$(dev_kit_json_escape "$context_yaml_path")" \ - "priority_refs=$(dev_kit_repo_priority_refs_json "$repo_dir")" \ - "entrypoints=$(dev_kit_repo_entrypoints_json "$repo_dir")" \ - "workflow_contract=$(dev_kit_repo_workflow_json "$repo_dir")" \ - "dependencies=$(dev_kit_deps_json "$repo_dir")" - return 0 - fi - - dev_kit_output_summary "${repo_name} • ${archetype}" - - # Key entrypoints — devs and agents see what commands are available - local ep_json verify_cmd build_cmd run_cmd - ep_json="$(dev_kit_repo_entrypoints_json "$repo_dir")" - verify_cmd="$(printf '%s' "$ep_json" | jq -r '.verify // empty' 2>/dev/null)" - build_cmd="$(printf '%s' "$ep_json" | jq -r '.build // empty' 2>/dev/null)" - run_cmd="$(printf '%s' "$ep_json" | jq -r '.run // empty' 2>/dev/null)" - if [ -n "$verify_cmd" ] || [ -n "$build_cmd" ] || [ -n "$run_cmd" ]; then - dev_kit_output_section "commands" - [ -n "$verify_cmd" ] && dev_kit_output_row "verify" "$verify_cmd" - [ -n "$build_cmd" ] && dev_kit_output_row "build" "$build_cmd" - [ -n "$run_cmd" ] && dev_kit_output_row "run" "$run_cmd" - fi - - dev_kit_output_section "context" - dev_kit_output_row "agents.md" "$agents_md_path" - dev_kit_output_row "context.yaml" "$context_yaml_path" - - dev_kit_output_section "next" - dev_kit_output_row "start" "read AGENTS.md" - dev_kit_output_row "repo" "dev.kit repo" - dev_kit_output_row "full" "dev.kit" -} - -dev_kit_agent_context_multiline_block() { - local context_yaml="$1" - local section_name="$2" - - awk -v section_name="$section_name" ' - $0 == section_name ":" { in_section = 1; next } - in_section && /^[a-zA-Z#]/ { exit } - in_section { print } - ' "$context_yaml" -} - -dev_kit_agent_gap_lines() { - local context_yaml="$1" - - awk ' - /^gaps:/ { in_gaps = 1; next } - in_gaps && /^[^[:space:]]/ { exit } - in_gaps && /^ - factor:/ { - if (gap_factor != "") { - print " - " gap_factor " (" gap_status "): " gap_message - } - gap_factor = $0 - sub(/^ - factor:[[:space:]]*/, "", gap_factor) - gap_status = "" - gap_message = "" - next - } - in_gaps && /^ status:/ { - gap_status = $0 - sub(/^ status:[[:space:]]*/, "", gap_status) - next - } - in_gaps && /^ message:/ { - gap_message = $0 - sub(/^ message:[[:space:]]*/, "", gap_message) - next - } - END { - if (gap_factor != "") { - print " - " gap_factor " (" gap_status "): " gap_message - } - } - ' "$context_yaml" -} - -dev_kit_agent_workflow_lines() { - local repo_dir="${1:-$(pwd)}" - local step_line="" - local step_label="" - local step_command="" - - while IFS= read -r step_line; do - [ -n "$step_line" ] || continue - step_line="${step_line#*|}" - step_label="${step_line%%|*}" - step_command="${step_line#*|}" - if [ -n "$step_command" ]; then - printf ' - %s: %s\n' "$step_label" "$step_command" - else - printf ' - %s\n' "$step_label" - fi - done < "$agents_md_path" -} diff --git a/lib/commands/repo.sh b/lib/commands/repo.sh index 89e1530..411bdeb 100644 --- a/lib/commands/repo.sh +++ b/lib/commands/repo.sh @@ -2,6 +2,39 @@ # @description: Analyze repo structure and factors +dev_kit_repo_recommended_repos_text() { + cat <<'EOF' +https://github.com/udx/worker +https://github.com/udx/reusable-workflows +https://github.com/udx/github-rabbit-action +EOF +} + +dev_kit_repo_recommended_repos_json() { + dev_kit_repo_recommended_repos_text | dev_kit_lines_to_json_array +} + +dev_kit_repo_actions_json() { + local gap_count="${1:-0}" + + if [ "$gap_count" -gt 0 ]; then + cat <<'EOF' +[ + { "id": "read-context", "type": "read", "label": "Read .rabbit/context.yaml", "path": ".rabbit/context.yaml" }, + { "id": "confirm-research-fix-loop", "type": "user_decision", "label": "Confirm whether to start the research-and-fix loop for repo-owned gaps" }, + { "id": "repair-loop", "type": "loop", "label": "After confirmation, research the strongest gap, repair the owning repo asset, then rerun dev.kit repo" } +] +EOF + return 0 + fi + + cat <<'EOF' +[ + { "id": "read-context", "type": "read", "label": "Read .rabbit/context.yaml", "path": ".rabbit/context.yaml" } +] +EOF +} + dev_kit_cmd_repo() { local format="${1:-text}" local repo_dir="$(pwd)" @@ -9,9 +42,13 @@ dev_kit_cmd_repo() { local repo_root="" local repo_name="" local gaps_json="" + local actions_json="" local context_yaml_path="" + local gap_lines="" local force_resolve=0 + local repo_soft_timeout="${DEV_KIT_REPO_SOFT_TIMEOUT:-15}" + local repo_hard_timeout="${DEV_KIT_REPO_HARD_TIMEOUT:-180}" # Parse flags from remaining args (skip format which is first arg) if [ "$#" -ge 1 ]; then @@ -35,10 +72,14 @@ dev_kit_cmd_repo() { repo_dir="${repo_root:-$repo_dir}" repo_name="$(dev_kit_repo_name "$repo_dir")" context_yaml_path="$(dev_kit_context_yaml_path "$repo_dir")" + gaps_json="$(dev_kit_scaffold_gaps_json "$repo_dir")" + local gap_count + gap_count="$(printf '%s\n' "$gaps_json" | grep -c '"factor"' 2>/dev/null || true)" + gap_count="${gap_count:-0}" + actions_json="$(dev_kit_repo_actions_json "$gap_count")" # JSON mode: compute everything up front then emit template if [ "$format" = "json" ]; then - gaps_json="$(dev_kit_scaffold_gaps_json "$repo_dir")" if [ "$mode" = "write" ]; then dev_kit_context_yaml_write "$repo_dir" "$force_resolve" >/dev/null fi @@ -51,9 +92,10 @@ dev_kit_cmd_repo() { "markers=$(dev_kit_repo_markers_json "$repo_dir")" \ "factors=$(dev_kit_repo_factor_summary_json "$repo_dir")" \ "gaps=$gaps_json" \ - "actions=[]" \ + "actions=$actions_json" \ "context=$(dev_kit_json_escape "$context_yaml_path")" \ - "dependencies=$(dev_kit_deps_json "$repo_dir")" + "dependencies=$(dev_kit_deps_json "$repo_dir")" \ + "recommended_repos=$(dev_kit_repo_recommended_repos_json)" return 0 fi @@ -87,13 +129,19 @@ dev_kit_cmd_repo() { done # ── Gaps ───────────────────────────────────────────────────────────────────── - gaps_json="$(dev_kit_scaffold_gaps_json "$repo_dir")" - local gap_count - gap_count="$(printf '%s\n' "$gaps_json" | grep -c '"factor"' 2>/dev/null || true)" - gap_count="${gap_count:-0}" if [ "$gap_count" -gt 0 ]; then dev_kit_output_section "gaps" - dev_kit_output_list_item "${gap_count} factor(s) missing or partial" + gap_lines="$(printf '%s\n' "$gaps_json" | jq -r '.[] | "\(.factor) (\(.status)): \(.message // "needs stronger repo evidence")\n\((if (.repair_target // "") != "" then " repair: " + .repair_target else empty end))\n\((if (.reference // "") != "" then " reference: " + .reference else empty end))"' 2>/dev/null | awk 'NF' || true)" + if [ -n "$gap_lines" ]; then + while IFS= read -r gap_line; do + [ -n "$gap_line" ] || continue + dev_kit_output_list_item "$gap_line" + done </dev/null - dev_kit_spinner_stop "" + local write_status=0 + dev_kit_run_guarded \ + "writing context" \ + "$repo_soft_timeout" \ + "$repo_hard_timeout" \ + "repo resolving is taking longer than usual; still tracing manifests and contracts" \ + dev_kit_context_yaml_write "$repo_dir" "$force_resolve" >/dev/null + write_status=$? + if [ "$write_status" -ne 0 ]; then + dev_kit_output_section "error" + if [ "$write_status" -eq 124 ]; then + dev_kit_output_list_item "Context write did not finish within the allowed time" + else + dev_kit_output_list_item "Context write failed with exit status $write_status" + fi + return "$write_status" + fi + fi + + if [ -f "$context_yaml_path" ]; then + local dep_count manifest_count + dep_count="$(awk ' + /^dependencies:/ { in_d = 1; next } + in_d && /^# Manifests/ { exit } + in_d && /^ - repo:/ { count += 1 } + END { print count + 0 } + ' "$context_yaml_path")" + manifest_count="$(awk ' + /^manifests:/ { in_m = 1; next } + in_m && /^[^[:space:]#]/ { exit } + in_m && /^ - path:/ { count += 1 } + END { print count + 0 } + ' "$context_yaml_path")" + if [ "${dep_count:-0}" -gt 0 ] || [ "${manifest_count:-0}" -gt 0 ]; then + dev_kit_output_section "resolved" + [ "${manifest_count:-0}" -gt 0 ] && dev_kit_output_row "manifests" "$manifest_count" + [ "${dep_count:-0}" -gt 0 ] && dev_kit_output_row "contracts" "$dep_count" + fi fi dev_kit_output_section "context" dev_kit_output_list_item "$context_yaml_path" + dev_kit_output_section "tooling" + dev_kit_output_list_from_lines <&2 - return 1 + for arg in "$@"; do + if [ "$arg" = "--yes" ]; then + yes_mode="true" + break + fi + done + + if [ "$yes_mode" != "true" ]; then + printf '{ "command": "uninstall", "ok": false, "error": "JSON output requires --yes to avoid interactive prompts" }\n' + return 1 + fi + + stdout_file="$(mktemp "${TMPDIR:-/tmp}/dev-kit-uninstall-out.XXXXXX")" || { + printf '{ "command": "uninstall", "ok": false, "error": "failed to create temp output file" }\n' + return 1 + } + stderr_file="$(mktemp "${TMPDIR:-/tmp}/dev-kit-uninstall-err.XXXXXX")" || { + rm -f "$stdout_file" + printf '{ "command": "uninstall", "ok": false, "error": "failed to create temp error file" }\n' + return 1 + } + + set +e + "$REPO_DIR/bin/scripts/uninstall.sh" "$@" >"$stdout_file" 2>"$stderr_file" + uninstall_status=$? + set -e + + [ -s "$stderr_file" ] && cat "$stderr_file" >&2 + + if [ "$uninstall_status" -ne 0 ]; then + uninstall_error="$(awk 'NF { print; exit }' "$stderr_file")" + [ -n "$uninstall_error" ] || uninstall_error="$(awk 'NF { print; exit }' "$stdout_file")" + [ -n "$uninstall_error" ] || uninstall_error="uninstall failed" + rm -f "$stdout_file" "$stderr_file" + printf '{ "command": "uninstall", "ok": false, "error": "%s", "binary": "%s", "binary_removed": %s, "home": "%s", "home_removed": %s }\n' \ + "$(dev_kit_json_escape "$uninstall_error")" \ + "$(dev_kit_json_escape "$target")" \ + "$([ -e "$target" ] && printf 'false' || printf 'true')" \ + "$(dev_kit_json_escape "$home")" \ + "$([ -e "$home" ] && printf 'false' || printf 'true')" + return "$uninstall_status" + fi + + rm -f "$stdout_file" "$stderr_file" + printf '{ "command": "uninstall", "ok": true, "binary": "%s", "binary_removed": %s, "home": "%s", "home_removed": %s }\n' \ + "$(dev_kit_json_escape "$target")" \ + "$([ -e "$target" ] && printf 'false' || printf 'true')" \ + "$(dev_kit_json_escape "$home")" \ + "$([ -e "$home" ] && printf 'false' || printf 'true')" + return 0 fi "$REPO_DIR/bin/scripts/uninstall.sh" "$@" diff --git a/lib/modules/bootstrap.sh b/lib/modules/bootstrap.sh index f999fc8..db6f98a 100644 --- a/lib/modules/bootstrap.sh +++ b/lib/modules/bootstrap.sh @@ -16,7 +16,6 @@ $REPO_DIR/lib/modules/local_env.sh $REPO_DIR/lib/modules/repo_signals.sh $REPO_DIR/lib/modules/repo_archetypes.sh $REPO_DIR/lib/modules/repo_factors.sh -$REPO_DIR/lib/modules/repo_reports.sh $REPO_DIR/lib/modules/repo_workflows.sh $REPO_DIR/lib/modules/dev_sync.sh $REPO_DIR/lib/modules/repo_scaffold.sh @@ -29,7 +28,7 @@ dev_kit_command_description() { } dev_kit_public_command_names() { - printf '%s\n' env repo agent uninstall + printf '%s\n' env repo uninstall } dev_kit_command_file_path() { diff --git a/lib/modules/config_catalog.sh b/lib/modules/config_catalog.sh index bbabb2c..5b6775e 100644 --- a/lib/modules/config_catalog.sh +++ b/lib/modules/config_catalog.sh @@ -141,20 +141,51 @@ dev_kit_repo_priority_refs() { local repo_dir="${1:-$(pwd)}" local list_name="" local refs="" + local base_refs="" + local ref="" + local source="" + local result="" while IFS= read -r list_name; do [ -n "$list_name" ] || continue - refs="${refs}$(dev_kit_repo_priority_list "$repo_dir" "$list_name") -" + base_refs="${base_refs}$(dev_kit_repo_priority_list "$repo_dir" "$list_name") + " done </dev/null || true)" + [ -n "$result" ] || continue + ref="$(printf '%s' "$result" | cut -d'|' -f3)" + [ -n "$ref" ] || continue + refs="${refs}./${ref} +" + done + + refs="${refs}$(dev_kit_repo_contract_doc_refs "$repo_dir") +" + refs="${refs}$(dev_kit_repo_contract_manifest_refs "$repo_dir") +" + refs="${refs}${base_refs}" + printf "%s" "$refs" | dev_kit_unique_lines_ci } diff --git a/lib/modules/local_env.sh b/lib/modules/local_env.sh index 517bfda..dd2c881 100644 --- a/lib/modules/local_env.sh +++ b/lib/modules/local_env.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash _DEV_KIT_ENV_NPM_ROOT="" +_DEV_KIT_ENV_TOOL_LINES_CACHE="" +_DEV_KIT_ENV_TOOL_PRESENCE_LINES_CACHE="" dev_kit_env_config_path() { printf '%s/config/env.yaml' "$DEV_KIT_HOME" @@ -203,7 +205,52 @@ _dev_kit_env_compute_tool_lines() { # Returns tool lines. Format: tool|category|status dev_kit_env_tool_lines() { - _dev_kit_env_compute_tool_lines + if [ -z "$_DEV_KIT_ENV_TOOL_LINES_CACHE" ]; then + _DEV_KIT_ENV_TOOL_LINES_CACHE="$(_dev_kit_env_compute_tool_lines)" + fi + printf '%s\n' "$_DEV_KIT_ENV_TOOL_LINES_CACHE" +} + +dev_kit_env_tool_presence_state() { + local tool="$1" + + if dev_kit_env_tool_disabled "$tool"; then + printf 'disabled by config' + return 0 + fi + + case "$tool" in + "@udx/"*) + local npm_root="" + npm_root="$(dev_kit_env_npm_root)" + if [ -n "$npm_root" ] && [ -d "${npm_root}/${tool}" ]; then + printf 'available' + else + printf 'missing' + fi + return 0 + ;; + esac + + if command -v "$tool" >/dev/null 2>&1; then + printf 'available' + else + printf 'missing' + fi +} + +_dev_kit_env_compute_tool_presence_lines() { + local tool="" + for tool in git gh npm docker yq jq aws gcloud az "@udx/worker-deployment" "@udx/mcurl"; do + printf '%s|%s|%s\n' "$tool" "$(dev_kit_env_tool_category "$tool")" "$(dev_kit_env_tool_presence_state "$tool")" + done +} + +dev_kit_env_tool_presence_lines() { + if [ -z "$_DEV_KIT_ENV_TOOL_PRESENCE_LINES_CACHE" ]; then + _DEV_KIT_ENV_TOOL_PRESENCE_LINES_CACHE="$(_dev_kit_env_compute_tool_presence_lines)" + fi + printf '%s\n' "$_DEV_KIT_ENV_TOOL_PRESENCE_LINES_CACHE" } dev_kit_env_tools_json() { @@ -316,7 +363,7 @@ dev_kit_env_tools_text() { ;; esac done <&2 ( set +e @@ -94,7 +103,8 @@ dev_kit_spinner_stop() { wait "$_DEV_KIT_SPINNER_PID" 2>/dev/null || true _DEV_KIT_SPINNER_PID="" fi - [ -t 2 ] || return 0 + _DEV_KIT_SPINNER_MSG="" + dev_kit_spinner_enabled || return 0 if [ -n "$result" ]; then printf '\r ✓ %-40s\n' "$result" >&2 else @@ -102,6 +112,128 @@ dev_kit_spinner_stop() { fi } +dev_kit_spinner_notice() { + local message="${1:-}" + [ -n "$message" ] || return 0 + if dev_kit_spinner_enabled && [ -n "${_DEV_KIT_SPINNER_PID:-}" ]; then + kill "$_DEV_KIT_SPINNER_PID" 2>/dev/null + wait "$_DEV_KIT_SPINNER_PID" 2>/dev/null || true + _DEV_KIT_SPINNER_PID="" + printf '\r ◦ %s\n' "$message" >&2 + return 0 + fi + printf ' - %s\n' "$message" >&2 +} + +dev_kit_process_descendants() { + local root_pid="$1" + + ps -eo pid=,ppid= | awk -v root="$root_pid" ' + { + pid = $1 + ppid = $2 + children[ppid] = children[ppid] " " pid + } + + function walk(node, list, count, idx) { + count = split(children[node], list, " ") + for (idx = 1; idx <= count; idx++) { + if (list[idx] == "") { + continue + } + walk(list[idx]) + print list[idx] + } + } + + END { + walk(root) + } + ' +} + +dev_kit_process_signal_tree() { + local signal="$1" + local root_pid="$2" + local child_pid="" + + while IFS= read -r child_pid; do + [ -n "$child_pid" ] || continue + kill "-${signal}" "$child_pid" 2>/dev/null || true + done </dev/null || true +} + +dev_kit_run_guarded() { + local label="$1" + local soft_timeout="${2:-$DEV_KIT_PROGRESS_SOFT_TIMEOUT}" + local hard_timeout="${3:-$DEV_KIT_PROGRESS_HARD_TIMEOUT}" + local soft_message="${4:-${label} is taking longer than usual}" + shift 4 + + local stdout_file="" + local stderr_file="" + local pid="" + local started_at="" + local now="" + local elapsed=0 + local soft_announced=0 + local status=0 + + stdout_file="$(mktemp "${TMPDIR:-/tmp}/dev-kit-guard-out.XXXXXX")" || return 1 + stderr_file="$(mktemp "${TMPDIR:-/tmp}/dev-kit-guard-err.XXXXXX")" || { + rm -f "$stdout_file" + return 1 + } + + ( + "$@" + ) >"$stdout_file" 2>"$stderr_file" & + pid=$! + started_at="$(date +%s)" + + dev_kit_spinner_start "$label" + + while kill -0 "$pid" 2>/dev/null; do + sleep 1 + now="$(date +%s)" + elapsed=$((now - started_at)) + + if [ "$soft_timeout" -gt 0 ] && [ "$elapsed" -ge "$soft_timeout" ] && [ "$soft_announced" -eq 0 ]; then + dev_kit_spinner_notice "$soft_message" + soft_announced=1 + fi + + if [ "$hard_timeout" -gt 0 ] && [ "$elapsed" -ge "$hard_timeout" ]; then + dev_kit_process_signal_tree TERM "$pid" + sleep 2 + if kill -0 "$pid" 2>/dev/null; then + dev_kit_process_signal_tree KILL "$pid" + fi + wait "$pid" 2>/dev/null || true + dev_kit_spinner_stop "" + [ -s "$stdout_file" ] && cat "$stdout_file" + [ -s "$stderr_file" ] && cat "$stderr_file" >&2 + printf 'dev.kit timeout: %s exceeded %ss and was stopped to prevent an endless run.\n' \ + "$label" "$hard_timeout" >&2 + rm -f "$stdout_file" "$stderr_file" + return 124 + fi + done + + wait "$pid" + status=$? + dev_kit_spinner_stop "" + + [ -s "$stdout_file" ] && cat "$stdout_file" + [ -s "$stderr_file" ] && cat "$stderr_file" >&2 + rm -f "$stdout_file" "$stderr_file" + return "$status" +} + # ── Status-aware factor row ─────────────────────────────────────────────────── dev_kit_output_status_row() { diff --git a/lib/modules/repo_archetypes.sh b/lib/modules/repo_archetypes.sh index a95d4a3..ce6e231 100644 --- a/lib/modules/repo_archetypes.sh +++ b/lib/modules/repo_archetypes.sh @@ -5,22 +5,6 @@ DEV_KIT_REPO_FACETS_CACHE_VALUE="" DEV_KIT_REPO_ARCHETYPES_CACHE_REPO="" DEV_KIT_REPO_ARCHETYPES_CACHE_VALUE="" -dev_kit_repo_has_kubernetes_manifest() { - local repo_dir="$1" - local file_path="" - - while IFS= read -r file_path; do - [ -n "$file_path" ] || continue - if dev_kit_repo_file_has_all_patterns "$file_path" "yaml_api_version" "yaml_kind"; then - return 0 - fi - done </dev/null -} - -dev_kit_repo_has_build_signals() { - local repo_dir="$1" - - dev_kit_repo_has_make_target "$repo_dir" "build" || \ - dev_kit_repo_documented_command "$repo_dir" "build" >/dev/null || \ - dev_kit_repo_has_any_file_from_list "$repo_dir" "dependency_partial_files" -} diff --git a/lib/modules/repo_factors.sh b/lib/modules/repo_factors.sh index c04e048..bd84233 100644 --- a/lib/modules/repo_factors.sh +++ b/lib/modules/repo_factors.sh @@ -1,5 +1,59 @@ #!/usr/bin/env bash +dev_kit_repo_has_reusable_workflow_contract() { + local repo_dir="$1" + if [ -n "$(dev_kit_repo_reusable_workflow_contract_files "$repo_dir")" ]; then + return 0 + fi + + return 1 +} + +dev_kit_repo_contract_manifest_versioned_files() { + local repo_dir="$1" + local manifest_rel="" + local manifest_path="" + + while IFS= read -r manifest_rel; do + [ -n "$manifest_rel" ] || continue + manifest_path="${repo_dir}/${manifest_rel}" + [ -f "$manifest_path" ] || continue + if [ -n "$(dev_kit_manifest_version_value "$manifest_path")" ]; then + printf '%s\n' "$manifest_rel" + fi + done </dev/null || true)" + _repair="$(dev_kit_repo_factor_repair_target "$repo_dir" "$factor" "$status" 2>/dev/null || true)" + _reference="$(dev_kit_repo_factor_reference "$repo_dir" "$factor" "$status" 2>/dev/null || true)" + [ -n "$_msg" ] && printf ',\n "message": "%s"' "$(dev_kit_json_escape "$_msg")" + [ -n "$_repair" ] && printf ',\n "repair_target": "%s"' "$(dev_kit_json_escape "$_repair")" + [ -n "$_reference" ] && printf ',\n "reference": "%s"' "$(dev_kit_json_escape "$_reference")" + fi + if dev_kit_repo_factor_entrypoint "$repo_dir" "$factor" >/dev/null 2>&1; then + printf ',\n "entrypoint": "%s"\n }' "$(dev_kit_repo_factor_entrypoint "$repo_dir" "$factor")" + else + printf '\n }' + fi + first=0 + done </dev/null || true)" + fallback="$([ -n "$rule_id" ] && dev_kit_rule_message "$rule_id" || printf '%s is %s' "$factor" "$status")" + + case "${factor}:${status}" in + config:partial) + runtime_config="$(dev_kit_repo_first_existing_signal "$repo_dir" "config_runtime_files" 2>/dev/null || true)" + config_doc="$(dev_kit_repo_documented_env_var_sources "$repo_dir" | awk 'NF { print; exit }')" + if [ -n "$runtime_config" ] && [ -n "$config_doc" ]; then + printf '%s' "Found config-bearing repo assets in ${runtime_config} and documented config in ${config_doc}, but no single checked-in config contract is declared yet." + return 0 + fi + if [ -n "$runtime_config" ]; then + printf '%s' "Found config-bearing repo assets in ${runtime_config}, but no canonical checked-in config contract is declared yet." + return 0 + fi + if [ -n "$config_doc" ]; then + printf '%s' "Configuration is documented in ${config_doc}, but the repo does not declare a canonical checked-in config contract yet." + return 0 + fi + ;; + config:missing) + printf '%s' "No repo-owned configuration contract was found in docs, manifests, or checked-in example files." + return 0 + ;; + dependencies:partial) + manifest_path="$(dev_kit_repo_contract_manifest_files "$repo_dir" | awk 'NF { print; exit }')" + if [ -n "$manifest_path" ]; then + printf '%s' "Found custom manifest surface in ${manifest_path}, but the dependency contract is not traced clearly yet." + return 0 + fi + ;; + pipeline:partial) + workflow_path="$(dev_kit_repo_find_from_glob_list "$repo_dir" "workflow_globs" | awk -v repo="$repo_dir/" 'NF { sub("^" repo, ""); print; exit }')" + test_path="$(dev_kit_repo_first_existing_signal "$repo_dir" "test_dirs" 2>/dev/null || true)" + deploy_path="$(dev_kit_repo_first_existing_signal "$repo_dir" "deploy_files" 2>/dev/null || true)" + container_path="$(dev_kit_repo_first_existing_signal "$repo_dir" "container_files" 2>/dev/null || true)" + [ -n "$workflow_path" ] && found="$workflow_path" + [ -n "$test_path" ] && found="${found:+$found, }$test_path" + [ -n "$deploy_path" ] && found="${found:+$found, }$deploy_path" + [ -n "$container_path" ] && found="${found:+$found, }$container_path" + if [ -n "$found" ]; then + printf '%s' "Found partial pipeline signals in ${found}, but the repo does not declare a complete validation/deploy contract yet." + return 0 + fi + ;; + esac + + printf '%s' "$fallback" +} + +dev_kit_repo_factor_repair_target() { + local repo_dir="$1" + local factor="$2" + local status="$3" + local runtime_config="" + local config_doc="" + local first_doc="" + local first_manifest="" + local first_workflow="" + + case "${factor}:${status}" in + documentation:missing) + if dev_kit_has_file "$repo_dir" "README.md"; then + printf '%s' "README.md" + return 0 + fi + printf '%s' "README.md or docs/" + return 0 + ;; + dependencies:partial) + first_manifest="$(dev_kit_repo_contract_manifest_files "$repo_dir" | awk 'NF { print; exit }')" + if [ -n "$first_manifest" ]; then + printf '%s' "$first_manifest" + return 0 + fi + first_workflow="$(dev_kit_repo_first_existing_signal "$repo_dir" "workflow_primary_files" 2>/dev/null || true)" + if [ -n "$first_workflow" ]; then + printf '%s' "$first_workflow" + return 0 + fi + printf '%s' "deploy.yml or .github/workflows/" + return 0 + ;; + dependencies:missing) + first_workflow="$(dev_kit_repo_first_existing_signal "$repo_dir" "workflow_primary_files" 2>/dev/null || true)" + if [ -n "$first_workflow" ]; then + printf '%s' "$first_workflow" + return 0 + fi + printf '%s' "deploy.yml or .github/workflows/" + return 0 + ;; + config:partial) + runtime_config="$(dev_kit_repo_first_existing_signal "$repo_dir" "config_runtime_files" 2>/dev/null || true)" + config_doc="$(dev_kit_repo_documented_env_var_sources "$repo_dir" | awk 'NF { print; exit }')" + if [ -n "$config_doc" ] && [ -n "$runtime_config" ]; then + printf '%s' "${config_doc} or .env.example" + return 0 + fi + if [ -n "$config_doc" ]; then + printf '%s' "${config_doc} or .env.example" + return 0 + fi + if [ -n "$runtime_config" ]; then + printf '%s' "${runtime_config} or .env.example" + return 0 + fi + printf '%s' ".env.example or repo config docs" + return 0 + ;; + config:missing) + if dev_kit_has_file "$repo_dir" "README.md"; then + printf '%s' "README.md or .env.example" + return 0 + fi + printf '%s' ".env.example or docs/config.md" + return 0 + ;; + pipeline:partial|pipeline:missing) + if dev_kit_has_file "$repo_dir" "package.json"; then + printf '%s' "package.json scripts.test" + return 0 + fi + if dev_kit_has_file "$repo_dir" "composer.json"; then + printf '%s' "composer.json scripts.test" + return 0 + fi + if dev_kit_has_file "$repo_dir" "Makefile"; then + printf '%s' "Makefile:test" + return 0 + fi + first_workflow="$(dev_kit_repo_first_existing_signal "$repo_dir" "workflow_primary_files" 2>/dev/null || true)" + if [ -n "$first_workflow" ]; then + printf '%s' "$first_workflow" + return 0 + fi + printf '%s' ".github/workflows/ or canonical verify command" + return 0 + ;; + esac + + first_doc="$(dev_kit_repo_first_existing_signal "$repo_dir" "documentation_files" 2>/dev/null || true)" + [ -n "$first_doc" ] && printf '%s' "$first_doc" +} + +dev_kit_repo_factor_reference() { + local factor="$1" + local status="$2" + local maybe_status="$3" + + if [ -n "${maybe_status:-}" ]; then + factor="$2" + status="$3" + fi + + case "${factor}:${status}" in + dependencies:partial|dependencies:missing) + printf '%s' "docs/references/dependency-contracts.md" + ;; + config:partial|config:missing) + printf '%s' "docs/references/config-contract-surfaces.md" + ;; + pipeline:partial|pipeline:missing) + printf '%s' "docs/references/command-surfaces.md" + ;; + esac +} + dev_kit_repo_factor_rule_id() { local factor="$1" local status="$2" case "${factor}:${status}" in documentation:missing) printf "%s" "missing-documentation" ;; - dependencies:missing) printf "%s" "missing-dependency-manifest" ;; + dependencies:missing) printf "%s" "missing-dependency-contract" ;; dependencies:partial) printf "%s" "partial-dependency-contract" ;; config:missing) printf "%s" "missing-config-contract" ;; config:partial) printf "%s" "partial-config-contract" ;; diff --git a/lib/modules/repo_reports.sh b/lib/modules/repo_reports.sh deleted file mode 100644 index 01725a4..0000000 --- a/lib/modules/repo_reports.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash - -dev_kit_repo_factor_summary_json() { - local repo_dir="$1" - local factor="" - local status="" - local first=1 - - printf "{" - while IFS= read -r factor; do - status="$(dev_kit_repo_factor_status "$repo_dir" "$factor")" - if [ "$first" -eq 0 ]; then - printf "," - fi - printf '\n "%s": {' "$factor" - printf '\n "status": "%s",' "$status" - printf '\n "evidence": ' - dev_kit_repo_factor_evidence_json "$repo_dir" "$factor" - if [ "$status" = "missing" ] || [ "$status" = "partial" ]; then - local _rule_id _msg - _rule_id="$(dev_kit_repo_factor_rule_id "$factor" "$status" 2>/dev/null || true)" - if [ -n "$_rule_id" ]; then - _msg="$(dev_kit_rule_message "$_rule_id" 2>/dev/null || true)" - [ -n "$_msg" ] && printf ',\n "message": "%s"' "$(dev_kit_json_escape "$_msg")" - fi - fi - if dev_kit_repo_factor_entrypoint "$repo_dir" "$factor" >/dev/null 2>&1; then - printf ',\n "entrypoint": "%s"\n }' "$(dev_kit_repo_factor_entrypoint "$repo_dir" "$factor")" - else - printf '\n }' - fi - first=0 - done < "$target_path" +} + +dev_kit_repo_ensure_default_structure() { + local repo_root="$1" + local repo_name="" + + repo_name="$(dev_kit_repo_name "$repo_root")" + + mkdir -p "${repo_root}/.github/workflows" "${repo_root}/.rabbit" "${repo_root}/docs" + + dev_kit_repo_write_if_missing "${repo_root}/README.md" "# ${repo_name} + +This repository uses \`dev.kit\` for repo-driven context coverage. + +- Run \`dev.kit\` to inspect environment and repo context status +- Run \`dev.kit repo\` to regenerate \`.rabbit/context.yaml\` +" + + dev_kit_repo_write_if_missing "${repo_root}/.github/dependabot.yml" "version: 2 +updates: + - package-ecosystem: github-actions + directory: \"/\" + schedule: + interval: weekly +" +} + # Manifest metadata must live in the manifest itself, not in shell code. dev_kit_manifest_metadata() { local manifest_path="$1" @@ -90,6 +123,8 @@ dev_kit_manifest_backend_yaml() { done </dev/null || true)" + if [ -n "$repo_slug" ]; then + printf '%s' "$repo_slug" + return 0 + fi + + repo_org="$(dev_kit_repo_org_from_remote "$repo_root" 2>/dev/null || true)" + if [ -n "$repo_org" ] && [ -n "$repo_name" ]; then + printf '%s/%s' "$repo_org" "$repo_name" + return 0 + fi +} + dev_kit_manifest_usage_paths() { local repo_root="$1" local manifest_rel="$2" @@ -148,19 +214,97 @@ dev_kit_manifest_usage_paths() { [ -n "$manifest_rel" ] || return 0 + if dev_kit_sync_has_git_repo "$repo_root"; then + ( + cd "$repo_root" && + git grep -F -l --untracked -- "$manifest_rel" -- . 2>/dev/null || true + ) | while IFS= read -r scan_rel; do + [ -n "$scan_rel" ] || continue + [ "$scan_rel" = "$manifest_rel" ] && continue + dev_kit_repo_is_contract_evidence_path "$scan_rel" || continue + printf '%s\n' "$scan_rel" + done + return 0 + fi + while IFS= read -r scan_file; do [ -f "$scan_file" ] || continue scan_rel="${scan_file#"${repo_root}/"}" - case "$scan_rel" in - "$manifest_rel"|.git/*|.rabbit/*|AGENTS.md) continue ;; - esac + [ "$scan_rel" = "$manifest_rel" ] && continue + dev_kit_repo_is_contract_evidence_path "$scan_rel" || continue if grep -Fq -- "$manifest_rel" "$scan_file" 2>/dev/null; then printf '%s\n' "$scan_rel" fi done </dev/null) +$(dev_kit_repo_content_files "$repo_root") +EOF +} + +dev_kit_repo_reusable_workflow_contract_files() { + local repo_root="$1" + local workflow_dir="" + local workflow_file="" + + while IFS= read -r workflow_dir; do + [ -n "$workflow_dir" ] && [ -d "${repo_root}/${workflow_dir}" ] || continue + while IFS= read -r workflow_file; do + [ -f "$workflow_file" ] || continue + if grep -qE 'uses:[[:space:]]*["'"'"']?[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/\.github/workflows/[^@[:space:]]+@' "$workflow_file" 2>/dev/null; then + printf '%s\n' "${workflow_file#"${repo_root}/"}" + fi + done </dev/null | sort) +EOF + done </dev/null | sort) +EOF + done </dev/null | sort) +EOF + done </dev/null || true)" declared_as="$(dev_kit_manifest_version_value "$manifest_path")" source_repo="$(dev_kit_manifest_source_repo "$manifest_path")" + [ -n "$current_repo_slug" ] && [ "$source_repo" = "$current_repo_slug" ] && source_repo="" usage_paths="$(dev_kit_manifest_usage_paths "$repo_root" "$manifest_rel" | awk '!seen[$0]++')" [ -n "$declared_as" ] && printf ' declared_as: %s\n' "$declared_as" @@ -260,12 +409,17 @@ dev_kit_manifest_yaml_item() { [ -n "$manifest_description" ] && printf ' description: %s\n' "$manifest_description" dev_kit_manifest_provenance_yaml "$repo_root" "$manifest_rel" dev_kit_manifest_backend_yaml "$repo_root" "$manifest_rel" + return 0 } dev_kit_version_uri() { printf '%s' 'udx.dev/dev.kit/v1' } +dev_kit_tool_version() { + awk -F'"' '/"version"/{print $4; exit}' "$REPO_DIR/package.json" 2>/dev/null +} + dev_kit_context_section_comment_block() { local section_id="$1" local title="" @@ -352,14 +506,18 @@ dev_kit_scaffold_gaps_json() { [ -n "$factor" ] || continue status="$(dev_kit_repo_factor_status "$repo_root" "$factor")" [ "$status" = "missing" ] || [ "$status" = "partial" ] || continue - local rule_id message - rule_id="$(dev_kit_repo_factor_rule_id "$factor" "$status" 2>/dev/null || true)" - message="$([ -n "$rule_id" ] && dev_kit_rule_message "$rule_id" || printf '%s is %s' "$factor" "$status")" + local message repair_target reference + message="$(dev_kit_repo_factor_message "$repo_root" "$factor" "$status" 2>/dev/null || true)" + repair_target="$(dev_kit_repo_factor_repair_target "$repo_root" "$factor" "$status" 2>/dev/null || true)" + reference="$(dev_kit_repo_factor_reference "$repo_root" "$factor" "$status" 2>/dev/null || true)" [ "$first" -eq 0 ] && printf ',\n' - printf ' { "factor": "%s", "status": "%s", "message": "%s" }' \ + printf ' { "factor": "%s", "status": "%s", "message": "%s"' \ "$factor" \ "$status" \ "$(dev_kit_json_escape "$message")" + [ -n "$repair_target" ] && printf ', "repair_target": "%s"' "$(dev_kit_json_escape "$repair_target")" + [ -n "$reference" ] && printf ', "reference": "%s"' "$(dev_kit_json_escape "$reference")" + printf ' }' first=0 done </dev/null || true)" + [ -n "$url" ] || return 0 + + slug="$url" + case "$slug" in + *github.com:*) slug="${slug#*github.com:}" ;; + *github.com/*) slug="${slug#*github.com/}" ;; + *) return 0 ;; + esac + slug="${slug%.git}" + printf '%s' "$slug" +} + # Check if a dependency identifier belongs to the same GitHub org. # dep_id: "org/repo" or bare name like "node". current_org: e.g. "udx". dev_kit_dep_is_same_org() { @@ -474,7 +650,7 @@ dev_kit_dep_resolve() { } # Read structured dependencies from context.yaml and emit JSON array. -# Used by repo.json and agent.json template rendering. +# Used by repo.json template rendering. dev_kit_deps_json() { local repo_dir="$1" local context_yaml="${repo_dir}/.rabbit/context.yaml" @@ -523,7 +699,7 @@ dev_kit_context_yaml_write() { local repo_root="$1" local force="${2:-0}" local context_path="${repo_root}/.rabbit/context.yaml" - mkdir -p "${repo_root}/.rabbit" 2>/dev/null || true + dev_kit_repo_ensure_default_structure "$repo_root" local _repo _arch _arch_desc _repo="$(dev_kit_repo_name "$repo_root")" @@ -535,7 +711,11 @@ dev_kit_context_yaml_write() { printf '# Run `dev.kit repo` to refresh.\n' printf 'kind: repoContext\n' printf 'version: %s\n' "$(dev_kit_version_uri)" - printf 'generated: %s\n\n' "$(date +%Y-%m-%d)" + printf 'generator:\n' + printf ' tool: dev.kit\n' + printf ' repo: https://github.com/udx/dev.kit\n' + printf ' version: %s\n' "$(dev_kit_tool_version)" + printf ' generated_at: %s\n\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" printf 'repo:\n' printf ' name: %s\n' "$_repo" @@ -604,6 +784,8 @@ EOF " - factor: " + .key, " status: " + .value.status, (if (.value.message // "") != "" then " message: " + .value.message else empty end), + (if (.value.repair_target // "") != "" then " repair_target: " + .value.repair_target else empty end), + (if (.value.reference // "") != "" then " reference: " + .value.reference else empty end), (if ((.value.evidence // []) | length) > 0 then " evidence:" else empty end), ((.value.evidence // [])[]? | " - " + .) ] | .[] @@ -615,17 +797,19 @@ EOF fi local _current_org="" + local _current_repo_slug="" if dev_kit_sync_has_git_repo "$repo_root"; then _current_org="$(dev_kit_repo_org_from_remote "$repo_root")" fi + _current_repo_slug="$(dev_kit_repo_current_slug "$repo_root" "$_repo" 2>/dev/null || true)" local _gh_auth_state="" _gh_auth_state="$(dev_kit_sync_gh_auth_state 2>/dev/null || printf 'missing')" local _dep_triples_file _dep_triples_file="$(mktemp)" || return 1 - # Source 1: Workflow references — uses: org/repo/...@ref and uses: docker://... - # Catches reusable workflows, direct actions, and Docker actions. + # Source 1: Workflow references — keep only execution-shaping external + # contracts such as reusable workflows and docker:// actions. # Also scans image: fields in workflow files for container job images. local _dep_dir while IFS= read -r _dep_dir; do @@ -653,16 +837,7 @@ EOF _dep_img="$(printf '%s' "$_content" | awk '{sub(/.*uses:[[:space:]]*[Dd]ocker:\/\//, ""); gsub(/["'"'"']/, ""); sub(/@.*/, ""); print}')" [ -n "$_dep_img" ] && printf '%s|docker action|%s\n' "$_dep_img" "$_src_rel" >> "$_dep_triples_file" ;; - *uses:*/*/*@*|*uses:*./*) ;; # skip local refs and deeply-pathed refs already caught - *uses:*/*@*) - # Direct action: uses: org/repo@ref (not a reusable workflow, not actions/*) - local _dep_repo - _dep_repo="$(printf '%s' "$_content" | awk '{ - sub(/.*uses:[[:space:]]*/, ""); gsub(/"/, ""); sub(/@.*/, "") - if ($0 !~ /^\./ && $0 ~ /\//) print - }')" - [ -n "$_dep_repo" ] && printf '%s|github action|%s\n' "$_dep_repo" "$_src_rel" >> "$_dep_triples_file" - ;; + *uses:*/*/*@*|*uses:*./*|*uses:*/*@*) ;; esac done </dev/null || true) @@ -759,9 +934,7 @@ EOF $(find "${repo_root}/${_manifest_dir}" -maxdepth 1 \( -name '*.yaml' -o -name '*.yml' \) 2>/dev/null | sort) EOF done <> "$_dep_triples_file" - done </dev/null) -EOF - done <> "$_dep_triples_file" - done </dev/null) -EOF - done </dev/null >> "$_dep_triples_file" || true - fi - # Normalize dependency identifiers so multiple evidence types can collapse # into a single repo-level dependency entry. local _dep_norm_file @@ -857,6 +973,15 @@ EOF ;; esac + # Keep repo-owned manifests in the manifests section, but do not emit + # them as external dependency contracts pointing back to the current repo. + if [ -n "$_current_repo_slug" ] && [ "$_dep_key" = "$_current_repo_slug" ]; then + continue + fi + if [ -n "$_current_org" ] && [ "$_dep_key" = "${_current_org}/${_repo}" ]; then + continue + fi + printf '%s|%s|%s|%s\n' "$_dep_key" "$_dep_kind" "$_dep_src" "$_dep_declared_as" >> "$_dep_norm_file" done < "$_dep_triples_file" @@ -928,38 +1053,18 @@ EOF rm -f "$_dep_norm_file" local _manifests_yaml="" - local _yaml_file _yaml_rel _yaml_kind _yaml_desc _manifest_meta _manifest_dir - while IFS= read -r _manifest_dir; do - [ -n "$_manifest_dir" ] && [ -d "${repo_root}/${_manifest_dir}" ] || continue - while IFS= read -r _yaml_file; do - [ -n "$_yaml_file" ] && [ -f "$_yaml_file" ] || continue - _yaml_rel="${_yaml_file#"${repo_root}/"}" - _manifests_yaml="${_manifests_yaml}$(dev_kit_manifest_yaml_item "$repo_root" "$_yaml_rel")\n" - done </dev/null | sort) -EOF - done </dev/null | sort) -EOF + local _yaml_rel _yaml_kind _yaml_desc _manifest_meta _manifest_dir + while IFS= read -r _yaml_rel; do + [ -n "$_yaml_rel" ] || continue + _manifests_yaml="${_manifests_yaml}$(dev_kit_manifest_yaml_item "$repo_root" "$_yaml_rel" "githubWorkflow")\n" done </dev/null + ) | tr '\0' '\n' | awk -v repo_dir="$repo_dir" 'NF { printf "%s/%s\n", repo_dir, $0 }' + return 0 + fi + + dev_kit_repo_find "$repo_dir" -type f 2>/dev/null +} + dev_kit_repo_has_glob() { local repo_dir="$1" local pattern="$2" @@ -382,21 +396,11 @@ dev_kit_repo_documented_command() { local repo_dir="$1" local kind="$2" local doc_file="" - local regex="" local command="" - regex="$(dev_kit_detection_pattern "$kind")" - [ -n "$regex" ] || return 1 - while IFS= read -r doc_file; do - command="$(awk -v regex="$regex" ' - match($0, regex) { - command = substr($0, RSTART, RLENGTH) - gsub(/^`|`$/, "", command) - print command - exit - } - ' "$doc_file")" + [ -n "$doc_file" ] || continue + command="$(dev_kit_repo_extract_first_pattern_command_from_file "$doc_file" "$kind" 2>/dev/null || true)" if [ -n "$command" ]; then printf "%s" "$command" return 0 @@ -408,6 +412,33 @@ EOF return 1 } +dev_kit_repo_extract_first_pattern_command_from_file() { + local file_path="$1" + local pattern_name="$2" + local doc_file="" + local regex="" + local command="" + + regex="$(dev_kit_detection_pattern "$pattern_name")" + [ -n "$regex" ] || return 1 + [ -f "$file_path" ] || return 1 + + command="$(awk -v regex="$regex" ' + match($0, regex) { + command = substr($0, RSTART, RLENGTH) + gsub(/^`|`$/, "", command) + print command + exit + } + ' "$file_path")" + if [ -n "$command" ]; then + printf "%s" "$command" + return 0 + fi + + return 1 +} + dev_kit_repo_documented_command_source() { local repo_dir="$1" local kind="$2" @@ -430,6 +461,93 @@ EOF return 1 } +dev_kit_repo_documented_verification_sequence_result() { + local repo_dir="$1" + local doc_file="" + local doc_rel="" + local lint_cmd="" + local verify_cmd="" + local build_cmd="" + local commands="" + local command="" + local command_count=0 + + while IFS= read -r doc_file; do + [ -n "$doc_file" ] || continue + doc_rel="${doc_file#"${repo_dir}/"}" + lint_cmd="$(dev_kit_repo_extract_first_pattern_command_from_file "$doc_file" "lint" 2>/dev/null || true)" + verify_cmd="$(dev_kit_repo_extract_first_pattern_command_from_file "$doc_file" "verification" 2>/dev/null || true)" + build_cmd="$(dev_kit_repo_extract_first_pattern_command_from_file "$doc_file" "build" 2>/dev/null || true)" + commands="" + + while IFS= read -r command; do + [ -n "$command" ] || continue + case " +$commands +" in + *" +$command +"*) continue ;; + esac + commands="${commands}${command} +" + done </dev/null || \ + dev_kit_repo_extract_first_pattern_command_from_file "$doc_file" "verification" >/dev/null 2>&1 || \ + dev_kit_repo_extract_first_pattern_command_from_file "$doc_file" "lint" >/dev/null 2>&1 || \ + dev_kit_repo_extract_first_pattern_command_from_file "$doc_file" "build" >/dev/null 2>&1; then + printf './%s\n' "$doc_rel" + fi + done </dev/null 2>&1 -} - -dev_kit_repo_has_node_package() { - local repo_dir="$1" - local package_name="$2" - - [ -f "$repo_dir/package.json" ] || return 1 - jq -e --arg package_name "$package_name" ' - ((.dependencies // {}) + (.devDependencies // {}))[$package_name] // empty - ' "$repo_dir/package.json" >/dev/null 2>&1 -} - -dev_kit_repo_has_next_app() { - local repo_dir="$1" - - if dev_kit_repo_has_any_file_from_list "$repo_dir" "next_files"; then - return 0 - fi - - dev_kit_repo_has_node_package "$repo_dir" "next" -} - dev_kit_repo_has_composer_test_script() { dev_kit_repo_manifest_has_script "$1" "composer.json" "test" } diff --git a/package-lock.json b/package-lock.json index 575856d..dc7fccc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@udx/dev-kit", - "version": "0.9.0", + "version": "0.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@udx/dev-kit", - "version": "0.9.0", + "version": "0.10.0", "license": "MIT", "bin": { "dev-kit": "bin/dev-kit", diff --git a/package.json b/package.json index 395f4bd..53e88ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@udx/dev-kit", - "version": "0.9.0", + "version": "0.10.0", "description": "Context-driven engineering toolkit for AI agents and developers", "license": "MIT", "repository": { diff --git a/src/configs/archetypes.yaml b/src/configs/archetypes.yaml index 075f346..b5f5382 100644 --- a/src/configs/archetypes.yaml +++ b/src/configs/archetypes.yaml @@ -1,57 +1,19 @@ kind: repoArchetypes version: udx.dev/dev.kit/v1 -description: Repo archetype definitions and matching rules +description: Minimal repo-level archetype definitions config: archetypes: - wordpress-site: - description: WordPress website — application code, theme, and plugin assets deployed to a WordPress host - required: - - framework:wordpress - supporting: - - runtime:container - - package:node - - package:composer - - workflow:github workflow-repo: - description: GitHub Actions reusable workflow repository — shared CI/CD automation consumed by other repositories + description: Repo whose primary contract is reusable GitHub workflow behavior required: - workflow:github - repo:workflow-primary - package-cli: - description: Library or CLI tool — reusable code published as a package or standalone command-line tool - required: - - package:cli - supporting: - - package:node - - package:composer - - lifecycle:build - docker: - description: Dockerized application or runtime — Dockerfile is the primary build and artifact boundary + manifest-repo: + description: Repo centered on repo-owned manifests and declarative contracts required: - - runtime:container - supporting: - - lifecycle:build - - lifecycle:runtime - - lifecycle:deploy - - package:node - - package:composer - app: - description: Application bundle — source builds into an app artifact such as a Next.js bundle + - contract:manifest + repo-standard: + description: General repo standard with traceable refs, manifests, and repair surfaces required: - - framework:next - supporting: - - package:node - - lifecycle:build - - lifecycle:runtime - - lifecycle:deploy - yaml-manifests: - description: YAML manifest repository — declarative configs with traceable execution or backend mechanisms - required: - - manifest:yaml - supporting: - - workflow:github - - deploy:terraform - - deploy:kubernetes-manifests - - runtime:container - - package:node + - repo:detected diff --git a/src/configs/audit-rules.yaml b/src/configs/audit-rules.yaml index 42edef9..f312a0b 100644 --- a/src/configs/audit-rules.yaml +++ b/src/configs/audit-rules.yaml @@ -7,23 +7,23 @@ config: - id: missing-documentation factor: documentation status: missing - message: Add a README so humans and agents can orient quickly around purpose, workflow, and boundaries. + message: Add a README so repo consumers can orient quickly around purpose, workflow, and boundaries. - id: partial-dependency-contract factor: dependencies status: partial - message: Runtime signals exist, but the dependency contract is incomplete. Prefer an explicit language or package manifest over container-only dependency discovery. - - id: missing-dependency-manifest + message: Custom execution-shaping contracts exist, but they are not traced clearly yet. Prefer versioned manifests, reusable workflow refs, image refs, or in-repo docs/source links over implicit tool knowledge. + - id: missing-dependency-contract factor: dependencies status: missing - message: Add an explicit dependency manifest so builds and runtime assumptions do not depend on tribal knowledge. + message: No meaningful external contract was detected. When repo behavior depends on external workflows, images, or custom manifests, add metadata or docs that trace those relationships clearly. - id: partial-config-contract factor: config status: partial - message: Config signals exist, but no explicit env contract file was detected. Add .env.example, .env.sample, or .env.template when runtime configuration is required. + message: Config signals exist, but the repo does not declare a clear configuration contract yet. Document required config in repo-owned docs, manifest metadata, or checked-in example files when they fit the repo design. - id: missing-config-contract factor: config status: missing - message: No explicit configuration contract was detected. Add runtime config docs or an env example when the repo requires environment configuration. + message: No explicit configuration contract was detected. Add repo-owned docs, manifest metadata, or checked-in example files that explain the required configuration. - id: partial-pipeline factor: pipeline status: partial diff --git a/src/configs/context-config.yaml b/src/configs/context-config.yaml index fadc072..d781ace 100644 --- a/src/configs/context-config.yaml +++ b/src/configs/context-config.yaml @@ -1,6 +1,6 @@ kind: contextConfig version: udx.dev/dev.kit/v1 -description: Repo root markers, direct-read refs, and documentation priority order +description: Minimal repo root markers, direct-read refs, and documentation priority order config: context_sections: @@ -8,7 +8,7 @@ config: title: Refs summary: Direct-read files and paths that define the repo contract. notes: - - Include only files or directories an agent should read before code exploration. + - Include only files or directories a repo consumer should read before code exploration. - Prefer README, focused docs, workflows, manifests, and explicit operational files. - Exclude broad implementation directories unless they are the contract themselves. source_lists: @@ -42,11 +42,11 @@ config: - pipeline dependencies: title: Dependencies - summary: External repos, actions, images, or versioned manifests this repo relies on. + summary: Meaningful dependency-repo contracts such as reusable workflows, images, or versioned manifests this repo relies on. notes: - - Capture behavior defined outside the current checkout. + - Capture execution-shaping behavior defined outside the current checkout. + - Avoid promoting standard package inventory or ordinary GitHub action refs into top-level context. - Normalize same-org versioned refs into repo slugs when possible. - - Keep where-used tracing so the dependency can be followed back to its source. workflow_dirs: - dependency_trace_workflow_dirs container_files: @@ -55,17 +55,15 @@ config: - dependency_trace_compose_files versioned_dirs: - dependency_trace_versioned_dirs - url_globs: - - dependency_trace_url_globs manifests: title: Manifests - summary: YAML files that define detection rules, workflows, evaluation, deploy, or runtime behavior. + summary: YAML files that define repo-specific workflow, deploy, or contract behavior. notes: - - Include manifests that materially shape repo behavior or agent understanding. + - Include custom config/manifests that materially shape repo behavior or contract understanding. + - Do not include workflow YAML only because it lives under .github/workflows. + - Promote workflow files only when they declare reusable workflow refs or other repo-specific execution contracts. - Prefer structured kind and description metadata from the manifest itself. - - Include eval and workflow manifests, not only deploy manifests. - workflow_dirs: - - manifest_workflow_dirs + - Include hidden or nested contract dirs when they contain repo-owned manifests with meaningful metadata. config_dirs: - manifest_config_dirs root_files: @@ -73,9 +71,9 @@ config: repo_root_files: - README.md - readme.md + - deploy.yml + - deploy.yaml - package.json - - composer.json - - wp-config.php - Dockerfile - Makefile - makefile @@ -93,13 +91,10 @@ config: - .github/workflows - Makefile - Dockerfile - - wp-config.php - - examples - docs repo_doc_paths: - .rabbit/context.yaml - CLAUDE.md - - AGENTS.md - README* - readme* - docs/*.md @@ -117,38 +112,6 @@ config: paths: - README.md - readme.md - todo_notes: - prefix: notes - kind: file - paths: - - TODO.md - - todo.md - refs_notes: - prefix: notes - kind: file - paths: - - refs.md - - REFS.md - wordpress_framework: - prefix: framework - kind: file - paths: - - wp-config.php - node_manifest: - prefix: manifest - kind: file - paths: - - package.json - composer_manifest: - prefix: manifest - kind: file - paths: - - composer.json - docker_runtime: - prefix: runtime - kind: file - paths: - - Dockerfile deploy_files: prefix: deploy kind: file @@ -166,8 +129,8 @@ config: kind: dir paths: - .github/workflows - udx_root: - prefix: udx + rabbit_root: + prefix: context kind: dir paths: - .rabbit diff --git a/src/configs/detection-patterns.yaml b/src/configs/detection-patterns.yaml index 843f307..a0fdac6 100644 --- a/src/configs/detection-patterns.yaml +++ b/src/configs/detection-patterns.yaml @@ -5,6 +5,7 @@ description: Regex patterns for command, workflow, and env detection config: command_patterns: verification: (npm test|pnpm test|yarn test|bun test|make test|bash tests/[^`[:space:]]+|\.\/tests/[^`[:space:]]+|vendor/bin/phpunit|phpunit|pytest|go test|cargo test) + lint: (npm run lint|pnpm lint|yarn lint|bun run lint|make lint|golangci-lint run|cargo clippy[^`]*|eslint [^`]*) build: (npm run build|pnpm build|yarn build|bun run build|make build|docker build[^`]*|go build|cargo build|composer build) run: (`[A-Za-z0-9._-]+ run[^`]*`|npm start|pnpm start|yarn start|bun run start|make run|docker compose up[^`]*|docker run[^`]*|php -S [^`]*|python -m [^`[:space:]]+|node ([./][^`[:space:]]+|[^`[:space:]]+\.js)) env_var: ([A-Z][A-Z0-9_]{2,}=|\$\{[A-Z][A-Z0-9_]{2,}\}) diff --git a/src/configs/detection-signals.yaml b/src/configs/detection-signals.yaml index 1e59e7a..617b77f 100644 --- a/src/configs/detection-signals.yaml +++ b/src/configs/detection-signals.yaml @@ -16,11 +16,13 @@ config: - docs/**/*.md prune_dirs: - .git - - wp-admin - - wp-content - - wp-includes - node_modules - .next + - .nuxt + - .svelte-kit + - .output + - .contentlayer + - .turbo - vendor - dist - .venv @@ -28,8 +30,6 @@ config: - .terraform - build - coverage - - .nuxt - - .turbo - .cache - target - out @@ -39,33 +39,10 @@ config: - Makefile - makefile - GNUmakefile - next_files: - - next.config.js - - next.config.mjs - - next.config.ts - wordpress_files: - - wp-config.php - wordpress_dirs: - - wp-content - - wp-content/plugins - - wp-content/themes - - wp-content/mu-plugins container_files: - Dockerfile - docker-compose.yml - docker-compose.yaml - container_globs: - - Dockerfile.* - dependency_manifest_files: - - package.json - - composer.json - - requirements.txt - - pyproject.toml - - go.mod - - Cargo.toml - - Gemfile - dependency_partial_files: - - Dockerfile config_contract_files: - .env.example - .env.sample @@ -75,9 +52,6 @@ config: - docker-compose.yaml - deploy.yml - deploy.yaml - runtime_files: - - Dockerfile - - Procfile test_dirs: - tests - test @@ -96,19 +70,6 @@ config: - deploy.yaml - docker-compose.yml - docker-compose.yaml - infra_dirs: - - terraform - - k8s - - cd - terraform_files: - - main.tf - - variables.tf - terraform_dirs: - - terraform - terraform_globs: - - terraform/*.tf - - terraform/**/*.tf - - "*.tf" yaml_manifest_globs: - "*.yaml" - "*.yml" @@ -119,25 +80,13 @@ config: dependency_trace_container_files: - Dockerfile # Dirs scanned recursively for YAML files with kind/version headers. - # The version URI (e.g. udx.dev/rabbit-automation-action/gcp-sql-instance/v1) - # traces back to source repo and module. This is the primary cross-repo tracing - # mechanism — repos declare their infrastructure provider in the version field. + # Version URIs are normalized into source repo contracts when possible. dependency_trace_versioned_dirs: - .rabbit # Docker Compose files scanned for image: references. dependency_trace_compose_files: - docker-compose.yml - docker-compose.yaml - # File globs scanned for GitHub URL references (github.com/org/repo patterns). - # Applied within priority ref dirs and repo root config files. - dependency_trace_url_globs: - - "*.yaml" - - "*.yml" - - "*.md" - # Manifest files that define where this repo is hosted/deployed. - # Listed as config manifests in context.yaml output. - manifest_workflow_dirs: - - .github/workflows manifest_config_dirs: - src/configs - configs diff --git a/src/templates/agent.json b/src/templates/agent.json deleted file mode 100644 index d649587..0000000 --- a/src/templates/agent.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "command": "agent", - "repo": "{{repo}}", - "path": "{{path}}", - "archetype": "{{archetype}}", - "agents_md": "{{agents_md}}", - "context": "{{context}}", - "priority_refs": {{priority_refs}}, - "entrypoints": {{entrypoints}}, - "workflow_contract": {{workflow_contract}}, - "dependencies": {{dependencies}} -} diff --git a/src/templates/repo.json b/src/templates/repo.json index 39c2b79..dd1d117 100644 --- a/src/templates/repo.json +++ b/src/templates/repo.json @@ -10,5 +10,6 @@ "gaps": {{gaps}}, "actions": {{actions}}, "context": "{{context}}", - "dependencies": {{dependencies}} + "dependencies": {{dependencies}}, + "recommended_repos": {{recommended_repos}} } diff --git a/tests/fixtures/docker-repo/.rabbit/deploy.yml b/tests/fixtures/docker-repo/.rabbit/deploy.yml new file mode 100644 index 0000000..ff78e4b --- /dev/null +++ b/tests/fixtures/docker-repo/.rabbit/deploy.yml @@ -0,0 +1,5 @@ +--- +kind: workerDeployConfig +version: udx.io/worker-v1/deploy +config: + image: "usabilitydynamics/udx-worker-engine:latest" diff --git a/tests/fixtures/documented-shell-repo/.github/dependabot.yml b/tests/fixtures/documented-shell-repo/.github/dependabot.yml new file mode 100644 index 0000000..c75e875 --- /dev/null +++ b/tests/fixtures/documented-shell-repo/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly diff --git a/tests/fixtures/simple-repo/package.json b/tests/fixtures/simple-repo/package.json index eff7ff8..a95582b 100644 --- a/tests/fixtures/simple-repo/package.json +++ b/tests/fixtures/simple-repo/package.json @@ -1,6 +1,9 @@ { "name": "simple-repo", "private": true, + "dependencies": { + "chalk": "^5.0.0" + }, "scripts": { "start": "node index.js" } diff --git a/tests/real-repos.sh b/tests/real-repos.sh index 6036ada..bf0ee64 100644 --- a/tests/real-repos.sh +++ b/tests/real-repos.sh @@ -77,11 +77,10 @@ for repo_path in "${repo_paths[@]}"; do cat "$run_out" >&2 exit 1 fi + dev.kit repo >/dev/null [ -f .rabbit/context.yaml ] || { printf 'missing .rabbit/context.yaml\n' >&2; exit 1; } - [ -f AGENTS.md ] || { printf 'missing AGENTS.md\n' >&2; exit 1; } printf 'context: %s\n' "$repo_path/.rabbit/context.yaml" - printf 'agents: %s\n' "$repo_path/AGENTS.md" printf '\nrepo:\n' sed -n '1,20p' .rabbit/context.yaml printf '\ngaps:\n' diff --git a/tests/suite.sh b/tests/suite.sh index f277ba6..7e1c4fc 100644 --- a/tests/suite.sh +++ b/tests/suite.sh @@ -13,6 +13,9 @@ DOCKER_REPO="$REPO_DIR/tests/fixtures/docker-repo" SIMPLE_ACTION_REPO="$TEST_HOME/simple-action-repo" HOME_ACTION_REPO="$TEST_HOME/home-action-repo" DOCKER_ACTION_REPO="$TEST_HOME/docker-action-repo" +EMPTY_REPO="$TEST_HOME/empty-repo" +IGNORED_ACTION_REPO="$TEST_HOME/ignored-action-repo" +WORKFLOW_CONTRACT_REPO="$TEST_HOME/workflow-contract-repo" AVAILABLE_TEST_GROUPS="core" TEST_ONLY="${DEV_KIT_TEST_ONLY:-}" @@ -26,7 +29,7 @@ usage() { Usage: bash tests/suite.sh [--only core] [--list] Groups: - core minimal happy-path smoke checks + core command flow, output, and context generation checks EOF } @@ -48,6 +51,28 @@ should_run() { return 1 } +replace_in_file() { + local file_path="$1" + local before="$2" + local after="$3" + local tmp_file="" + + tmp_file="$(mktemp "${TMPDIR:-/tmp}/dev-kit-replace.XXXXXX")" || return 1 + awk -v before="$before" -v after="$after" ' + BEGIN { replaced = 0 } + { + line = $0 + pos = index(line, before) + if (pos > 0 && replaced == 0) { + line = substr(line, 1, pos - 1) after substr(line, pos + length(before)) + replaced = 1 + } + print line + } + END { exit(replaced == 0) } + ' "$file_path" >"$tmp_file" && mv "$tmp_file" "$file_path" +} + while [ "$#" -gt 0 ]; do case "$1" in --only) @@ -85,50 +110,277 @@ done <&1 + )" + assert_contains "$guard_soft_output" "repo resolving is taking longer than usual" "guard: emits soft timeout notice" + assert_contains "$guard_soft_output" "done" "guard: preserves command output" + + set +e + guard_hard_output="$( + DEV_KIT_SPINNER_DISABLE=1 \ + dev_kit_run_guarded "guard test" 1 2 "repo resolving is taking longer than usual" \ + bash -lc 'sleep 3' 2>&1 + )" + guard_hard_status=$? + set -e + [ "$guard_hard_status" -eq 124 ] || fail "guard: stops hard timeout" + pass "guard: stops hard timeout" + assert_contains "$guard_hard_output" "dev.kit timeout: guard test exceeded 2s" "guard: reports hard timeout" + + guard_child_pid_file="$TEST_HOME/guard-child.pid" + set +e + guard_group_output="$( + DEV_KIT_SPINNER_DISABLE=1 \ + dev_kit_run_guarded "guard child test" 1 2 "repo resolving is taking longer than usual" \ + bash -lc 'sleep 30 & child=$!; printf "%s\n" "$child" > "$1"; wait "$child"' _ "$guard_child_pid_file" 2>&1 + )" + guard_group_status=$? + set -e + [ "$guard_group_status" -eq 124 ] || fail "guard: stops child process groups" + guard_child_pid="$(cat "$guard_child_pid_file")" + if kill -0 "$guard_child_pid" 2>/dev/null; then + kill "$guard_child_pid" 2>/dev/null || true + fail "guard: terminates child processes on timeout" + fi + pass "guard: terminates child processes on timeout" + assert_contains "$guard_group_output" "dev.kit timeout: guard child test exceeded 2s" "guard: reports child timeout" + cp -R "$DOCUMENTED_SHELL_REPO" "$HOME_ACTION_REPO" - rm -rf "$HOME_ACTION_REPO/.dev-kit" "$HOME_ACTION_REPO/.rabbit" "$HOME_ACTION_REPO/AGENTS.md" + rm -rf "$HOME_ACTION_REPO/.dev-kit" + rm -f "$HOME_ACTION_REPO/.rabbit/context.yaml" home_json="$(cd "$HOME_ACTION_REPO" && dev.kit --json)" assert_contains "$home_json" "\"repo_detected\": true" "home: detects repo" assert_contains "$home_json" "\"synced\": {" "home: reports synced artifacts" + assert_contains "$home_json" "\"context_status\": \"missing\"" "home: reports missing context" assert_contains "$home_json" "\"helpers\": [" "home: reports helpers" home_text="$(cd "$HOME_ACTION_REPO" && dev.kit)" assert_contains "$home_text" "[required]" "home text: renders env tools" - assert_contains "$home_text" "[synced]" "home text: syncs repo artifacts" - assert_file_exists "$HOME_ACTION_REPO/.rabbit/context.yaml" "home: writes context.yaml" - assert_file_exists "$HOME_ACTION_REPO/AGENTS.md" "home: writes AGENTS.md" + assert_contains "$home_text" "[context]" "home text: renders context section" + assert_contains "$home_text" "Repo context is missing." "home text: guides missing context" + assert_contains "$home_text" "dev.kit repo" "home text: suggests repo generation" + assert_not_contains "$home_text" "env: dev.kit env" "home text: does not suggest env as next step" + assert_contains "$home_text" "[next]" "home text: renders next section" + assert_file_missing "$HOME_ACTION_REPO/.rabbit/context.yaml" "home: does not write context.yaml" assert_not_contains "$(dev_kit_github_repo_refs_in_file "$REPO_DIR/src/configs/detection-signals.yaml")" "org/repo" "home: ignores placeholder github refs" + repo_home_json="$(cd "$HOME_ACTION_REPO" && dev.kit repo --json)" + assert_contains "$repo_home_json" "\"context\":" "home fixture repo: writes context path" + assert_contains "$repo_home_json" "\"id\": \"confirm-research-fix-loop\"" "repo json: includes confirmation loop action" + + repo_global_json="$(cd "$HOME_ACTION_REPO" && dev.kit --json repo)" + assert_contains "$repo_global_json" "\"command\": \"repo\"" "global json: routes to repo command" + + uninstall_bin_dir="$TEST_HOME/uninstall-bin" + uninstall_home_dir="$TEST_HOME/uninstall-home" + mkdir -p "$uninstall_bin_dir" "$uninstall_home_dir" + touch "$uninstall_bin_dir/dev.kit" + uninstall_json="$( + cd "$HOME_ACTION_REPO" && \ + DEV_KIT_BIN_DIR="$uninstall_bin_dir" \ + DEV_KIT_HOME="$uninstall_home_dir" \ + dev.kit --json uninstall --yes + )" + assert_contains "$uninstall_json" "\"command\": \"uninstall\"" "uninstall json: reports command" + assert_contains "$uninstall_json" "\"ok\": true" "uninstall json: reports success" + assert_contains "$uninstall_json" "\"binary_removed\": true" "uninstall json: reports binary removal" + assert_contains "$uninstall_json" "\"home_removed\": true" "uninstall json: reports home removal" + assert_file_missing "$uninstall_bin_dir/dev.kit" "uninstall json: removes binary target" + assert_file_missing "$uninstall_home_dir" "uninstall json: removes home target" + + set +e + uninstall_failure_json="$( + REPO_DIR="$TEST_HOME/missing-repo" \ + DEV_KIT_BIN_DIR="$uninstall_bin_dir" \ + DEV_KIT_HOME="$uninstall_home_dir" \ + dev_kit_cmd_uninstall json --yes 2>/dev/null + )" + uninstall_failure_status=$? + set -e + [ "$uninstall_failure_status" -ne 0 ] || fail "uninstall json: fails when uninstall script fails" + pass "uninstall json: fails when uninstall script fails" + assert_contains "$uninstall_failure_json" "\"ok\": false" "uninstall json: reports failure" + assert_contains "$uninstall_failure_json" "\"error\":" "uninstall json: includes error message" + + home_repeat_json="$(cd "$HOME_ACTION_REPO" && DEV_KIT_REPO_HARD_TIMEOUT=1 dev.kit --json)" + assert_contains "$home_repeat_json" "\"context_status\": \"existing\"" "home: reuses existing context" + assert_contains "$home_repeat_json" "\"context_reason\": null" "home: fresh context has no stale reason" + + home_repeat_text="$(cd "$HOME_ACTION_REPO" && dev.kit)" + assert_contains "$home_repeat_text" "[coverage]" "home text: summarizes coverage" + assert_contains "$home_repeat_text" "[refs]" "home text: summarizes refs" + assert_contains "$home_repeat_text" "[gaps]" "home text: summarizes gaps" + assert_contains "$home_repeat_text" "[next]" "home text: summarizes next step" + assert_contains "$home_repeat_text" "repair: README.md or .env.example" "home text: shows gap repair target" + assert_contains "$home_repeat_text" "reference: docs/references/config-contract-surfaces.md" "home text: shows gap reference" + assert_contains "$home_repeat_text" "repair: fix repo-owned gaps, then rerun dev.kit repo" "home text: prints repair loop next step" + + replace_in_file \ + "$HOME_ACTION_REPO/.rabbit/context.yaml" \ + "No repo-owned configuration contract was found in docs, manifests, or checked-in example files." \ + "Add .env.example, .env.sample, or .env.template when repo configuration is required." + + stale_home_json="$(cd "$HOME_ACTION_REPO" && dev.kit --json)" + assert_contains "$stale_home_json" "\"context_status\": \"stale\"" "home: marks outdated context as stale" + assert_contains "$stale_home_json" "\"context_reason\": \"gap coverage no longer matches current repo evidence\"" "home: reports stale reason" + + stale_home_text="$(cd "$HOME_ACTION_REPO" && dev.kit)" + assert_contains "$stale_home_text" "Repo context is stale." "home text: warns about stale context" + assert_contains "$stale_home_text" "gap coverage no longer matches current repo evidence" "home text: explains stale reason" + assert_not_contains "$stale_home_text" "[refs]" "home text: hides stale refs summary" + env_json="$(cd "$HOME_ACTION_REPO" && dev.kit env --json)" assert_contains "$env_json" "\"command\": \"env\"" "env: reports command name" repo_json="$(cd "$DOCUMENTED_SHELL_REPO" && dev.kit repo --json)" assert_contains "$repo_json" "\"archetype\":" "repo: reports archetype" assert_contains "$repo_json" "\"context\":" "repo: reports context path" + assert_contains "$repo_json" "\"repair_target\": \"README.md or .env.example\"" "repo: includes repair target" + assert_contains "$repo_json" "\"reference\": \"docs/references/config-contract-surfaces.md\"" "repo: includes reference doc" + assert_contains "$repo_json" "\"id\": \"confirm-research-fix-loop\"" "repo: includes confirmation loop action" + + repo_text="$(cd "$DOCUMENTED_SHELL_REPO" && dev.kit repo)" + assert_contains "$repo_text" "[read first]" "repo text: renders read first section" + assert_contains "$repo_text" "[factors]" "repo text: renders factors section" + assert_contains "$repo_text" "[context]" "repo text: renders context section" + assert_contains "$repo_text" "[tooling]" "repo text: renders tooling section" + assert_contains "$repo_text" "[next]" "repo text: renders next section" + assert_contains "$repo_text" "confirm whether to start the research-and-fix loop now" "repo text: confirms before repair loop" + + set +e + repo_write_failure_output="$( + DEV_KIT_SPINNER_DISABLE=1 + dev_kit_context_yaml_write() { + printf 'boom\n' >&2 + return 42 + } + dev_kit_cmd_repo text "$DOCUMENTED_SHELL_REPO" 2>&1 + )" + repo_write_failure_status=$? + set -e + [ "$repo_write_failure_status" -eq 42 ] || fail "repo text: preserves non-timeout write failures" + pass "repo text: preserves non-timeout write failures" + assert_contains "$repo_write_failure_output" "Context write failed with exit status 42" "repo text: distinguishes non-timeout write failures" + assert_contains "$repo_write_failure_output" "boom" "repo text: preserves underlying write error" + + self_repo_json="$(cd "$REPO_DIR" && dev.kit repo --json)" + assert_not_contains "$self_repo_json" "\"repo\": \"udx/dev.kit\"" "repo: omits self dependency contracts" + assert_not_contains "$(cat "$REPO_DIR/.rabbit/context.yaml")" "source_repo: udx/dev.kit" "repo: omits self source repo provenance" + assert_not_contains "$(cat "$REPO_DIR/.rabbit/context.yaml")" ".rabbit/dev.kit/" "repo: excludes generated rabbit evidence" cp -R "$SIMPLE_REPO" "$SIMPLE_ACTION_REPO" - rm -rf "$SIMPLE_ACTION_REPO/.dev-kit" "$SIMPLE_ACTION_REPO/.rabbit" + rm -rf "$SIMPLE_ACTION_REPO/.dev-kit" + rm -f "$SIMPLE_ACTION_REPO/.rabbit/context.yaml" - agent_json="$(cd "$SIMPLE_ACTION_REPO" && dev.kit agent --json)" - assert_contains "$agent_json" "\"workflow_contract\":" "agent: reports workflow contract" + simple_repo_json="$(cd "$SIMPLE_ACTION_REPO" && dev.kit repo --json)" + assert_contains "$simple_repo_json" "\"context\":" "simple repo: reports context path" context_yaml="${SIMPLE_ACTION_REPO}/.rabbit/context.yaml" - assert_file_exists "$context_yaml" "agent: creates .rabbit/context.yaml" - assert_contains "$(cat "$context_yaml")" "kind: repoContext" "agent: context.yaml has kind header" - assert_not_contains "$(cat "$context_yaml")" "/Users/" "agent: context.yaml has no absolute paths" - assert_contains "$(cat "${SIMPLE_ACTION_REPO}/AGENTS.md")" "Use these repo-derived steps as the default operating path." "agent: AGENTS.md uses repo-derived workflow guidance" + assert_file_exists "$context_yaml" "repo: creates .rabbit/context.yaml" + assert_contains "$(cat "$context_yaml")" "kind: repoContext" "repo: context.yaml has kind header" + assert_contains "$(cat "$context_yaml")" "generator:" "repo: context.yaml has generator metadata" + assert_contains "$(cat "$context_yaml")" "tool: dev.kit" "repo: context.yaml records generator tool" + assert_contains "$(cat "$context_yaml")" "generated_at:" "repo: context.yaml records generated timestamp" + assert_not_contains "$(cat "$context_yaml")" "/Users/" "repo: context.yaml has no absolute paths" + assert_not_contains "$(cat "$context_yaml")" "kind: npm package" "repo: context.yaml omits package inventory" cp -R "$DOCKER_REPO" "$DOCKER_ACTION_REPO" - rm -rf "$DOCKER_ACTION_REPO/.rabbit" "$DOCKER_ACTION_REPO/AGENTS.md" + rm -f "$DOCKER_ACTION_REPO/.rabbit/context.yaml" docker_repo_json="$(cd "$DOCKER_ACTION_REPO" && dev.kit repo --json)" assert_contains "$docker_repo_json" "\"context\":" "docker repo: reports context path" docker_context_yaml="${DOCKER_ACTION_REPO}/.rabbit/context.yaml" + assert_contains "$(cat "$docker_context_yaml")" "generator:" "docker repo: includes generator metadata" assert_contains "$(cat "$docker_context_yaml")" "path: deploy.yml" "docker repo: includes deploy manifest" + assert_contains "$(cat "$docker_context_yaml")" "path: .rabbit/deploy.yml" "docker repo: includes hidden custom manifest" assert_contains "$(cat "$docker_context_yaml")" "source_repo: udx/worker" "docker repo: traces manifest owner from version" + + mkdir -p "$IGNORED_ACTION_REPO/.next/cache" + git -C "$IGNORED_ACTION_REPO" init >/dev/null 2>&1 + cat > "$IGNORED_ACTION_REPO/README.md" <<'EOF' +# Ignored Action Repo +EOF + cat > "$IGNORED_ACTION_REPO/.gitignore" <<'EOF' +.next/ +EOF + cat > "$IGNORED_ACTION_REPO/deploy.yml" <<'EOF' +version: udx.io/worker-v1/deploy +kind: workerDeployConfig +EOF + cat > "$IGNORED_ACTION_REPO/.next/cache/reference.txt" <<'EOF' +deploy.yml +EOF + + ignored_repo_json="$(cd "$IGNORED_ACTION_REPO" && dev.kit repo --json)" + assert_contains "$ignored_repo_json" "\"context\":" "ignored repo: reports context path" + ignored_context_yaml="${IGNORED_ACTION_REPO}/.rabbit/context.yaml" + assert_not_contains "$(cat "$ignored_context_yaml")" ".next/cache/reference.txt" "ignored repo: excludes gitignored artifact references" + + mkdir -p "$WORKFLOW_CONTRACT_REPO/.github/workflows" + git -C "$WORKFLOW_CONTRACT_REPO" init >/dev/null 2>&1 + cat > "$WORKFLOW_CONTRACT_REPO/README.md" <<'EOF' +# Workflow Contract Repo +EOF + cat > "$WORKFLOW_CONTRACT_REPO/.github/workflows/ci.yml" <<'EOF' +name: CI +on: + push: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 +EOF + cat > "$WORKFLOW_CONTRACT_REPO/.github/workflows/deploy.yml" <<'EOF' +name: Deploy +on: + workflow_dispatch: +jobs: + deploy: + uses: udx/reusable-workflows/.github/workflows/deploy.yml@main +EOF + + workflow_repo_json="$(cd "$WORKFLOW_CONTRACT_REPO" && dev.kit repo --json)" + assert_contains "$workflow_repo_json" "\"context\":" "workflow repo: reports context path" + workflow_context_yaml="${WORKFLOW_CONTRACT_REPO}/.rabbit/context.yaml" + assert_contains "$(cat "$workflow_context_yaml")" "path: .github/workflows/deploy.yml" "workflow repo: includes reusable workflow manifest" + assert_not_contains "$(cat "$workflow_context_yaml")" "path: .github/workflows/ci.yml" "workflow repo: excludes marketplace-only workflow manifest" + assert_contains "$(cat "$workflow_context_yaml")" "repo: udx/reusable-workflows" "workflow repo: traces reusable workflow dependency" + + mkdir -p "$EMPTY_REPO" + git -C "$EMPTY_REPO" init >/dev/null 2>&1 + + empty_repo_json="$(cd "$EMPTY_REPO" && dev.kit repo --json)" + assert_contains "$empty_repo_json" "\"recommended_repos\":" "empty repo: reports recommended repos" + assert_contains "$empty_repo_json" "https://github.com/udx/worker" "empty repo: includes worker recommendation" + assert_contains "$empty_repo_json" "https://github.com/udx/reusable-workflows" "empty repo: includes reusable workflow recommendation" + assert_contains "$empty_repo_json" "https://github.com/udx/github-rabbit-action" "empty repo: includes rabbit action recommendation" + + empty_repo_text="$(cd "$EMPTY_REPO" && dev.kit repo)" + assert_contains "$empty_repo_text" "[tooling]" "empty repo: prints tooling section" + assert_contains "$empty_repo_text" "https://github.com/udx/worker" "empty repo: prints worker recommendation" + + assert_file_exists "$EMPTY_REPO/README.md" "empty repo: creates README" + assert_file_exists "$EMPTY_REPO/docs" "empty repo: creates docs dir" + assert_file_exists "$EMPTY_REPO/.rabbit" "empty repo: creates rabbit dir" + assert_file_exists "$EMPTY_REPO/.github/workflows" "empty repo: creates workflows dir" + assert_file_exists "$EMPTY_REPO/.github/dependabot.yml" "empty repo: creates dependabot config" + assert_contains "$(cat "$EMPTY_REPO/.github/dependabot.yml")" "package-ecosystem: github-actions" "empty repo: dependabot targets github actions" + assert_contains "$(cat "$EMPTY_REPO/README.md")" "This repository uses \`dev.kit\`" "empty repo: seeds README" fi printf "ok - dev.kit suite completed\n" diff --git a/tests/worker-smoke.sh b/tests/worker-smoke.sh index 1214084..25758d5 100755 --- a/tests/worker-smoke.sh +++ b/tests/worker-smoke.sh @@ -143,6 +143,4 @@ docker run --rm \ dev.kit env --json printf '\n== dev.kit repo --json ==\n' dev.kit repo --json - printf '\n== dev.kit agent --json ==\n' - dev.kit agent --json "