diff --git a/.rabbit/context.yaml b/.rabbit/context.yaml index 7c53354..b711f8a 100644 --- a/.rabbit/context.yaml +++ b/.rabbit/context.yaml @@ -5,8 +5,13 @@ version: udx.dev/dev.kit/v1 generator: tool: dev.kit repo: https://github.com/udx/dev.kit - version: 0.11.0 - generated_at: 2026-05-19T18:52:32Z + version: 0.12.0 + generated_at: 2026-05-27T09:28:30Z + sources: + homepage: https://udx.dev/kit + repository: https://github.com/udx/dev.kit + package: https://www.npmjs.com/package/@udx/dev-kit + installation: https://github.com/udx/dev.kit/blob/latest/docs/installation.md repo: name: dev.kit @@ -23,6 +28,7 @@ refs: - ./Makefile - ./docs/references/command-surfaces.md - ./docs/real-repo-validation.md + - ./docs/references/output-schemas.md - ./docs/references/repo-design.md - ./src/configs/archetypes.yaml - ./src/configs/audit-rules.yaml @@ -166,6 +172,7 @@ manifests: source_repo: udx/worker used_by: - Makefile + - docs/context-contract.md - docs/references/command-surfaces.md - docs/references/config-contract-surfaces.md - docs/repo-contract-boundary.md @@ -176,6 +183,7 @@ manifests: evidence: - version: udx.io/worker-v1/deploy - path reference: Makefile + - path reference: docs/context-contract.md - path reference: docs/references/command-surfaces.md - path reference: docs/references/config-contract-surfaces.md - path reference: docs/repo-contract-boundary.md diff --git a/README.md b/README.md index b1f2a60..f9736b6 100644 --- a/README.md +++ b/README.md @@ -178,12 +178,14 @@ All commands support `--json`. - [How It Works](docs/how-it-works.md) - [Repo Contract Boundary](docs/repo-contract-boundary.md) +- [Context Contract](docs/context-contract.md) - [Environment Config](docs/environment-config.md) - [Context Coverage](docs/context-coverage.md) - [Integration](docs/integration.md) - [Real Repo Validation](docs/real-repo-validation.md) - [Smart Dependency Detection](docs/smart-dependency-detection.md) - [Reference Docs](docs/references/README.md) +- [Reference: Output Schemas](docs/references/output-schemas.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) diff --git a/changes.md b/changes.md index ac30e80..f870310 100644 --- a/changes.md +++ b/changes.md @@ -1,5 +1,12 @@ # Changes +### 0.12.0 + +- Add generator source refs to `.rabbit/context.yaml` so generated context points to the dev.kit homepage, source repo, npm package, and installation guide. +- Validate generated context portability before writing it, rejecting machine-local absolute paths and excluding temporary context files from repo evidence. +- Add repo-owned context contract and output schema docs to clarify what dev.kit reads, emits, and must not own. +- Harden guarded command timeouts so child processes are cleaned up by process group without disabling timeout protection. + ### 0.11.0 - Expose repo-centric workflow status in `dev.kit`, `dev.kit env`, and `dev.kit repo` output so environment checks, context refresh, and gap repair appear as one repo-owned loop diff --git a/docs/context-contract.md b/docs/context-contract.md new file mode 100644 index 0000000..75950c8 --- /dev/null +++ b/docs/context-contract.md @@ -0,0 +1,69 @@ +# Context Contract + +`dev.kit` is a deterministic repo tool. It reads repo-owned evidence, emits a compact generated contract, and exposes weak coverage so humans, agents, scripts, and CI/CD can work from the same repository state. + +The generated contract is `.rabbit/context.yaml`. + +## What dev.kit Reads + +`dev.kit` should prefer repo-owned surfaces that a maintainer can review and repair: + +- `.rabbit/context.yaml` when checking existing generated context state +- `AGENTS.md`, `CLAUDE.md`, and similar repo-owned instruction files +- `README.md`, `changes.md`, and focused docs +- `.github/workflows/` +- manifests such as `deploy.yml`, package manifests, Docker files, and structured YAML configs +- scripts, Makefiles, tests, and checked-in examples that define runnable behavior + +Live services such as GitHub issues, PRs, reviews, and workflow runs can help a current task, but they should not become durable repo truth inside `.rabbit/context.yaml`. + +## What dev.kit Emits + +`.rabbit/context.yaml` should contain generated repo evidence and deterministic interpretation: + +- generator metadata, version, source refs, and generation timestamp +- repo identity and archetype +- direct-read refs for humans and agents +- detected verify, build, and run commands with sources +- coverage gaps with evidence and repair targets +- dependency contracts such as reusable workflows, images, and versioned manifests +- manifest inventory with provenance and usage evidence + +The artifact should be portable. It must not contain machine-local absolute paths, temp files, cache paths, or generated evidence that only exists on one workstation. + +## What dev.kit Must Not Own + +`dev.kit` must not become the source of truth for: + +- global UDX strategy +- app-specific runtime truth +- secrets, credentials, or local machine state +- issue, PR, or Slack conversation history +- long-form agent prompts or subjective workflow narration +- hand-authored corrections to generated context + +If a gap needs durable meaning, repair the owning repo asset and rerun `dev.kit repo`. + +## Workflow Boundary + +The command surface stays small: + +- `dev.kit` inspects environment and repo context status. +- `dev.kit env` reports tool, credential, and capability coverage. +- `dev.kit repo` generates or refreshes `.rabbit/context.yaml`. + +Inspection should be read-only unless the command is explicitly a writer. Repair should happen in repo-owned assets, not in generated output. + +## Integrity Rules + +The context contract is useful only when it stays traceable: + +- Prefer structured evidence over inferred prose. +- Keep observed facts and inferred relationships visibly separate. +- Emit gaps instead of guessing. +- Record source paths for commands, manifests, and repair references. +- Keep direct-read refs concise. +- Keep generated output portable across machines. +- Treat stale or legacy generator metadata as a reason to rerun `dev.kit repo`. + +For the stable machine-readable shapes, see [Reference: Output Schemas](references/output-schemas.md). diff --git a/docs/references/README.md b/docs/references/README.md index 698f24e..be20a6e 100644 --- a/docs/references/README.md +++ b/docs/references/README.md @@ -7,6 +7,7 @@ Use this directory for compact reference material that helps interpret repo cont - command and workflow contract surfaces - config contract surfaces - dependency contract boundaries +- output schema shapes for tool consumers - agent and developer workflow practices - repo design guidance diff --git a/docs/references/output-schemas.md b/docs/references/output-schemas.md new file mode 100644 index 0000000..ae62775 --- /dev/null +++ b/docs/references/output-schemas.md @@ -0,0 +1,121 @@ +# Output Schemas + +This is the stable schema reference for `dev.kit` outputs. It documents the intended shape for humans, agents, scripts, and CI/CD without changing the command surface. + +The current contract is intentionally small and may grow by adding fields. Consumers should ignore unknown fields. + +## Home Output + +Command: + +```bash +dev.kit --json +``` + +Top-level fields: + +- `name`: tool name +- `home`: local `dev.kit` home path +- `state`: install state +- `workflow`: environment and repo workflow jobs +- `workspace`: current directory and detected repo state +- `synced`: generated context path, status, reason, and counts +- `localhost_tools`: detected local tools +- `global_context`: capability summary derived from environment detection +- `start_here`: ordered local workflow hints +- `helpers`: supported command entrypoints + +Stable status values should stay compact: `ready`, `blocked`, `workspace_only`, `needs_repo_context`, `stale_context`, and `needs_repair`. + +## Environment Output + +Command: + +```bash +dev.kit env --json +``` + +Top-level fields: + +- `command`: `env` +- `home`: local `dev.kit` home path +- `workflow`: environment workflow job +- `tools`: detected tools grouped by category +- `capabilities`: derived capability booleans +- `config`: environment override config path and disabled tool or credential lists + +Environment output describes what can be observed safely from the current machine. It should not imply unavailable credentials or tools exist. + +## Repo Output + +Command: + +```bash +dev.kit repo --json +``` + +Top-level fields: + +- `command`: `repo` +- `repo`: repo name +- `path`: repo root path for the current machine +- `mode`: `write` or `check` +- `archetype`: detected repo archetype +- `markers`: root and capability markers +- `factors`: coverage summary by factor +- `gaps`: missing or partial coverage entries +- `actions`: structured next actions +- `workflow`: repo workflow job +- `context`: generated context path +- `dependencies`: dependency contract summary parsed from generated context +- `recommended_repos`: supporting tool repos + +Repo JSON may include local paths because it reports the current machine state. `.rabbit/context.yaml` must remain portable and relative. + +## Context Artifact + +File: + +```text +.rabbit/context.yaml +``` + +Stable sections: + +- `kind` +- `version` +- `generator` +- `repo` +- `refs` +- `commands` +- `gaps` +- `dependencies` +- `manifests` + +`generator` should include: + +- `tool` +- `repo` +- `version` +- `generated_at` +- `sources` + +`commands` should stay limited to repo entrypoints such as `verify`, `build`, and `run`. Installation, source, and guide references belong under generator/source metadata or docs, not in `commands`. + +## Repair Proposals + +Repair guidance should be structured as data, not prose-only advice: + +- `factor`: the weak coverage area +- `status`: `missing` or `partial` +- `message`: short explanation +- `repair_target`: repo-owned asset to improve +- `reference`: local guidance doc when available +- `evidence`: observed signals that caused the gap + +The repair loop is: + +1. inspect the gap +2. update the owning repo asset +3. rerun `dev.kit repo` +4. verify the generated context changed for the right reason diff --git a/lib/modules/output.sh b/lib/modules/output.sh index baa8f35..49788ca 100644 --- a/lib/modules/output.sh +++ b/lib/modules/output.sh @@ -167,6 +167,19 @@ EOF kill "-${signal}" "$root_pid" 2>/dev/null || true } +dev_kit_process_signal_list() { + local signal="$1" + local pid_list="$2" + local target_pid="" + + while IFS= read -r target_pid; do + [ -n "$target_pid" ] || continue + kill "-${signal}" "$target_pid" 2>/dev/null || true + done </dev/null || true + "$@" & + printf '%s\n' "$!" > "$group_leader_file" + wait "$!" ) >"$stdout_file" 2>"$stderr_file" & pid=$! started_at="$(date +%s)" @@ -208,18 +233,24 @@ dev_kit_run_guarded() { 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" + guarded_group_leader="$(cat "$group_leader_file" 2>/dev/null || true)" + timeout_pids="$(printf '%s\n%s\n' "$(dev_kit_process_descendants "$pid")" "$pid")" + if [ -n "$guarded_group_leader" ]; then + kill -TERM "-${guarded_group_leader}" 2>/dev/null || true + fi + dev_kit_process_signal_list TERM "$timeout_pids" + sleep 1 + if [ -n "$guarded_group_leader" ]; then + kill -KILL "-${guarded_group_leader}" 2>/dev/null || true fi + dev_kit_process_signal_list KILL "$timeout_pids" 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" + rm -f "$stdout_file" "$stderr_file" "$group_leader_file" return 124 fi done @@ -230,7 +261,7 @@ dev_kit_run_guarded() { [ -s "$stdout_file" ] && cat "$stdout_file" [ -s "$stderr_file" ] && cat "$stderr_file" >&2 - rm -f "$stdout_file" "$stderr_file" + rm -f "$stdout_file" "$stderr_file" "$group_leader_file" return "$status" } diff --git a/lib/modules/repo_scaffold.sh b/lib/modules/repo_scaffold.sh index a04c404..0bc6653 100644 --- a/lib/modules/repo_scaffold.sh +++ b/lib/modules/repo_scaffold.sh @@ -179,7 +179,7 @@ dev_kit_repo_is_contract_evidence_path() { local path="$1" case "$path" in - ""|.git/*|.rabbit/context.yaml|.rabbit/dev.kit/*|AGENTS.md|.udx/*|.claude/*|.copilot/*|.cursor/*) + ""|.git/*|.rabbit/context.yaml|.rabbit/context.yaml.tmp.*|.rabbit/dev.kit/*|AGENTS.md|.udx/*|.claude/*|.copilot/*|.cursor/*) return 1 ;; esac @@ -486,6 +486,25 @@ dev_kit_context_yaml_path() { printf '%s/.rabbit/context.yaml\n' "$repo_root" } +dev_kit_context_yaml_local_path_matches() { + local context_path="$1" + + [ -f "$context_path" ] || return 0 + + grep -En '(/Users/|/private/|file://|/home/[^[:space:]]+|/var/folders|/tmp/)' "$context_path" 2>/dev/null || true +} + +dev_kit_context_yaml_validate_portable() { + local context_path="$1" + local matches="" + + matches="$(dev_kit_context_yaml_local_path_matches "$context_path")" + if [ -n "$matches" ]; then + printf 'Generated context contains machine-local absolute paths:\n%s\n' "$matches" >&2 + return 1 + fi +} + # Report gaps as JSON array [{factor, status, message}] # Reads from existing factor analysis — no new detection here dev_kit_scaffold_gaps_json() { @@ -692,7 +711,12 @@ dev_kit_context_yaml_write() { local repo_root="$1" local force="${2:-0}" local context_path="${repo_root}/.rabbit/context.yaml" + local tmp_context_path="" dev_kit_repo_ensure_default_structure "$repo_root" + tmp_context_path="$(mktemp "${context_path}.tmp.XXXXXX")" || { + printf 'Failed to create temporary context file for %s\n' "$context_path" >&2 + return 1 + } local _repo _arch _arch_desc _repo="$(dev_kit_repo_name "$repo_root")" @@ -708,7 +732,12 @@ dev_kit_context_yaml_write() { 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 ' generated_at: %s\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + printf ' sources:\n' + printf ' homepage: https://udx.dev/kit\n' + printf ' repository: https://github.com/udx/dev.kit\n' + printf ' package: https://www.npmjs.com/package/@udx/dev-kit\n' + printf ' installation: https://github.com/udx/dev.kit/blob/latest/docs/installation.md\n\n' printf 'repo:\n' printf ' name: %s\n' "$_repo" @@ -1065,7 +1094,18 @@ EOF printf '%b' "$_manifests_yaml" fi - } > "$context_path" + } > "$tmp_context_path" + + if ! dev_kit_context_yaml_validate_portable "$tmp_context_path"; then + rm -f "$tmp_context_path" + return 1 + fi + + if ! mv "$tmp_context_path" "$context_path"; then + rm -f "$tmp_context_path" + printf 'Failed to move generated context into place: %s\n' "$context_path" >&2 + return 1 + fi printf "%s" "$context_path" } diff --git a/package-lock.json b/package-lock.json index dc7fccc..2936bda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@udx/dev-kit", - "version": "0.10.0", + "version": "0.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@udx/dev-kit", - "version": "0.10.0", + "version": "0.12.0", "license": "MIT", "bin": { "dev-kit": "bin/dev-kit", diff --git a/package.json b/package.json index b0f05b2..41e0b56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@udx/dev-kit", - "version": "0.11.0", + "version": "0.12.0", "description": "Context-driven engineering toolkit for AI agents and developers", "license": "MIT", "repository": { diff --git a/tests/suite.sh b/tests/suite.sh index affd553..dd7aaf5 100644 --- a/tests/suite.sh +++ b/tests/suite.sh @@ -139,6 +139,7 @@ if should_run_explicit "repo-contract"; then assert_not_contains "$self_context_yaml" "source_repo: udx/dev.kit" "repo contract: omits self source repo provenance" assert_not_contains "$repo_validation_manifest" "source_repo: udx/worker" "repo contract: does not treat probe repo values as manifest source repo" assert_not_contains "$self_context_yaml" ".rabbit/dev.kit/" "repo contract: excludes generated rabbit evidence" + assert_not_contains "$self_context_yaml" ".rabbit/context.yaml.tmp." "repo contract: excludes context temp files from evidence" cp -R "$DOCKER_REPO" "$DOCKER_ACTION_REPO" rm -f "$DOCKER_ACTION_REPO/.rabbit/context.yaml" @@ -333,6 +334,7 @@ if should_run "core"; then assert_not_contains "$self_context_yaml" "source_repo: udx/dev.kit" "repo: omits self source repo provenance" assert_not_contains "$repo_validation_manifest" "source_repo: udx/worker" "repo: does not treat probe repo values as manifest source repo" assert_not_contains "$self_context_yaml" ".rabbit/dev.kit/" "repo: excludes generated rabbit evidence" + assert_not_contains "$self_context_yaml" ".rabbit/context.yaml.tmp." "repo: excludes context temp files from evidence" cp -R "$SIMPLE_REPO" "$SIMPLE_ACTION_REPO" rm -rf "$SIMPLE_ACTION_REPO/.dev-kit" @@ -346,8 +348,15 @@ if should_run "core"; then 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")" "repo: https://github.com/udx/dev.kit" "repo: context.yaml records generator repo" + assert_contains "$(cat "$context_yaml")" "sources:" "repo: context.yaml records generator source refs" + assert_contains "$(cat "$context_yaml")" "homepage: https://udx.dev/kit" "repo: context.yaml records dev.kit homepage" + assert_contains "$(cat "$context_yaml")" "package: https://www.npmjs.com/package/@udx/dev-kit" "repo: context.yaml records package source" + assert_contains "$(cat "$context_yaml")" "installation: https://github.com/udx/dev.kit/blob/latest/docs/installation.md" "repo: context.yaml records installation guide" 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")" "/private/" "repo: context.yaml has no private temp paths" + assert_not_contains "$(cat "$context_yaml")" "file://" "repo: context.yaml has no file URI paths" assert_not_contains "$(cat "$context_yaml")" "kind: npm package" "repo: context.yaml omits package inventory" cp -R "$DOCKER_REPO" "$DOCKER_ACTION_REPO"