diff --git a/.rabbit/context.yaml b/.rabbit/context.yaml index 972cd34..09c1ccc 100644 --- a/.rabbit/context.yaml +++ b/.rabbit/context.yaml @@ -1,74 +1,177 @@ # Generated by dev.kit repo — do not edit manually. # Run `dev.kit repo` to refresh. kind: repoContext -version: udx.io/dev.kit/v1 -generated: 2026-04-17 +version: udx.dev/dev.kit/v1 +generated: 2026-05-04 repo: name: dev.kit - archetype: library-cli - profile: node + archetype: package-cli + +# 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: Prefer README, focused docs, workflows, manifests, and explicit operational files. +# Note: Exclude broad implementation directories unless they are the contract themselves. refs: - ./README.md - - ./docs/installation.md - - ./docs/context.md - - ./docs/agents.md - - ./docs/integration.md - - ./docs - - ./.rabbit + - ./changes.md + - ./deploy.yml - ./.github/workflows - ./Makefile - - ./package.json - - ./deploy.yml - - ./lib - - ./src - - ./tests + - ./docs + +# Commands — Canonical repo entrypoints detected from strong repo signals. +# Note: Prefer declared make targets and package scripts before regex matches in docs. +# Note: Emit only commands that can be traced to a concrete source. +# Note: Record the source path so the command can be reviewed and corrected. commands: - verify: make test - build: make build - run: make run + verify: + run: make test + source: Makefile + +# 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. +# Note: Include message and evidence so the status can be reviewed. +# Note: Prefer traceable refs and missing signals over vague advice. -# Gaps — factors that are missing or partial gaps: - - config (partial) + - 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. + 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. +# 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. -# External dependencies — cross-repo and upstream references -# Trace these to find infrastructure, deployment, and build logic outside this repo. -# Same-org deps are resolved with metadata. External deps listed for agent reference. dependencies: - repo: udx/reusable-workflows - type: reusable workflow + kind: reusable workflow resolved: true archetype: workflow-repo - profile: unknown + description: Reusable GitHub Actions workflow templates for CI/CD 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 + declared_as: udx.io/worker-v1/deploy + archetype: runtime-image + description: UDX Worker Docker image + used_by: + - deploy.yml -# Config manifests — traceable workflow and tooling dependencies -# Read these to understand what controls repo behavior before reading shell code. -manifests: - - src/configs/archetype-rules.yaml — archetype definitions and matching rules - - src/configs/archetype-signals.yaml — file/dir signals for framework and platform detection - - src/configs/audit-rules.yaml — factor gap messages and improvement guidance - - src/configs/context-config.yaml — repo root markers and priority paths - - src/configs/detection-patterns.yaml — regex patterns for build/verify/run command detection - - src/configs/detection-signals.yaml — file/dir/glob patterns for factor analysis - - src/configs/development-practices.yaml — engineering principles inlined into repo context - - src/configs/development-workflows.yaml — git workflow steps, PR process, and operational notes - - src/configs/github-issues.yaml — issue templates, labels, and agent issue workflow - - src/configs/github-prs.yaml — PR templates, bot reviewers, and post-merge checklist - - src/configs/knowledge-base.yaml — org hierarchy and preferred knowledge sources - - src/configs/learning-workflows.yaml — agent session discovery and lesson extraction rules - - src/configs/repo-scaffold.yaml — baseline dirs/files per archetype and factor - - .github/workflows/context7-ops.yml - - .github/workflows/npm-release-ops.yml - - 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. +# Note: Prefer structured kind and description metadata from the manifest itself. +# Note: Include eval and workflow manifests, not only deploy manifests. -# Lessons from agent sessions -lessons: - - .rabbit/dev.kit/lessons-dev.kit-2026-04-15.md - - .rabbit/dev.kit/lessons-dev.kit-2026-04-14.md +manifests: + - path: src/configs/archetypes.yaml + kind: repoArchetypes + description: Repo archetype definitions and matching rules + declared_as: udx.dev/dev.kit/v1 + source_repo: udx/dev.kit + used_by: + - lib/modules/config_catalog.sh + evidence: + - version: udx.dev/dev.kit/v1 + - path reference: lib/modules/config_catalog.sh + - path: src/configs/audit-rules.yaml + 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: + - version: udx.dev/dev.kit/v1 + - 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 + declared_as: udx.dev/dev.kit/v1 + source_repo: udx/dev.kit + used_by: + - lib/modules/config_catalog.sh + evidence: + - version: udx.dev/dev.kit/v1 + - path reference: lib/modules/config_catalog.sh + - path: src/configs/detection-patterns.yaml + 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: + - version: udx.dev/dev.kit/v1 + - path reference: lib/modules/repo_signals.sh + - path: src/configs/detection-signals.yaml + 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 + 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: 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 + - src/configs/context-config.yaml + 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: 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 diff --git a/AGENTS.md b/AGENTS.md index 325e68c..d93a309 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,132 +1,38 @@ # AGENTS.md -_Auto-generated by `dev.kit agent`. Sources: `.rabbit/context.yaml`, GitHub history, lesson artifacts._ - -## Contract - -This repository is a deterministic execution contract. Agents MUST interpret declared context — no scanning, no guesswork, no invention. - -### Rules - -1. **Do NOT scan the filesystem.** No `find`, `ls -R`, `glob`, or recursive directory walks. All paths you need are listed below. -2. **Context boundaries are strict.** Read only files in Priority refs and Config manifests. If a file is not listed, do not read it unless a listed file explicitly references it. -3. **Manifests before code.** When you need to understand behavior, read the YAML manifest that defines it — not the code that implements it. Manifests are the interface. -4. **Context over memory.** Operate from repo-declared context. Do not carry assumptions from prior sessions or rely on prompt history when the contract is on disk. -5. **Prefer workflow verification, not automatic local enforcement.** Detect the repo verify command from `context.yaml`, prefer GitHub workflow executions when the repo already has CI coverage, and use local verification to reproduce failures, debug quickly, or cover workflow gaps. -6. **Follow the Workflow below.** Do not invent ad hoc steps or skip phases. The workflow is the execution sequence. -7. **Reuse over invention.** Check existing org patterns, configs, and workflows before creating new ones. -8. **Prefer live GitHub experience over generic defaults.** After loading the repo contract, use current issues, pull requests, review state, and commit history as the primary dynamic source. Fall back to workflow and practice catalogs when GitHub signal is missing, thin, or irrelevant. -9. **Remember this context.** If your agent supports persistent memory, save that this repo uses `dev.kit` for context management. Start every session — or after merges, config changes, or workflow updates — by running: `dev.kit` → `dev.kit repo` → `dev.kit agent`. Each command guides to the next required step. This resyncs repo context, environment state, and the execution contract before any work begins. +_Auto-generated by `dev.kit agent`. Source: `.rabbit/context.yaml`._ ## Repo: dev.kit -- archetype: library-cli -- profile: node +- archetype: package-cli - context: ./.rabbit/context.yaml -### Commands - -``` - verify: make test - build: make build - run: make run -``` +## Operating contract -## Use context.yaml +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. -All refs, config manifests, command surfaces, dependencies, and gaps live in `.rabbit/context.yaml`. +### Dependency context -Do not duplicate that inventory here. Read `context.yaml` first, then use this file for operating rules, workflow, and dynamic guidance. +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. -### Gaps +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. -Incomplete factors. Address within the workflow, not as separate tasks. +### Gap repair loop - - config (partial) +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. -## Versioned workflow artifacts +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. -`.rabbit/` contains generated context downstream of repo signals. These are versioned artifacts, not primary sources. - - - `.rabbit/context.yaml` — generated execution contract (source of truth for this file) - -Prior session lessons — read before starting work: - - - .rabbit/dev.kit/lessons-dev.kit-2026-04-15.md - - .rabbit/dev.kit/lessons-dev.kit-2026-04-14.md + - 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. ## Workflow -The dev.kit lifecycle: **repo → agent → work → PR → merge**. Follow these steps in order. Use them as repo-declared defaults when live GitHub context does not provide a more specific current signal. Steps with notes contain operational guidance. - - - Refresh repo context: Run `dev.kit` → `dev.kit repo` → `dev.kit agent` before starting work. Each command guides to the next required step. This resyncs repo context, environment state, and the AGENTS.md execution contract. A current context.yaml is the source of truth for refs, commands, gaps, and lessons. Do not rely on ad hoc prompt memory when the repo contract can be read from disk. When GitHub context is available, prefer current issues, pull requests, review state, and commit history over stale memory or generic workflow defaults. - - Read linked GitHub issue and confirm scope: If a GitHub issue URL is available, read the full body and comments, confirm the repo matches the issue scope, and map acceptance criteria before writing any code. Use the issue URL as the cross-repo context root. - - Reuse GitHub repo patterns: Before creating a branch, PR, issue, or status update, inspect the repo's recent GitHub history and reuse its established naming and writing patterns. Treat existing branch names, PR titles and bodies, issue templates, and close-out comments as the default style guide. - - Inspect git status - - Analyze local changes - - Analyze branch state - - Group logical commits - - Bump version and changelog if supported - - Create or validate feature branch - - Push branch to remote - - Generate pull request description: Pick the PR template type from src/configs/github-prs.yaml (feature, deployment, ops, hotfix). Fill every required section. Include "Closes #N" for linked issues. Add a "Backlog from this investigation" section for any new gaps found. Use github-prs.yaml and current GitHub PR/body patterns in this repo as the base guidance. - - Create pull request - - Monitor related workflow executions: After PR creation, monitor the related GitHub workflow runs and status checks. Open the run details when needed, watch for failed or stuck jobs, and treat GitHub workflow execution as the primary verification path when the repo already has CI coverage. - - Loop automated review feedback: After PR creation, wait for Copilot, Devin, and CodeQL reviews. Read each review from github-prs.yaml bot guidance. Address actionable findings with code changes, reply to each bot comment, and resolve the thread when handled. Repeat this loop after each push until bot feedback is clean. Do not request human review while bot findings are unaddressed. - - Verify required status checks: All required checks must pass before requesting human review. For infra PRs, open check details and review the Terraform plan output. For CodeQL, review findings in the Security tab. When the repo already has GitHub workflow coverage, prefer those executions as the primary verification path and monitor them closely. Use local verification to reproduce failures or cover gaps, not as a universal gate. - - Post close-out comment on linked issue: After PR is created, post a brief comment on the linked issue with the PR URL, what changed, and any follow-up items. GitHub auto-closes the issue on merge when "Closes #N" is in the PR body — do not close manually. - - Post-merge close-out and backlog: After merge: verify issue auto-closed, post close-out comment, open issues for any backlog items from the PR, verify monitoring changes are live. See post_merge steps in github-prs.yaml. - -### Learned from prior sessions - -Patterns detected from agent sessions on this repo. Follow these in addition to the workflow above. - -- Verify the build or runtime locally before running deploy-oriented workflow assets or reporting the change as complete. -- Use repo workflow assets like deploy.yml, workflow files, and repo docs as the execution contract instead of inventing an ad hoc deploy path. -- Keep the delivery chain explicit: create or sync the branch, prepare the PR, and connect the related GitHub issue before close-out. -- Report outcomes with exact URLs, versions, findings deltas, and next steps so the follow-up can be reused by humans and agents without drift. -- Use README, docs, and tests as the first alignment surface before broad refactors so the implementation stays anchored to an explicit workflow. -- Keep local verification targeted and lightweight during iteration, then move broader or slower validation into GitHub Actions or other CI gates. -- Treat cleanup of legacy modules, configs, and leftovers as part of the feature work so the repo keeps converging on the new operating model. -- Prefer reusable YAML/manifests plus small shell wrappers over embedding policy directly into imperative scripts. -- Package agent context from repo artifacts and manifests so the workflow stays repo-centric and does not depend on ad hoc prompt memory. -- Express repo rules in YAML/manifests first, then keep shell glue thin and composable. -- Refresh repo context and AGENTS.md from repo artifacts before deeper agent work so the workflow stays repo-centric. - -**Reusable templates:** - -- `Issue-to-scope`: start from the linked issue, confirm repo/workspace match, and restate the exact scope before changing code. -- `Workflow tracing`: locate the actual workflow file or deploy source first, then trace the commands and supporting docs that really drive execution. -- `Verify-before-sync`: run the relevant local build/test check before syncing, reporting completion, or preparing the PR. -- `Delivery chain`: sync the branch, prepare the PR in repo style, and connect the related issue before close-out. -- `Post-merge follow-up`: gather release/workflow evidence and post a concise update with links, findings delta, and next steps. -- `Docs-first cleanup loop`: review README/docs/tests, restate the target workflow, then simplify code and remove mismatched legacy paths in the same pass. -- `Verification scope`: run the smallest local check that proves the current change, defer heavyweight smoke coverage to CI, and call that tradeoff out explicitly. -- `Legacy reduction`: when a new direction is accepted, archive or delete conflicting old modules/configs instead of carrying both models forward. -- `Config-over-code`: express repo rules in YAML/manifests first, then keep shell glue thin and composable. -- `Agent handoff`: refresh repo context, manifest, and AGENTS instructions before deeper agent work so the repo contract is the source of truth. - -## Engineering practices - - - Keep the repository as the primary source of truth so context-driven engineering comes from repo contracts, docs, tests, config, and repo-native notes instead of agent memory. - - Prefer repo-centric mechanisms that discover workflows, tools, formats, and refs dynamically instead of hardcoding per-agent assumptions. - - Keep markdown, yaml, diagrams, tests, and command contracts self-contained in the repo so local and remote UDX workflows stay aligned. - - Keep deterministic workflow logic in repo config and scripts, and reserve AI agents for reading that contract, generating grounded summaries, and handling non-deterministic judgment without inventing hidden rules. - - Operate from repo-declared context at all times. Do not carry assumptions across sessions or rely on prompt history when the repo contract is available on disk. - - After loading the repo contract, prefer current GitHub issues, pull requests, review state, and commit history as the primary dynamic source for agent decisions. Use repo workflow and practice catalogs as fallback defaults when live GitHub signal is missing or thin. - - Reuse the repo's current GitHub patterns for branch names, PR titles and descriptions, issue writeups, and follow-up comments instead of inventing a new style for each change. - - When understanding behavior, read the YAML manifest that defines it before reading the code that implements it. Manifests are the interface — code is the mechanism. - - Check existing org patterns, configs, workflows, and templates before creating new ones. Reuse declared patterns instead of inventing alternatives. - - Detect the repo's canonical verification surface from context, but prefer GitHub workflow executions and monitor their outcomes when the repo already has CI coverage. Use local verification for quick scoped debugging, missing CI coverage, or reproducing workflow failures. - - After a PR exists, read automated review feedback, fix actionable findings, reply to false positives, resolve threads, and repeat until workflow state and bot feedback are clean. - - Keep the delivery chain explicit — branch, PR, and issue must be connected before close-out. Do not treat any step as done until the link to the next step is visible. - - Report outcomes with exact URLs, versions, findings deltas, and next steps so follow-up can be reused by humans and agents without drift. - - Use README, docs, and tests as the first alignment surface before broad refactors. Read the declared workflow before changing the implementation. - - Do not require custom repo files for dev.kit to work. Prefer standard engineering signals such as README, docs, tests, manifests, workflows, and deployment config, with dev.kit-owned continuity treated as optional acceleration only. - - Express repo rules in YAML manifests first, then keep shell glue thin and composable. Policy belongs in config, not buried in imperative scripts. - - When a new direction is accepted, archive or delete conflicting old modules and configs instead of carrying both models forward. - - If local verification is needed, run the smallest local check that proves the current change. Prefer broader or slower coverage in GitHub workflows, monitor the workflow run, and call the tradeoff out explicitly. - - Make sure to develop and test incrementally, so it is easier to detect problems early and build on verified behavior. - - Make sure to protect development executions with scoped and limited tasks, so failures are easier to isolate and blast radius stays low. - - Use the linked GitHub issue as the cross-repo context root. When work spans multiple repos, the issue URL anchors scope, acceptance criteria, and follow-up across all of them. +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 the highest-priority repo refs first: ./README.md, ./changes.md, ./deploy.yml, ./.github/workflows, ./Makefile, ./docs + - Run the canonical verification command: make test diff --git a/Makefile b/Makefile index c9e3aaa..21677f3 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,15 @@ WORKER_IMAGE := usabilitydynamics/udx-worker:latest -.PHONY: test test-docker test-docker-pull test-shell eval eval-view +.PHONY: test test-real test-docker test-docker-pull test-shell eval eval-view # Run tests locally test: bash tests/suite.sh +# Run optional local integration checks against real repos +test-real: + bash tests/real-repos.sh + # Run tests inside the worker container via deploy.yml # Requires @udx/worker-deployment: npm install -g @udx/worker-deployment test-docker: diff --git a/README.md b/README.md index 3a129d1..adfc788 100644 --- a/README.md +++ b/README.md @@ -2,154 +2,114 @@ -**GitHub-first session flow for developers and AI agents.** +**Repository context coverage and agent operating guidance.** -dev.kit separates three concerns: +`dev.kit` turns repo design into a usable contract for agents. -- base repo context signals -- deterministic tracing and mapping -- agent execution behavior +It does three things: -It generates `.rabbit/context.yaml` as the structured repo contract, then generates `AGENTS.md` as a built execution artifact that tells agents how to use that context with current GitHub experience first, and repo-declared workflow defaults when GitHub does not provide enough signal. +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 -In practice, dev.kit is middleware between repo facts and live GitHub experience. It keeps each work session anchored to the repo contract, then pushes developers and agents to use current issues, pull requests, review threads, workflow runs, and prior repo patterns before inventing new approaches. +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. ```bash npm install -g @udx/dev-kit ``` ---- - -## How it works - -``` -dev.kit → dev.kit repo → dev.kit agent -───────────────── ────────────────── ────────────────── -check environment analyze repo generate AGENTS.md -detect archetype trace dependencies write execution contract -guide to next write context.yaml from context.yaml -``` - -Each command moves the session forward and tells the next actor what to do. Agents should rerun the flow at each new interaction or session so context, workflow, and repo state stay synced. - ---- - ## 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 + cd my-repo -dev.kit # check tools, detect repo -dev.kit repo # analyze factors, trace deps, write .rabbit/context.yaml -dev.kit agent # generate AGENTS.md execution contract +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 ``` -The intended operating loop is: - -1. install `dev.kit` -2. start work with `dev.kit`, `dev.kit repo`, and `dev.kit agent` -3. read `.rabbit/context.yaml` and `AGENTS.md` -4. do the actual implementation using current GitHub repo experience first -5. resync the same flow at the next interaction or after repo changes - ---- +## Operating loop -## Generated Context And Workflow +The intended loop is simple: -**`.rabbit/context.yaml`** — generated repo map from repo definitions, source files, detected commands, traced dependencies, gaps, and other serializable repo signals: +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 -```yaml -repo: - name: dev.kit - archetype: library-cli - profile: node +That keeps context dynamic, grounded in repo signals, and resistant to drift. -refs: - - ./README.md - - ./package.json - -commands: - verify: make test - build: make build +## Commands -dependencies: - - repo: udx/reusable-workflows - type: reusable workflow - resolved: true - archetype: workflow-repo - used_by: - - .github/workflows/npm-release-ops.yml +| 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. | -gaps: - - config (partial) -``` +All commands support `--json` for machine-readable output and should guide the next step in human- and agent-friendly terms. -**`AGENTS.md`** — generated execution artifact for agents. It should stay simpler than `context.yaml`: rules, workflow, verification, and how to use current GitHub and learned context without duplicating refs, manifests, or dependency maps already serialized in `context.yaml`. +## Generated artifacts -Together, these two artifacts create a disciplined loop: +`dev.kit` produces two main artifacts: -- `context.yaml` says what the repo declares and what dev.kit could trace -- `AGENTS.md` says how to act on that contract using live GitHub experience first +- `.rabbit/context.yaml` — the machine-readable repo contract +- `AGENTS.md` — the generated operating layer for agents ---- - -## Commands +Keep the boundary strict: -| Command | What it does | -|---------|-------------| -| `dev.kit` | Check environment, detect repo, show next step | -| `dev.kit repo` | Analyze factors, trace dependencies, pull GitHub signals, write `context.yaml` | -| `dev.kit repo --force` | Re-resolve all dependencies from scratch | -| `dev.kit agent` | Generate `AGENTS.md` from `context.yaml` | -| `dev.kit learn` | Extract patterns from Claude/Codex sessions into lessons artifact | +- `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 -All commands support `--json` for machine-readable output. - ---- +## Install -## Repo Context +```bash +# npm (recommended) +npm install -g @udx/dev-kit -Repo context comes from repo source first: README, docs, workflows, manifests, tests, and other declared refs. `dev.kit repo` then traces and maps dependencies, commands, gaps, and other serializable signals into `context.yaml`. `AGENTS.md` turns that repo map into an operating contract for agents, using current GitHub context as the primary dynamic input and repo workflow/practice catalogs as fallback defaults. +# no npm? +curl -fsSL https://raw.githubusercontent.com/udx/dev.kit/latest/bin/scripts/install.sh | bash +``` -That means dev.kit does not just tell an agent what files exist. It also pushes the session toward the repo's real delivery loop: branch naming based on repo history, issue and PR writing based on existing patterns, bot feedback loops, and workflow status checks before close-out. +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). -## Cross-repo tracing +## Docs -Traces dependencies from 6 sources: workflow reuse, GitHub actions, Docker images, versioned YAML, GitHub URLs, npm packages. +- [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 -Same-org repos resolved via `gh api` + sibling directory. Docker images mapped to source repos automatically. +## Testing -```yaml -# udx/rabbit-automation-action -dependencies: - - repo: udx/gh-workflows - type: reusable workflow - resolved: true +For fast local checks: - - repo: usabilitydynamics/udx-worker-tooling:0.19.0 - type: base image - resolved: true - source_repo: udx/worker-tooling +```bash +bash tests/suite.sh --only core ``` ---- - -## Install +For installed-CLI testing in a real worker environment: ```bash -# npm (recommended) -npm install -g @udx/dev-kit - -# no npm? -curl -fsSL https://raw.githubusercontent.com/udx/dev.kit/latest/bin/scripts/install.sh | bash +bash tests/worker-smoke.sh ``` -Use one install path at a time. Installing with npm removes the curl-managed `~/.udx/dev.kit` home and shim. Installing with curl removes the global `@udx/dev-kit` package before laying down the local shim and home directory. - ---- +For opt-in validation against real local repos: -## Docs +```bash +bash tests/real-repos.sh /path/to/repo1 /path/to/repo2 +``` -- [Installation](docs/installation.md) — npm and curl installs, cleanup, uninstall, and verification -- [Context](docs/context.md) — `.rabbit/context.yaml`, its sections, and how it is generated -- [Agents](docs/agents.md) — `AGENTS.md` generation and how agents use it -- [Integration](docs/integration.md) — how the CLI, repo context, and agent workflow fit together +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 ec476c8..791a28b 100755 --- a/bin/dev-kit +++ b/bin/dev-kit @@ -38,7 +38,8 @@ usage() { local command_name="" local description="" cat <<'EOF' -Usage: dev.kit +Usage: dev.kit [--json] + dev.kit Commands: EOF @@ -61,34 +62,35 @@ EOF home_usage() { echo "Usage: dev.kit [--json]" echo - echo "Shows repo-aware landing output for the current directory." + echo "Checks environment, refreshes repo context, and generates agent guidance when a repo is detected." } dev_kit_run_home() { local format="${1:-text}" local state="installed" local repo_dir="$(pwd)" + 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="" - # JSON mode: compute everything up front then emit - if [ "$format" = "json" ]; then - local repo_root="" repo_detected="false" repo_kind="workspace" - local archetype="n/a" profile="n/a" git_state="no" - local priority_refs="" next_git_action="" - - 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")" - profile="$(dev_kit_repo_primary_profile "$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 + 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" 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 + + 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 @@ -101,11 +103,10 @@ dev_kit_run_home() { printf ' "repo_detected": %s,\n' "$repo_detected" printf ' "kind": "%s"' "$(dev_kit_json_escape "$repo_kind")" if [ "$repo_detected" = "true" ]; then - printf ',\n "repo": { "name": "%s", "root": "%s", "archetype": "%s", "profile": "%s", "git": %s, "markers": %s, "priority_refs": %s, "entrypoints": %s, "next_git_action": %s }\n' \ + printf ',\n "repo": { "name": "%s", "root": "%s", "archetype": "%s", "git": %s, "markers": %s, "priority_refs": %s, "entrypoints": %s, "next_git_action": %s }\n' \ "$(dev_kit_json_escape "$(dev_kit_repo_name "$repo_root")")" \ "$(dev_kit_json_escape "$repo_root")" \ "$(dev_kit_json_escape "$archetype")" \ - "$(dev_kit_json_escape "$profile")" \ "$([ "$git_state" = "yes" ] && printf 'true' || printf 'false')" \ "$(dev_kit_repo_markers_json "$repo_root")" \ "$(dev_kit_repo_priority_refs_json "$repo_root")" \ @@ -115,6 +116,11 @@ dev_kit_run_home() { printf '\n' fi printf ' },\n' + 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 ' },\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 @@ -125,9 +131,9 @@ dev_kit_run_home() { 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": "learn", "label": "Review lessons-learned workflow", "command": "dev.kit learn" }\n' + printf ' { "id": "agent", "label": "Start an AI session with repo context", "command": "dev.kit agent" }\n' printf ' ]\n' printf '}\n' return 0 @@ -152,33 +158,29 @@ $(dev_kit_env_tools_text) EOF # ── Repo detection ────────────────────────────────────────────────────────── - local repo_root="" - repo_root="$(dev_kit_repo_root "$repo_dir")" - if [ -n "$repo_root" ]; then - dev_kit_spinner_start "detecting repo" - local archetype profile + dev_kit_spinner_start "syncing repo context" archetype="$(dev_kit_repo_primary_archetype "$repo_root")" - profile="$(dev_kit_repo_primary_profile "$repo_root")" + 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} • profile ${profile}" + dev_kit_output_summary "$(dev_kit_repo_name "$repo_root") • ${archetype}" - dev_kit_output_section "do next" - # Guide to the next required step in the pipeline - local _context_yaml="${repo_root}/.rabbit/context.yaml" - local _agents_md="${repo_root}/AGENTS.md" - if [ ! -f "$_context_yaml" ]; then - dev_kit_output_row "required" "dev.kit repo — analyse repo and write context.yaml" - elif [ ! -f "$_agents_md" ]; then - dev_kit_output_row "required" "dev.kit agent — generate AGENTS.md from context" - else - dev_kit_output_row "required" "dev.kit repo — refresh context before starting work" - fi + 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 "next" + dev_kit_output_row "env" "dev.kit env" + dev_kit_output_row "repo" "dev.kit repo" + dev_kit_output_row "agent" "dev.kit agent" else dev_kit_output_summary "no repo detected at ${repo_dir}" - dev_kit_output_section "do next" - dev_kit_output_list_item "Navigate into a repo, then run dev.kit again." + dev_kit_output_section "next" + dev_kit_output_row "start" "cd && dev.kit" fi } @@ -193,12 +195,12 @@ command_usage() { echo " --check Report gaps without writing context.yaml" echo " --force Re-resolve all dependencies from scratch" ;; - agent) + env) echo " --json Output machine-readable JSON" + echo " --config Create or inspect the environment config file" ;; - learn) + agent) echo " --json Output machine-readable JSON" - echo " --workflow Override the configured learn workflow id" ;; uninstall) echo " --yes Remove dev.kit without a confirmation prompt" diff --git a/changes.md b/changes.md index 409058a..207ea3a 100644 --- a/changes.md +++ b/changes.md @@ -2,12 +2,17 @@ ### 0.8.0 +- Add `dev.kit env` and env config controls so repo and agent output can reflect actual tool and credential availability - Keep `.rabbit/context.yaml` focused on repo facts, tracing, commands, and gaps instead of inlining engineering practices and canonical workflow text -- Generate workflow and practice guidance for `AGENTS.md` directly from the config catalogs and learning sources, simplifying the boundary between repo context and agent behavior +- Generate workflow guidance for `AGENTS.md` directly from the config catalogs and repo context, simplifying the boundary between repo context and agent behavior - Rework the docs surface around that model: simplify the README, add focused `installation`, `context`, `agents`, and `integration` docs, and remove older overlapping overview/workflow/architecture pages -- Tighten GitHub-first workflow guidance across configs, generated agent instructions, and learned templates, including repo-pattern reuse, history-aware debugging, and bot-feedback loops -- Simplify lesson/template generation by consolidating workflow-tracing signals, deduping reusable templates by template name, and filtering unresolved placeholder template IDs from generated output +- Tighten GitHub-first workflow guidance across configs and generated agent instructions +- Remove `dev.kit learn` from the public command surface and trim the related configs, templates, and docs - Clean up install and packaging flow with global npm postinstall cleanup and explicit npm-versus-curl ownership rules +- Improve manifest contract tracing with repo-owned evidence from top-level `version` headers, GitHub references, and actual usage paths +- Collapse duplicate dependency evidence into single repo entries and avoid false dependency traces from nested YAML metadata or placeholder GitHub refs +- Add optional `tests/real-repos.sh` coverage for local public/private repos and trim unused synthetic fixtures +- Add upgrade-first guidance to generated `AGENTS.md` and installation/experience docs so new sessions start from the current `dev.kit` build ### 0.7.0 diff --git a/config/env.yaml b/config/env.yaml new file mode 100644 index 0000000..9ba8961 --- /dev/null +++ b/config/env.yaml @@ -0,0 +1,6 @@ +kind: envConfig +version: udx.dev/dev.kit/v1 + +config: + disabled_tools: [] + disabled_credentials: [] diff --git a/docs/agents.md b/docs/agents.md deleted file mode 100644 index a955a59..0000000 --- a/docs/agents.md +++ /dev/null @@ -1,167 +0,0 @@ -# Agents - -`dev.kit agent` turns repo context into agent instructions. - -Its main output is `AGENTS.md`, generated from `.rabbit/context.yaml` plus the repo's workflow, practice, and learning inputs. - -## Role - -If `context.yaml` answers what is known, `AGENTS.md` answers how an agent should operate. - -This is the behavior layer. It tells an agent how to use the fetched repo contract efficiently and with minimal drift. - -`AGENTS.md` should stay simpler than `context.yaml`. It is a built artifact, not the full repo map. - -One core rule should stay explicit: start each new interaction or session by rerunning: - -```bash -dev.kit -dev.kit repo -dev.kit agent -``` - -That keeps repo context, workflow expectations, and generated instructions in sync before deeper work. - -## What It Generates - -Run: - -```bash -dev.kit agent -``` - -This writes `AGENTS.md`. If `.rabbit/context.yaml` does not exist yet, `dev.kit agent` generates context first. - -## Why `AGENTS.md` Exists - -Raw repo facts are not enough. Agents still need explicit operating instructions for how to read, decide, verify, and hand work off. - -`AGENTS.md` exists to define: - -- session-start and interaction-start behavior -- context boundaries -- what to read first -- how to prioritize manifests over implementation -- how to follow the repo workflow -- how to choose between GitHub workflow verification and local verification -- how to use current GitHub history as the primary dynamic source -- how to fall back to repo workflow, practice catalogs, and lessons without drifting -- how to loop on PR reviews, status checks, and follow-up comments until delivery is actually clean - -## Inputs - -`AGENTS.md` is generated from repo evidence, not handwritten prompt text: - -- `.rabbit/context.yaml` -- YAML workflow and practice catalogs in `src/configs/` -- GitHub repo context when available -- lessons from prior agent sessions - -That keeps the instructions grounded and refreshable. - -The intended decision order is: - -1. current repo contract from `.rabbit/context.yaml` -2. current GitHub experience for this repo -3. repo-declared default workflows and practices -4. prior lessons and other secondary history - -That order matters. `AGENTS.md` should keep agents from skipping straight to implementation when the repo already has issue history, open PR discussion, branch conventions, workflow results, or bot feedback that should shape the next action. - -## Main Sections - -The generated contract typically includes: - -- rules -- repo commands -- priority refs -- config manifests -- external dependencies -- GitHub context -- workflow steps -- learned practices - -## What Belongs Here - -`AGENTS.md` is where dynamic execution guidance belongs. - -That includes: - -- how an agent should start each session -- how an agent should interpret repo context -- how an agent should use GitHub issues, PRs, and recent history first -- how an agent should sequence work and verification -- how an agent should avoid scanning and guesswork - -This is also where dev.kit can keep common GitHub-facing behaviors explicit, for example: - -- derive branch, issue, and PR naming from current repo patterns -- write PR bodies and issue updates in the style the repo already uses -- monitor workflow runs after a push -- read bot feedback, reply, fix, and resolve threads before human review - -Verification should follow the same priority: - -1. detect the repo's canonical verify surface from `context.yaml` -2. prefer GitHub workflow executions and monitor them when the repo already has CI coverage -3. use local verification when GitHub coverage is missing, when a workflow failure needs local reproduction, or when a quick scoped local check is the fastest way to debug - -So `AGENTS.md` should acknowledge local verify commands from repo context, but it should not enforce local execution as a universal rule. - -After a PR exists, the same contract should stay explicit: - -1. monitor related GitHub workflow executions and status checks -2. loop bot feedback on the PR -3. fix issues, reply to comments, and resolve threads -4. repeat until workflow state and bot feedback are clean - -It should not restate: - -- priority refs -- manifest inventories -- dependency maps - -Those already belong in `.rabbit/context.yaml`. - -This is the layer that combines static repo contract with current repo experience. - -## Relationship To `.rabbit/context.yaml` - -`.rabbit/context.yaml` is the structured repo map. - -`AGENTS.md` is the agent-facing execution contract built on top of that map. - -A useful shorthand is: - -- `context.yaml` = fetched and serialized repo knowledge -- `AGENTS.md` = instructions for using that knowledge well - -They should stay separate, but tightly coupled. - -`context.yaml` should stay factual and serializable. - -`AGENTS.md` should stay directive, current-state aware, and smaller. - -## Efficiency Goal - -The best result is a short path from repo state to grounded agent action: - -1. `context.yaml` tells the agent what exists and what was detected. -2. `AGENTS.md` tells the agent how to act on that information. -3. The agent spends less time rediscovering the repo and more time doing scoped work. - -## JSON Surface - -For machine-readable agent integration, use: - -```bash -dev.kit agent --json -``` - -The JSON template for that surface is: - -- `src/templates/agent.json` - -## Provider-Agnostic - -`AGENTS.md` is not tied to one model or tool. The goal is a repo-native execution contract that can guide Codex, Claude, Gemini, Copilot, or other agents without rewriting the repo’s expectations for each provider. diff --git a/docs/context-coverage.md b/docs/context-coverage.md new file mode 100644 index 0000000..b60f19d --- /dev/null +++ b/docs/context-coverage.md @@ -0,0 +1,89 @@ +# Context Coverage + +`.rabbit/context.yaml` is the structured coverage report for the repository. + +It should answer three questions: + +1. what did `dev.kit` detect? +2. what can be serialized cleanly? +3. what is still missing or only partial? + +## What It Covers + +`context.yaml` is for facts and deterministic transforms built from repo signals. + +Typical sections include: + +- 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 + +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. + +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. + +## 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. + +That means a gap can represent: + +- something missing +- something incomplete +- something present but too thin to treat as strong coverage + +Each gap entry should say: + +- which factor is weak +- whether it is `missing` or `partial` +- the current message for that condition +- the observed evidence that led to that result + +This is why context coverage testing should include broken or degraded repos, not only healthy ones. + +## Gap Repair Loop + +Gaps are meant to drive a repair loop: + +1. read the gap and its evidence +2. identify the repo-owned source asset that should carry that contract +3. patch that source asset instead of editing generated output +4. rerun `dev.kit repo` +5. confirm that the regenerated context improved + +That means gaps are part of maximum context discovery, not just an error report. + +## 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 +- long-form operating guidance +- issue or PR handling advice +- subjective reasoning about what an agent should do next +- local-only lesson artifacts + +Those belong in `AGENTS.md`. + +## Coverage Strategy + +The coverage model is repo-first: + +- read README, docs, manifests, workflows, tests, and deploy config +- detect commands and factor signals +- trace deterministic dependencies +- report gaps where coverage is weak + +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: + +- strong traceability where possible +- explicit unknowns where not +- a clear path to improve repo coverage over time diff --git a/docs/context.md b/docs/context.md deleted file mode 100644 index 7fa6f3e..0000000 --- a/docs/context.md +++ /dev/null @@ -1,141 +0,0 @@ -# Context - -`.rabbit/context.yaml` is the structured repo contract produced by `dev.kit repo`. - -It should answer: what can be fetched from this repo programmatically, what was detected, what is missing, and what other repos or workflows this repo depends on. - -## Role - -`context.yaml` is the machine-friendly map of the repository. It is not the place for agent policy, step-by-step behavior, or prompt-style instructions. - -Its boundary is best understood as two layers combined into one artifact: - -- base repo context signals -- deterministic tracing and mapping built from those signals - -Use it for facts such as: - -- repo identity -- priority refs -- canonical commands -- detected gaps -- manifests that define behavior -- traced dependencies and where they are used -- GitHub-derived repo experience that can be serialized cleanly - -## How It Is Produced - -Run: - -```bash -dev.kit repo -``` - -That command inspects repo-native signals, resolves what it can deterministically, and writes `.rabbit/context.yaml`. - -If the file is missing, `dev.kit agent` can generate it first before writing `AGENTS.md`. - -## What Feeds It - -`context.yaml` comes from evidence `dev.kit` can fetch or derive from the repo and its configured integrations: - -- README and docs -- manifests like `package.json`, `composer.json`, `Dockerfile` -- `.github/workflows/*` -- `deploy.yml` -- tests and command surfaces -- YAML config catalogs in `src/configs/` -- GitHub repo signals when available - -The important split is: - -- signals are the raw repo-facing inputs -- tracing and mapping are the deterministic transforms `dev.kit` performs on top of them - -Examples of tracing and mapping include: - -- detecting canonical verify, build, and run commands -- mapping reusable workflows to upstream repos -- mapping Docker images to likely source repos -- mapping versioned YAML contracts to upstream modules or repos -- mapping dependencies to the local files that use them - -The key boundary is simple: if `dev.kit` can detect it, trace it, or serialize it, it belongs here. - -## Main Sections - -The generated file in this repo currently includes: - -- `repo` -- `refs` -- `commands` -- `gaps` -- `dependencies` -- `manifests` -- `lessons` - -Depending on available integrations, it may also include GitHub-derived data. GitHub belongs here only when it can be fetched and serialized as repo experience data. It does not replace the repo contract. - -Examples of GitHub-derived data that fit here: - -- open pull requests -- recent pull request history -- linked issue references -- repo URLs that anchor the current work - -Those belong here because they can be serialized as current repo experience. Review decisions, step-by-step agent behavior, and how to respond to live findings still belong in `AGENTS.md`. - -## What Each Section Means - -`repo` identifies the repository through values such as `name`, `archetype`, and `profile`. - -`refs` is the priority reading list. It tells an agent or tool which files and directories matter first. - -This is the only place refs should live. `AGENTS.md` should point to `context.yaml`, not repeat the ref list. - -`commands` is the detected execution surface, such as `verify`, `build`, and `run`. - -`dependencies` is cross-repo tracing. Each entry explains what external repo, package, or workflow was referenced and which local files use it. - -`gaps` is a checklist of missing or partial factors `dev.kit` could detect programmatically. - -`manifests` lists the config files that define repo behavior. In `dev.kit`, these are first-class interfaces. - -`lessons` links prior session artifacts produced by `dev.kit learn`. - -## What Does Not Belong Here - -`context.yaml` should not try to be the final agent prompt. - -It is not where you explain: - -- how an agent should interpret ambiguity -- how an agent should sequence work for a user -- how an agent should balance repo context against current task context -- how an agent should loop on bot review comments -- how an agent should decide between local verification and GitHub workflow verification - -That layer belongs in `AGENTS.md`. - -## Efficiency Goal - -The point of `context.yaml` is compression without losing structure. - -It should let an agent answer questions like: - -- What commands exist? -- What docs and manifests matter first? -- What repo signals were used? -- What dependencies are real, and where are they used? -- Which engineering factors are missing? -- Which repo facts and traced signals are already available? - -If that data is available in `context.yaml`, the agent does not need to rediscover it by scanning. - -## JSON Contract - -For automation, the repo command JSON surface is defined by: - -- `src/templates/repo.json` - -That JSON output and `.rabbit/context.yaml` are the stable structured surfaces from `dev.kit repo`. diff --git a/docs/environment-config.md b/docs/environment-config.md new file mode 100644 index 0000000..149fbd9 --- /dev/null +++ b/docs/environment-config.md @@ -0,0 +1,75 @@ +# Environment Config + +`dev.kit` is environment-aware. Generated output depends on what can actually be observed from the current machine, available tools, and allowed credentials. + +That is why environment detection is a first-class command: + +```bash +dev.kit env +``` + +## What `dev.kit env` Does + +`dev.kit env` reports: + +- required tools such as `git`, `gh`, `npm`, `docker`, `jq`, and `yq` +- cloud tools when present +- 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. + +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 + +## `--config` + +Use: + +```bash +dev.kit env --config +``` + +This creates or updates: + +```text +$DEV_KIT_HOME/config/env.yaml +``` + +The goal is a small, explicit control surface for disabling tools or credentials you do not want `dev.kit` to use. + +## Config Shape + +Example: + +```yaml +kind: envConfig +version: udx.dev/dev.kit/v1 + +config: + disabled_tools: + - gh + - docker + disabled_credentials: + - gh + - aws +``` + +This does not uninstall tools or revoke credentials. It only changes what `dev.kit` treats as available for its own detection and guidance. + +## Why This Matters + +Environment state affects context coverage. + +Examples: + +- if `gh` is unavailable or disabled, GitHub-aware 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. + +In other words, `dev.kit env` is not a side utility. It is the capability layer that makes later repo and agent outputs trustworthy. diff --git a/docs/experience-guidance.md b/docs/experience-guidance.md new file mode 100644 index 0000000..23d2771 --- /dev/null +++ b/docs/experience-guidance.md @@ -0,0 +1,73 @@ +# 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 new file mode 100644 index 0000000..25d4289 --- /dev/null +++ b/docs/how-it-works.md @@ -0,0 +1,119 @@ +# How It Works + +`dev.kit` turns repo-declared context into a working contract for agents. + +The default starting point is: + +```bash +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 + +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 + +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`. + +## Command Flow + +Think of the command flow as four linked layers: + +### 1. Environment layer + +`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. + +### 2. Repo contract layer + +`dev.kit repo` inspects repo-owned signals and writes `.rabbit/context.yaml`. + +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 + +`dev.kit agent` generates `AGENTS.md` from the current repo contract. + +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. + +### 4. 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 + +That makes gaps part of the workflow, not just passive reporting. + +## Generated Artifacts + +`dev.kit` produces two core artifacts: + +- `.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. + +`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. + +The intended split is: + +- `context.yaml` answers what the repo declares +- `AGENTS.md` answers how an agent should use that declaration + +The goal is to free agents from carrying repo-specific memory in prompts while still keeping the operating model current. + +## 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. +- `lib/modules/*.sh` implements thin, config-driven detection and rendering helpers. +- `lib/commands/*.sh` exposes the public command flow: `env`, `repo`, `agent`, 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. + +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. + +## Command Roles + +`dev.kit env` inspects tools, auth state, and local env config. It defines what later steps can responsibly assume. + +`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. + +## Working Model + +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 + +That keeps the repo as the source of truth and reduces prompt drift between sessions. diff --git a/docs/installation.md b/docs/installation.md index 6c85326..874e4cb 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -7,15 +7,27 @@ Whichever path you use last becomes the active install. The installer cleans up the other path first so one install owns `dev.kit` at a time. -Installation only puts the command on the machine. The normal operating loop starts after install: +Installation only puts the command on the machine. Before relying on `dev.kit` for a session, make sure the active install is current, then start the normal operating loop: ```bash dev.kit -dev.kit repo -dev.kit agent ``` -That loop refreshes repo context, regenerates `AGENTS.md`, and makes the next work step explicit before an agent or developer starts changing the repo. +`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. + +## Upgrade + +Refresh `dev.kit` with the same install path you use already: + +```bash +# npm-managed install +npm install -g @udx/dev-kit + +# curl-managed install +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. ## Recommended Path @@ -101,13 +113,13 @@ After either install path, verify the active install with: dev.kit ``` -That confirms the command resolves correctly and shows the next step in the session flow. +That confirms the command resolves correctly and runs the normal guided flow when a repo is detected. -After that, continue with: +If you want to inspect or control environment capabilities directly, continue with: ```bash -dev.kit repo -dev.kit agent +dev.kit env +dev.kit env --config ``` -The goal is not only to confirm that the binary works. The goal is to start the session from current repo contract and current GitHub-aware workflow guidance rather than stale local memory. +Use `dev.kit repo` and `dev.kit agent` separately only when one generated artifact needs to be refreshed on its own. diff --git a/docs/integration.md b/docs/integration.md deleted file mode 100644 index 5661876..0000000 --- a/docs/integration.md +++ /dev/null @@ -1,120 +0,0 @@ -# Integration - -`dev.kit` works because it separates repo knowledge from agent behavior, then links them in one session flow. - -The goal is not to generate more files. The goal is to reduce uncertainty about what exists, what matters, and how to act. - -Another way to say it: dev.kit is middleware between repo-declared context and live GitHub experience. It keeps agents and developers grounded in the repo contract, then points them toward the current issue, PR, review, and workflow state that should drive the next decision. - -## Core Flow - -The integration model starts with three commands: - -```bash -dev.kit -dev.kit repo -dev.kit agent -``` - -Those commands connect five layers: - -1. local environment detection -2. structured repo context generation -3. deterministic tracing and mapping -4. agent contract generation -5. human or agent execution - -For agents, this is not only a first-time setup flow. It is the resync loop. On each new interaction or session, rerun the flow so the next action starts from current repo context rather than stale memory. - -## Separation Of Responsibilities - -The split should stay explicit: - -- `dev.kit repo` produces `.rabbit/context.yaml` -- `dev.kit agent` produces `AGENTS.md` - -`context.yaml` is the fetched map of the repo and its detectable signals. - -`AGENTS.md` is the operating contract that tells an agent how to use that map together with current GitHub context, learned patterns, and repo workflow defaults. - -That separation keeps both artifacts smaller and more reliable. - -In practice: - -- `context.yaml` owns refs, manifests, commands, dependencies, and gaps -- `AGENTS.md` points back to `context.yaml` and stays focused on workflow, practices, and behavior - -## Developer Integration - -For developers, `dev.kit` provides: - -- a quick environment check -- a generated summary of repo factors and gaps -- a canonical command surface for verify, build, and run -- a repeatable way to understand repo expectations before changing code - -This reduces time spent rediscovering how a repo works. - -It also shortens common GitHub loops. Instead of inventing a new branch name, PR structure, or issue update style each time, the repo contract can point the session back to current repo patterns and current review state first. - -## Agent Integration - -For agents, `dev.kit` provides: - -- a structured reading surface in `context.yaml` -- config and workflow manifests as first-class interfaces -- a behavior contract in `AGENTS.md` -- JSON output for automation and toolchains - -This means agents can spend less effort on discovery and more effort on scoped execution. - -That execution should stay GitHub-aware. The intended behavior is not just "read files, then code." It is "refresh repo contract, inspect current GitHub experience, act, then loop on workflows and automated review until the change is actually ready." - -## GitHub And History - -GitHub and learning data are most useful when they support agent decisions, not when they blur the repo map. - -In practice: - -- repo and integration signals can be serialized into `context.yaml` -- current issues, PRs, and repo history are the primary dynamic inputs in `AGENTS.md` -- workflow expectations and practice catalogs act as fallback defaults in `AGENTS.md` -- lessons remain secondary memory that should not outrank live repo or GitHub state - -That gives agents both structure and recency without mixing roles. - -## Workflow Integration - -The generated workflow is intended to fit into normal engineering work: - -1. start the session with `dev.kit`, `dev.kit repo`, and `dev.kit agent` -2. read the generated repo contract and agent contract -3. inspect current GitHub issue, PR, review, and branch context before inventing a new path -4. do the actual implementation or review work -5. verify through the repo-declared surface, preferring GitHub workflow runs when the repo already has CI coverage -6. loop on bot reviews, workflow failures, and follow-up comments until the delivery chain is clean -7. optionally run `dev.kit learn` so session outcomes feed future runs - -## Config-Driven Integration - -`dev.kit` does not rely on hidden prompt rules. It integrates through repo-owned config and workflow assets: - -- `src/configs/*.yaml` -- workflow files -- repo manifests -- docs -- tests - -That makes behavior inspectable, versioned, and reusable. - -## Efficiency Goal - -The best integration is: - -- `context.yaml` stays factual and structured -- tracing and mapping stay deterministic -- `AGENTS.md` stays directive and execution-oriented -- GitHub experience stays the primary dynamic source for agent judgment -- both are regenerated cheaply at session start - -That gives developers and agents one contract with two surfaces instead of two competing sources of truth. diff --git a/docs/smart-dependency-detection.md b/docs/smart-dependency-detection.md new file mode 100644 index 0000000..d8ccdc6 --- /dev/null +++ b/docs/smart-dependency-detection.md @@ -0,0 +1,47 @@ +# Smart Dependency Detection + +`dev.kit repo` does more than list local files. It also traces dependencies that shape how the repo really works. + +## What It Detects + +Cross-repo tracing currently covers sources such as: + +- reusable GitHub workflows +- GitHub actions +- Docker images +- versioned YAML references +- GitHub URLs +- npm packages + +These are then mapped into dependency entries in `.rabbit/context.yaml`. + +## Resolution Model + +The tracing model is deterministic. + +If `dev.kit` can resolve a dependency confidently, it records: + +- the dependency target +- its kind +- whether it was resolved +- where it is used in the current repo + +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. + +The point is not to invent a full dependency graph. The point is to make execution-shaping external context visible and traceable. + +## 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. + +## Coverage Limits + +Dependency detection still follows the same rule as the rest of `dev.kit`: report what can actually be observed. + +If a dependency cannot be resolved confidently from the available repo and environment signals, it should remain partial rather than be invented. + +That is also why unresolved or weak dependency coverage should feed the broader gap-repair loop instead of being hidden. diff --git a/lib/commands/agent.sh b/lib/commands/agent.sh index 9458faf..9845eba 100644 --- a/lib/commands/agent.sh +++ b/lib/commands/agent.sh @@ -38,9 +38,8 @@ dev_kit_cmd_agent() { dev_kit_spinner_start "writing agents.md" dev_kit_agent_write_agents_md "$repo_dir" "$agents_md_path" - local archetype profile + local archetype archetype="$(dev_kit_repo_primary_archetype "$repo_dir")" - profile="$(dev_kit_repo_primary_profile "$repo_dir")" dev_kit_spinner_stop "" if [ "$format" = "json" ]; then @@ -49,7 +48,6 @@ dev_kit_cmd_agent() { "repo=$(dev_kit_json_escape "$repo_name")" \ "path=$(dev_kit_json_escape "$repo_dir")" \ "archetype=$(dev_kit_json_escape "$archetype")" \ - "profile=$(dev_kit_json_escape "$profile")" \ "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")" \ @@ -59,7 +57,7 @@ dev_kit_cmd_agent() { return 0 fi - dev_kit_output_summary "${repo_name} • ${archetype} • profile ${profile}" + 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 @@ -78,26 +76,10 @@ dev_kit_cmd_agent() { dev_kit_output_row "agents.md" "$agents_md_path" dev_kit_output_row "context.yaml" "$context_yaml_path" - dev_kit_output_section "ready" - dev_kit_output_list_item "Context synced. Start your session following AGENTS.md workflow." - dev_kit_output_list_item "Run dev.kit → dev.kit repo → dev.kit agent at each new interaction or after repo updates to resync." -} - -dev_kit_agent_context_list() { - 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 && /^ - / { - gsub(/^ - "/, " - ") - sub(/"$/, "") - gsub(/\\"/, "\"") - gsub(/\\\\/, "\\") - print - } - ' "$context_yaml" + 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() { @@ -111,67 +93,59 @@ dev_kit_agent_context_multiline_block() { ' "$context_yaml" } -dev_kit_agent_github_section() { +dev_kit_agent_gap_lines() { local context_yaml="$1" - local section_name="$2" - awk -v section_name="$section_name" ' - $0 == " " section_name ":" { in_section = 1; next } - in_section && /^ - / { - sub(/^ - "?/, " - ") - sub(/"$/, "") - gsub(/\\"/, "\"") - gsub(/\\\\/, "\\") - print + 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_section && /^[^ ]|^ [a-z]/ { exit } - ' "$context_yaml" -} - -dev_kit_agent_practice_lines() { - local practices_file="" - practices_file="$(dev_kit_practices_config_path)" - [ -f "$practices_file" ] || return 0 - - awk ' - $1 == "config:" { in_config = 1; next } - in_config && $1 == "practices:" { in_practices = 1; next } - in_practices && $1 == "-" && $2 == "id:" { next } - in_practices && $1 == "message:" { - $1 = "" - sub(/^ /, "") - printf " - %s\n", $0 + 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 + } } - ' "$practices_file" + ' "$context_yaml" } dev_kit_agent_workflow_lines() { - local workflow_file="" - workflow_file="$(dev_kit_workflow_config_path)" - - if [ -f "$workflow_file" ]; then - awk ' - /^ - id:/ { flush(); label=""; note=""; in_note=0; next } - /^ label:/ { sub(/^[[:space:]]*label:[[:space:]]*/, "", $0); label=$0; next } - /^ note:[[:space:]]*>/ { in_note=1; next } - in_note && /^ / { - sub(/^[[:space:]]+/, "", $0) - note = (note == "") ? $0 : note " " $0 - next - } - in_note { flush(); in_note=0 } - function flush() { - if (label == "") return - if (note != "") printf " - %s: %s\n", label, note - else printf " - %s\n", label - } - END { flush() } - ' "$workflow_file" - return 0 - fi - - dev_kit_repo_workflow_json "${1:-$(pwd)}" | jq -r '.[]? | " - " + .label' 2>/dev/null || true + 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 </dev/null || true)" - if [ -n "$_pr_bodies" ]; then - _pr_headings="$(dev_kit_learning_github_pr_heading_pattern "$_pr_bodies")" - _pr_example="$(dev_kit_learning_github_best_pr_example "$_pr_bodies")" - - if [ -n "$_pr_headings" ] || [ -n "$_pr_example" ]; then - printf '## PR description guide\n\n' - printf '_Detected from recent merged PRs in this repo. Follow this structure when creating PRs._\n\n' - - if [ -n "$_pr_headings" ]; then - printf '**Common sections** (appear in multiple PRs):\n\n' - while IFS= read -r _h; do - [ -n "$_h" ] || continue - printf -- '- %s\n' "$_h" - done </dev/null || true)" - if [ -n "$_issue_comments" ]; then - _issue_patterns="$(dev_kit_learning_github_issue_update_detect "$_issue_comments")" - _issue_example="$(dev_kit_learning_github_best_issue_comment "$_issue_comments")" - - if [ -n "$_issue_patterns" ] || [ -n "$_issue_example" ]; then - printf '## Issue update guide\n\n' - printf '_Detected from recent issue comments by the authenticated user. Follow this style when posting updates._\n\n' - - if [ -n "$_issue_patterns" ]; then - printf '**Detected patterns:**\n\n' - while IFS= read -r _p; do - [ -n "$_p" ] || continue - printf -- '- %s\n' "$_p" - done < "$agents_md_path" } diff --git a/lib/commands/env.sh b/lib/commands/env.sh new file mode 100644 index 0000000..c4f4d68 --- /dev/null +++ b/lib/commands/env.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# @description: Inspect environment tools and dev.kit usage config + +dev_kit_cmd_env() { + local format="${1:-text}" + local manage_config=0 + + if [ "$#" -ge 1 ]; then + shift + fi + + while [ "$#" -gt 0 ]; do + case "$1" in + --config) manage_config=1 ;; + --*) + printf 'Unknown flag: %s\n' "$1" >&2 + printf 'Usage: dev.kit env [--json] [--config]\n' >&2 + return 1 + ;; + esac + shift + done + + if [ "$manage_config" -eq 1 ]; then + dev_kit_env_config_ensure + fi + + local config_path disabled_tools disabled_credentials + config_path="$(dev_kit_env_config_path)" + disabled_tools="$(dev_kit_env_config_list "disabled_tools")" + disabled_credentials="$(dev_kit_env_config_list "disabled_credentials")" + + if [ "$format" = "json" ]; then + printf '{\n' + printf ' "command": "env",\n' + printf ' "home": "%s",\n' "$(dev_kit_json_escape "$DEV_KIT_HOME")" + printf ' "tools": %s,\n' "$(dev_kit_env_tools_json)" + printf ' "capabilities": %s,\n' "$(dev_kit_global_context_capabilities_json)" + printf ' "config": {\n' + printf ' "path": "%s",\n' "$(dev_kit_json_escape "$config_path")" + printf ' "exists": %s,\n' "$([ -f "$config_path" ] && printf 'true' || printf 'false')" + printf ' "disabled_tools": %s,\n' "$(printf '%s' "$disabled_tools" | dev_kit_lines_to_json_array)" + printf ' "disabled_credentials": %s\n' "$(printf '%s' "$disabled_credentials" | dev_kit_lines_to_json_array)" + printf ' }\n' + printf '}\n' + return 0 + fi + + dev_kit_output_title "dev.kit env" + + local _env_line _env_cat _env_val _prev_cat="" + while IFS= read -r _env_line; do + [ -n "$_env_line" ] || continue + _env_cat="${_env_line%%|*}" + _env_val="${_env_line#*|}" + if [ "$_env_cat" != "$_prev_cat" ]; then + dev_kit_output_section "$_env_cat" + _prev_cat="$_env_cat" + fi + dev_kit_output_list_item "$_env_val" + done </dev/null && pwd || true)" - [ -n "$_norm" ] && repo_dir="$_norm" - - local lastrun_file="${repo_dir}/.rabbit/dev.kit/learn-last-run" - local latest_artifact - latest_artifact="$(dev_kit_learning_latest_artifact_path "$repo_dir")" - dev_kit_spinner_start "scanning agent sessions" - local session_refs - if [ -n "$latest_artifact" ] && [ -f "$latest_artifact" ]; then - session_refs="$(dev_kit_learning_all_session_refs "$repo_dir" "$lastrun_file")" - else - # No durable lessons artifact means there is no incremental baseline yet. - session_refs="$(dev_kit_learning_all_session_refs "$repo_dir" "")" - fi - dev_kit_spinner_stop "" - - if [ "$format" = "json" ]; then - local observed_json flow_json shared_context_json - observed_json="$(dev_kit_learning_observed_sources_json "$session_refs" "$repo_dir")" - flow_json="$(dev_kit_learning_merged_flow_json "$session_refs" "$repo_dir")" - shared_context_json="$(dev_kit_learning_merged_shared_context_json "$session_refs" "$repo_dir")" - - dev_kit_template_render "learn.json" \ - "command=learn" \ - "repo=$(dev_kit_json_escape "$repo_dir")" \ - "workflow_id=$(dev_kit_json_escape "$workflow_id")" \ - "workflow_name=$(dev_kit_json_escape "$(dev_kit_learning_workflow_name "$workflow_id")")" \ - "description=$(dev_kit_json_escape "$(dev_kit_learning_workflow_description "$workflow_id")")" \ - "sources=$(dev_kit_learning_workflow_sources "$workflow_id" | dev_kit_lines_to_json_array)" \ - "observed_sources=$observed_json" \ - "destinations=$(dev_kit_learning_destinations_json "$workflow_id")" \ - "session=null" \ - "flow=$flow_json" \ - "shared_context=$shared_context_json" \ - "knowledge_base=$(dev_kit_knowledge_hierarchy_json)" \ - "knowledge_sources=$(dev_kit_knowledge_preferred_sources | dev_kit_lines_to_json_array)" - return 0 - fi - - # ── text mode ──────────────────────────────────────────────────────────────── - - dev_kit_output_title "dev.kit learn" - dev_kit_output_summary "$(dev_kit_learning_workflow_name "$workflow_id") • lessons from agent sessions" - dev_kit_output_section "summary" - dev_kit_output_row "repo" "$repo_dir" - dev_kit_output_row "workflow" "$(dev_kit_learning_workflow_description "$workflow_id")" - - # source availability - dev_kit_output_section "sources" - local counts - counts="$(dev_kit_learning_source_counts "$session_refs")" - local claude_count codex_count - claude_count="$(printf "%s" "$counts" | sed 's/claude:\([0-9]*\).*/\1/')" - codex_count="$(printf "%s" "$counts" | sed 's/.*codex:\([0-9]*\)/\1/')" - dev_kit_output_row "claude" "${claude_count} session(s) found" - dev_kit_output_row "codex" "${codex_count} session(s) found" - - if [ -z "$session_refs" ]; then - dev_kit_output_section "observed" - if [ -n "$latest_artifact" ] && [ -f "$latest_artifact" ]; then - dev_kit_output_list_item "no new agent sessions found since the latest lessons artifact" - dev_kit_output_section "artifact" - dev_kit_output_list_item "$latest_artifact" - else - dev_kit_output_list_item "no agent sessions found for this repo" - fi - dev_kit_output_section "send to" - dev_kit_learning_destinations_text "$workflow_id" - return 0 - fi - - # sessions observed - dev_kit_output_section "observed" - local ref - while IFS= read -r ref; do - [ -n "$ref" ] || continue - local src id - src="$(dev_kit_learning_ref_source "$ref")" - id="$(dev_kit_learning_ref_id "$ref")" - dev_kit_output_list_item "[${src}] ${id}" - done </dev/null || true - printf "%d\n" "$(date +%s)" > "$lastrun_file" 2>/dev/null || true - - dev_kit_output_section "send to" - dev_kit_learning_destinations_text "$workflow_id" - - dev_kit_output_section "next" - dev_kit_output_row "refresh context" "dev.kit repo" - dev_kit_output_row "update agent" "dev.kit agent" -} - -dev_kit_learning_latest_artifact_path() { - local repo_dir="$1" - local latest_path="" - [ -d "${repo_dir}/.rabbit/dev.kit" ] || return 0 - - latest_path="$( - find "${repo_dir}/.rabbit/dev.kit" -maxdepth 1 -type f -name 'lessons-*.md' 2>/dev/null \ - | sort \ - | tail -n 1 - )" - - [ -n "$latest_path" ] && printf "%s" "$latest_path" - return 0 -} - -# ── artifact writer ─────────────────────────────────────────────────────────── - -dev_kit_learning_write_artifact() { - local repo_dir="$1" - local refs="$2" - local previous_artifact="${3:-}" - local repo_name date_stamp artifact_path - local previous_workflow_rules previous_references previous_templates previous_evidence - - repo_name="$(basename "$repo_dir")" - date_stamp="$(date +%Y-%m-%d)" - artifact_path="${repo_dir}/.rabbit/dev.kit/lessons-${repo_name}-${date_stamp}.md" - - mkdir -p "${repo_dir}/.rabbit/dev.kit" 2>/dev/null || true - previous_workflow_rules="$(dev_kit_learning_previous_section_lines "$previous_artifact" "Workflow rules")" - previous_references="$(dev_kit_learning_previous_section_lines "$previous_artifact" "Operational references")" - previous_templates="$( - dev_kit_learning_previous_section_lines "$previous_artifact" "Ready templates" \ - | dev_kit_learning_normalize_template_lines - )" - previous_evidence="$(dev_kit_learning_previous_section_lines "$previous_artifact" "Evidence highlights")" - - { - printf '# Lessons — %s — %s\n\n' "$repo_name" "$date_stamp" - - # source summary - local counts claude_count codex_count - counts="$(dev_kit_learning_source_counts "$refs")" - claude_count="$(printf "%s" "$counts" | sed 's/claude:\([0-9]*\).*/\1/')" - codex_count="$(printf "%s" "$counts" | sed 's/.*codex:\([0-9]*\)/\1/')" - printf 'Sources: claude (%s session(s)), codex (%s session(s))\n\n' \ - "$claude_count" "$codex_count" - - local evidence_lines rule_matches theme_matches flow_matches - local workflow_rules references templates - evidence_lines="$(dev_kit_learning_merge_unique_lines \ - "$previous_evidence" \ - "$(dev_kit_learning_evidence_highlights "$refs" "$repo_dir")" \ - | sed -n '1,8p')" - rule_matches="$(dev_kit_learning_merged_rule_matches "$refs" "$repo_dir")" - theme_matches="$(dev_kit_learning_prompt_theme_ids "$refs" "$repo_dir")" - flow_matches="$(dev_kit_learning_merged_flow_matches "$refs" "$repo_dir")" - workflow_rules="$(dev_kit_learning_merge_unique_lines \ - "$previous_workflow_rules" \ - "$( - while IFS= read -r rule_id; do - [ -n "$rule_id" ] || continue - printf '%s\n' "$(dev_kit_learning_session_rule_message "$rule_id")" - done < "$artifact_path" 2>/dev/null || return 0 - - printf "%s" "$artifact_path" -} - -dev_kit_learning_previous_section_lines() { - local artifact_path="${1:-}" - local heading="$2" - [ -n "$artifact_path" ] || return 0 - [ -f "$artifact_path" ] || return 0 - - awk -v heading="$heading" ' - $0 == "## " heading { in_section = 1; next } - /^## / && in_section { exit } - in_section && /^- / { - sub(/^- /, "", $0) - print - } - ' "$artifact_path" -} - -dev_kit_learning_merge_unique_lines() { - while [ "$#" -gt 0 ]; do - printf '%s\n' "$1" - shift - done | awk ' - { - gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0) - if ($0 == "") next - key = $0 - if (match($0, /^`[^`]+`:/)) { - key = substr($0, RSTART, RLENGTH) - } - if (seen[key]++) next - print - } - ' -} - -dev_kit_learning_normalize_template_lines() { - while IFS= read -r line; do - [ -n "$line" ] || continue - case "$line" in - '`'*) - printf '%s\n' "$line" - ;; - *) - printf '%s\n' "$(dev_kit_learning_flow_template "$line")" - ;; - esac - done -} - -dev_kit_learning_evidence_highlights() { - local refs="$1" - local repo_dir="${2:-$(pwd)}" - dev_kit_learning_merged_user_prompts "$refs" "$repo_dir" | sed -n '1,5p' -} - -dev_kit_learning_prompt_theme_ids() { - local refs="$1" - local repo_dir="${2:-$(pwd)}" - local prompts - - prompts="$(dev_kit_learning_merged_user_prompts "$refs" "$repo_dir" | tr '[:upper:]' '[:lower:]')" - - printf '%s\n' "$prompts" | grep -Eq 'readme|docs|good docs|docs first' && printf 'docs-first\n' - printf '%s\n' "$prompts" | grep -Eq 'smoke tests|github actions|don'\''t run tests locally|talking too long each time|performance.*tests' && printf 'verification-scope\n' - printf '%s\n' "$prompts" | grep -Eq 'cleanup|legacy|archive|leftovers' && printf 'cleanup-legacy\n' - printf '%s\n' "$prompts" | grep -Eq 'separate configuration[s]? from code|configuration separate from code|configurations? from code|yml \+ shell|yaml \+ shell' && printf 'config-over-code\n' - printf '%s\n' "$prompts" | grep -Eq 'agent.*repo context|engineering drift|repo-centric|dev\.kit agent' && printf 'repo-centric-agent-context\n' -} - -dev_kit_learning_prompt_theme_message() { - case "$1" in - docs-first) - printf '%s' 'Use README, docs, and tests as the first alignment surface before broad refactors so the implementation stays anchored to an explicit workflow.' - ;; - verification-scope) - printf '%s' 'Keep local verification targeted and lightweight during iteration, then move broader or slower validation into GitHub Actions or other CI gates.' - ;; - cleanup-legacy) - printf '%s' 'Treat cleanup of legacy modules, configs, and leftovers as part of the feature work so the repo keeps converging on the new operating model.' - ;; - config-over-code) - printf '%s' 'Prefer reusable YAML/manifests plus small shell wrappers over embedding policy directly into imperative scripts.' - ;; - repo-centric-agent-context) - printf '%s' 'Package agent context from repo artifacts and manifests so the workflow stays repo-centric and does not depend on ad hoc prompt memory.' - ;; - esac -} - -dev_kit_learning_prompt_theme_template() { - case "$1" in - docs-first) - printf '%s' '`Docs-first cleanup loop`: review README/docs/tests, restate the target workflow, then simplify code and remove mismatched legacy paths in the same pass.' - ;; - verification-scope) - printf '%s' '`Verification scope`: run the smallest local check that proves the current change, defer heavyweight smoke coverage to CI, and call that tradeoff out explicitly.' - ;; - cleanup-legacy) - printf '%s' '`Legacy reduction`: when a new direction is accepted, archive or delete conflicting old modules/configs instead of carrying both models forward.' - ;; - config-over-code) - printf '%s' '`Config-over-code`: express repo rules in YAML/manifests first, then keep shell glue thin and composable.' - ;; - repo-centric-agent-context) - printf '%s' '`Agent handoff`: refresh repo context, manifest, and AGENTS instructions before deeper agent work so the repo contract is the source of truth.' - ;; - esac -} - -dev_kit_learning_flow_template() { - case "$1" in - issue-scope) - printf '%s' '`Issue-to-scope`: start from the linked issue, confirm repo/workspace match, and restate the exact scope before changing code.' - ;; - verify-before-sync) - printf '%s' '`Verify-before-sync`: detect the repo verification surface, prefer GitHub workflow runs when the repo already has CI coverage, and use local checks for scoped debugging or reproduction.' - ;; - pr-chain) - printf '%s' '`Delivery chain`: sync the branch, prepare the PR in repo style, and connect the related issue before close-out.' - ;; - github-style-reuse) - printf '%s' '`GitHub style reuse`: inspect recent branch names, PRs, issues, and close-out comments, then reuse the repo'\''s current naming and writing patterns.' - ;; - post-follow-up) - printf '%s' '`Post-merge follow-up`: gather release/workflow evidence and post a concise update with links, findings delta, and next steps.' - ;; - docs-first-alignment) - printf '%s' '`Docs-first alignment`: review README, docs, and tests before refactoring, restate the target workflow, then simplify code and remove mismatched legacy paths.' - ;; - workflow-tracing) - printf '%s' '`Workflow tracing`: locate the actual workflow or deploy file first, then trace the commands and supporting docs that drive execution.' - ;; - verification-scope) - printf '%s' '`Verification scope`: run the smallest local check that proves the current change, defer heavyweight coverage to CI, and call the tradeoff out explicitly.' - ;; - legacy-reduction) - printf '%s' '`Legacy reduction`: when a new direction is accepted, archive or delete conflicting old modules/configs instead of carrying both models forward.' - ;; - agent-handoff) - printf '%s' '`Agent handoff`: refresh repo context, manifests, and AGENTS.md before deeper agent work so the repo contract is the source of truth.' - ;; - history-debugging) - printf '%s' '`History-aware debugging`: use related issues, PRs, and recent repo history first to understand prior changes and likely regressions before widening scope.' - ;; - *) - printf '%s' "$1" - ;; - esac -} diff --git a/lib/commands/repo.sh b/lib/commands/repo.sh index 2901708..89e1530 100644 --- a/lib/commands/repo.sh +++ b/lib/commands/repo.sh @@ -5,7 +5,7 @@ dev_kit_cmd_repo() { local format="${1:-text}" local repo_dir="$(pwd)" - local mode="learn" + local mode="write" local repo_root="" local repo_name="" local gaps_json="" @@ -39,7 +39,7 @@ dev_kit_cmd_repo() { # JSON mode: compute everything up front then emit template if [ "$format" = "json" ]; then gaps_json="$(dev_kit_scaffold_gaps_json "$repo_dir")" - if [ "$mode" = "learn" ]; then + if [ "$mode" = "write" ]; then dev_kit_context_yaml_write "$repo_dir" "$force_resolve" >/dev/null fi dev_kit_template_render "repo.json" \ @@ -48,7 +48,6 @@ dev_kit_cmd_repo() { "path=$(dev_kit_json_escape "$repo_dir")" \ "mode=$(dev_kit_json_escape "$mode")" \ "archetype=$(dev_kit_json_escape "$(dev_kit_repo_primary_archetype "$repo_dir")")" \ - "profile=$(dev_kit_json_escape "$(dev_kit_repo_primary_profile "$repo_dir")")" \ "markers=$(dev_kit_repo_markers_json "$repo_dir")" \ "factors=$(dev_kit_repo_factor_summary_json "$repo_dir")" \ "gaps=$gaps_json" \ @@ -90,7 +89,8 @@ dev_kit_cmd_repo() { # ── 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 || printf '0')" + 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" @@ -105,7 +105,7 @@ EOF fi # ── Write context.yaml ────────────────────────────────────────────────────── - if [ "$mode" = "learn" ]; then + if [ "$mode" = "write" ]; then dev_kit_spinner_start "writing context" dev_kit_context_yaml_write "$repo_dir" "$force_resolve" >/dev/null dev_kit_spinner_stop "" @@ -115,5 +115,8 @@ EOF dev_kit_output_list_item "$context_yaml_path" dev_kit_output_section "next" - dev_kit_output_row "run" "dev.kit agent" + dev_kit_output_row "agent" "dev.kit agent" + if [ "$gap_count" -gt 0 ]; then + dev_kit_output_row "repair" "follow AGENTS.md gap repair loop, then dev.kit repo" + fi } diff --git a/lib/modules/bootstrap.sh b/lib/modules/bootstrap.sh index 1ca42d6..f999fc8 100644 --- a/lib/modules/bootstrap.sh +++ b/lib/modules/bootstrap.sh @@ -19,7 +19,6 @@ $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/learning_sources.sh $REPO_DIR/lib/modules/repo_scaffold.sh EOF } @@ -30,7 +29,7 @@ dev_kit_command_description() { } dev_kit_public_command_names() { - printf '%s\n' repo agent learn uninstall + printf '%s\n' env repo agent uninstall } dev_kit_command_file_path() { diff --git a/lib/modules/config_catalog.sh b/lib/modules/config_catalog.sh index 583cb8d..bbabb2c 100644 --- a/lib/modules/config_catalog.sh +++ b/lib/modules/config_catalog.sh @@ -1,37 +1,23 @@ #!/usr/bin/env bash -DEV_KIT_KNOWLEDGE_CONFIG_FILE="src/configs/knowledge-base.yaml" -DEV_KIT_LEARNING_CONFIG_FILE="src/configs/learning-workflows.yaml" -DEV_KIT_LEARNING_DEFAULT_WORKFLOW="pr-lessons" -DEV_KIT_PRACTICES_CONFIG_FILE="src/configs/development-practices.yaml" -DEV_KIT_WORKFLOW_CONFIG_FILE="src/configs/development-workflows.yaml" - dev_kit_config_path() { printf "%s/%s" "$REPO_DIR" "$1" } -dev_kit_archetype_signals_path() { - dev_kit_config_path "src/configs/archetype-signals.yaml" -} - -dev_kit_archetype_rules_path() { - dev_kit_config_path "src/configs/archetype-rules.yaml" -} - -dev_kit_archetype_signal_list() { - dev_kit_yaml_config_list "$(dev_kit_archetype_signals_path)" "$1" +dev_kit_archetypes_path() { + dev_kit_config_path "src/configs/archetypes.yaml" } dev_kit_archetype_rule_ids() { - dev_kit_yaml_named_block_ids "$(dev_kit_archetype_rules_path)" "archetypes" + dev_kit_yaml_named_block_ids "$(dev_kit_archetypes_path)" "archetypes" } dev_kit_archetype_facets() { - dev_kit_yaml_nested_mapping_list "$(dev_kit_archetype_rules_path)" "archetypes" "$1" "$2" + dev_kit_yaml_nested_mapping_list "$(dev_kit_archetypes_path)" "archetypes" "$1" "$2" } dev_kit_archetype_description() { - dev_kit_yaml_named_block_scalar "$(dev_kit_archetype_rules_path)" "archetypes" "$1" "description" + dev_kit_yaml_named_block_scalar "$(dev_kit_archetypes_path)" "archetypes" "$1" "description" } dev_kit_context_config_path() { @@ -54,6 +40,41 @@ dev_kit_context_marker_group_paths() { dev_kit_yaml_named_block_list "$(dev_kit_context_config_path)" "marker_groups" "$1" "paths" } +dev_kit_context_section_field() { + dev_kit_yaml_named_block_scalar "$(dev_kit_context_config_path)" "context_sections" "$1" "$2" +} + +dev_kit_context_section_notes() { + dev_kit_yaml_named_block_list "$(dev_kit_context_config_path)" "context_sections" "$1" "notes" +} + +dev_kit_context_section_list() { + dev_kit_yaml_named_block_list "$(dev_kit_context_config_path)" "context_sections" "$1" "$2" +} + +dev_kit_context_section_detection_list_values() { + local section_id="$1" + local key="$2" + local list_name="" + + while IFS= read -r list_name; do + [ -n "$list_name" ] || continue + dev_kit_detection_list "$list_name" + done </dev/null | sort) +EOF + else + while IFS= read -r match; do + [ -n "$match" ] || continue + match="${match#"${repo_dir}/"}" + case "$match" in + *_old.md|*_old.markdown|README_old.md|readme_old.md) continue ;; + esac + dev_kit_ref_is_excluded "$match" && continue + refs="${refs}./${match} +" + done </dev/null | sort) +EOF + fi + ;; + *) + if [ -e "$repo_dir/$path" ]; then + dev_kit_ref_is_excluded "$path" && continue + refs="${refs}./${path} +" + fi + ;; + esac done </dev/null 2>&1 } @@ -221,341 +240,6 @@ dev_kit_sync_repo_supports_release_metadata() { [ -f "$repo_dir/package.json" ] || [ -f "$repo_dir/composer.json" ] || [ -f "$repo_dir/CHANGELOG.md" ] || [ -f "$repo_dir/changelog.md" ] } -dev_kit_sync_repo_hooks_dir() { - local repo_dir="$1" - local hooks_path="" - - hooks_path="$(git -C "$repo_dir" config --get core.hooksPath 2>/dev/null || true)" - if [ -n "$hooks_path" ]; then - printf "%s" "$hooks_path" - return 0 - fi - - if [ -d "$repo_dir/$(dev_kit_sync_default_hooks_dir)" ]; then - printf "%s" "$(dev_kit_sync_default_hooks_dir)" - return 0 - fi - - printf "%s" ".git/hooks" -} - -dev_kit_sync_hook_file() { - local repo_dir="$1" - local hook_name="$2" - local hooks_dir="" - - hooks_dir="$(dev_kit_sync_repo_hooks_dir "$repo_dir")" - printf "%s/%s" "$repo_dir/$hooks_dir" "$hook_name" -} - -dev_kit_sync_hook_summary() { - local repo_dir="$1" - local hook_name="$2" - local hook_file="" - local summary="" - - hook_file="$(dev_kit_sync_hook_file "$repo_dir" "$hook_name")" - if [ ! -f "$hook_file" ]; then - printf "%s|%s" "missing" "No $hook_name hook detected" - return 0 - fi - - if [ "$hook_name" = "pre-push" ] && rg -n "bash tests/run.sh" "$hook_file" >/dev/null 2>&1; then - printf "%s|%s" "present" "Runs bash tests/run.sh before push" - return 0 - fi - - printf "%s|%s" "present" "Hook exists at $(basename "$hook_file")" -} - -dev_kit_sync_hook_recommendation() { - local repo_dir="$1" - local hook_name="$2" - local hook_file="" - - hook_file="$(dev_kit_sync_hook_file "$repo_dir" "$hook_name")" - if [ ! -f "$hook_file" ]; then - printf "%s" "No hook-specific preparation detected." - return 0 - fi - - if [ "$hook_name" = "pre-push" ] && rg -n "bash tests/run.sh" "$hook_file" >/dev/null 2>&1; then - printf "%s" "Before push, identify the repo verification surface; prefer GitHub workflow runs when available, and use local execution to reproduce or isolate failures." - return 0 - fi - - printf "%s" "Review the configured hook before running the related git action." -} - -dev_kit_sync_hooks_lines() { - local repo_dir="$1" - local hook_name="" - local state_summary="" - local state="" - local summary="" - local recommendation="" - - for hook_name in pre-commit commit-msg pre-push; do - state_summary="$(dev_kit_sync_hook_summary "$repo_dir" "$hook_name")" - state="${state_summary%%|*}" - summary="${state_summary#*|}" - recommendation="$(dev_kit_sync_hook_recommendation "$repo_dir" "$hook_name")" - printf "%s|%s|%s|%s\n" "$hook_name" "$state" "$summary" "$recommendation" - done -} - -dev_kit_sync_hooks_text() { - local repo_dir="$1" - local line="" - local hook_name="" - local state="" - local summary="" - local recommendation="" - - while IFS= read -r line; do - [ -n "$line" ] || continue - hook_name="${line%%|*}" - line="${line#*|}" - state="${line%%|*}" - line="${line#*|}" - summary="${line%%|*}" - recommendation="${line#*|}" - printf ' - %s: %s\n' "$hook_name" "$state" - printf ' summary: %s\n' "$summary" - printf ' recommendation: %s\n' "$recommendation" - done <" or "claude:" -# Plain UUIDs (legacy callers) are treated as codex. - -DEV_KIT_SESSION_PATH_CACHE_KEY="" -DEV_KIT_SESSION_PATH_CACHE_VALUE="" -DEV_KIT_DISCOVERED_SESSION_CACHE_KEY="" -DEV_KIT_DISCOVERED_SESSION_CACHE_VALUE="" -DEV_KIT_SESSION_CONTENT_CACHE_KEY="" -DEV_KIT_SESSION_CONTENT_CACHE_VALUE="" - -# ── ref helpers ──────────────────────────────────────────────────────────────── - -dev_kit_learning_ref_source() { - case "$1" in - claude:*) printf "claude" ;; - codex:*) printf "codex" ;; - *) printf "codex" ;; # backward compat: plain UUID = codex - esac -} - -dev_kit_learning_ref_id() { - printf "%s" "${1#*:}" -} - -# ── path expansion ───────────────────────────────────────────────────────────── - -dev_kit_learning_expand_path() { - local value="$1" - value="${value/\$CODEX_HOME/${CODEX_HOME:-$HOME/.codex}}" - value="${value/\$CLAUDE_PROJECTS_ROOT/${CLAUDE_PROJECTS_ROOT:-}}" - case "$value" in - "~"*) value="${HOME}${value#\~}" ;; - esac - printf "%s\n" "$value" -} - -# ── enabled sources ──────────────────────────────────────────────────────────── - -dev_kit_learning_enabled_sources() { - # DEV_KIT_LEARN_SOURCES env var overrides config (comma or space separated) - if [ -n "${DEV_KIT_LEARN_SOURCES:-}" ]; then - printf "%s\n" "$DEV_KIT_LEARN_SOURCES" | tr ',' '\n' | tr ' ' '\n' | grep -v '^$' - return 0 - fi - dev_kit_learning_config_enabled_sources 2>/dev/null || printf "claude\ncodex\n" -} - -# ── codex session discovery ──────────────────────────────────────────────────── - -dev_kit_learning_session_roots() { - local root="" - while IFS= read -r root; do - [ -n "$root" ] || continue - dev_kit_learning_expand_path "$root" - done </dev/null - else - find "$root" -type f -name '*.jsonl' -print 2>/dev/null - fi - done < -a-b-dev-kit - printf "%s" "$1" | sed -E 's|/|-|g; s|[^[:alnum:]_-]|-|g; s|-+|-|g' -} - -dev_kit_learning_claude_project_dir() { - local repo_dir="$1" - printf "%s/%s" \ - "$(dev_kit_learning_claude_projects_root_path)" \ - "$(dev_kit_learning_claude_project_id "$repo_dir")" -} - -dev_kit_learning_claude_recent_session_paths() { - local repo_dir="$1" - local lastrun_file="${2:-}" - local max_recent="${3:-6}" - local history_path since_epoch - history_path="$(dev_kit_learning_claude_history_path)" - since_epoch="0" - - if [ -n "$lastrun_file" ] && [ -f "$lastrun_file" ]; then - since_epoch="$(cat "$lastrun_file" 2>/dev/null || printf '0')" - fi - - if [ -f "$history_path" ]; then - dev_kit_learning_claude_recent_session_paths_from_history "$repo_dir" "$since_epoch" "$max_recent" - return 0 - fi - - dev_kit_learning_claude_recent_session_paths_from_project_dir "$repo_dir" "$lastrun_file" "$max_recent" -} - -dev_kit_learning_claude_recent_session_paths_from_project_dir() { - local repo_dir="$1" - local lastrun_file="${2:-}" - local max_recent="${3:-6}" - local project_dir - - project_dir="$(dev_kit_learning_claude_project_dir "$repo_dir")" - [ -d "$project_dir" ] || return 0 - - if [ -n "$lastrun_file" ] && [ -f "$lastrun_file" ]; then - find "$project_dir" -maxdepth 1 -type f -name '*.jsonl' -newer "$lastrun_file" -print 2>/dev/null - else - find "$project_dir" -maxdepth 1 -type f -name '*.jsonl' -print 2>/dev/null - fi | sort -r | head -"$max_recent" -} - -dev_kit_learning_claude_recent_session_paths_from_history() { - local repo_dir="$1" - local since_epoch="${2:-0}" - local max_recent="${3:-6}" - local history_path projects_root - - history_path="$(dev_kit_learning_claude_history_path)" - projects_root="$(dev_kit_learning_claude_projects_root_path)" - [ -f "$history_path" ] || return 0 - [ -d "$projects_root" ] || return 0 - - awk -v project="$repo_dir" -v since_epoch="$since_epoch" ' - index($0, "\"project\":\"" project "\"") { - ts = "" - sid = "" - if (match($0, /"timestamp":[0-9]+/)) { - ts = substr($0, RSTART + 12, RLENGTH - 12) - } - if (match($0, /"sessionId":"[^"]+"/)) { - sid = substr($0, RSTART + 13, RLENGTH - 14) - } - if (sid == "" || ts == "") next - if ((ts / 1000) < since_epoch) next - if (!seen[sid] || ts > seen_ts[sid]) { - seen[sid] = 1 - seen_ts[sid] = ts - } - } - END { - for (sid in seen) { - printf "%s|%s\n", seen_ts[sid], sid - } - } - ' "$history_path" \ - | sort -t'|' -k1,1nr \ - | head -"$max_recent" \ - | while IFS='|' read -r _ts sid; do - [ -n "$sid" ] || continue - dev_kit_learning_claude_session_path_for_id "$sid" "$repo_dir" - printf '\n' - done \ - | awk 'NF && !seen[$0]++' -} - -dev_kit_learning_claude_session_path_for_id() { - local session_id="$1" - local repo_dir="${2:-$(pwd)}" - local path - - [ -n "$session_id" ] || return 0 - - path="$(dev_kit_learning_claude_project_dir "$repo_dir")/${session_id}.jsonl" - if [ -f "$path" ]; then - printf "%s" "$path" - return 0 - fi - - path="$( - find "$(dev_kit_learning_claude_projects_root_path)" -maxdepth 2 -type f -name "${session_id}.jsonl" 2>/dev/null \ - | head -n 1 - )" - [ -f "$path" ] && printf "%s" "$path" -} - -# ── unified session path resolution ─────────────────────────────────────────── - -dev_kit_learning_session_path() { - local ref="$1" - local repo_dir="${2:-$(pwd)}" - local source id - - source="$(dev_kit_learning_ref_source "$ref")" - id="$(dev_kit_learning_ref_id "$ref")" - - if [ "$DEV_KIT_SESSION_PATH_CACHE_KEY" = "${source}:${id}" ]; then - printf "%s" "$DEV_KIT_SESSION_PATH_CACHE_VALUE" - return 0 - fi - - local path="" - case "$source" in - claude) path="$(dev_kit_learning_claude_session_path_for_id "$id" "$repo_dir")" ;; - *) path="$(dev_kit_learning_codex_session_path_for_id "$id")" ;; - esac - - DEV_KIT_SESSION_PATH_CACHE_KEY="${source}:${id}" - DEV_KIT_SESSION_PATH_CACHE_VALUE="$path" - printf "%s" "$path" -} - -# ── multi-source session collection ─────────────────────────────────────────── - -# Returns "source:id" lines for all enabled sources, capped per-source, -# filtering sessions older than lastrun_file when provided. -dev_kit_learning_all_session_refs() { - local repo_dir="${1:-$(pwd)}" - local lastrun_file="${2:-}" - local max_recent source session_path session_id - - max_recent="$(dev_kit_learning_source_discovery_scalar "max_recent_sessions" 2>/dev/null || true)" - [ -n "$max_recent" ] || max_recent="6" - - while IFS= read -r source; do - [ -n "$source" ] || continue - case "$source" in - codex) - while IFS= read -r session_path; do - [ -n "$session_path" ] || continue - session_id="$(dev_kit_learning_codex_session_id_from_path "$session_path")" - [ -n "$session_id" ] || continue - # verify cwd matches repo - local cwd - cwd="$(dev_kit_learning_codex_session_cwd "$session_path")" - [ "$cwd" = "$repo_dir" ] || continue - printf "codex:%s\n" "$session_id" - done < 3) print value - } - } - ' "$path" \ - | dev_kit_learning_clean_prompt_lines \ - | awk 'NF && !seen[$0]++' - fi - ;; - *) - # Codex supports both legacy top-level message lines and current - # response_item payloads containing input_text blocks. - awk ' - { - is_user_message = 0 - - if ($0 ~ /"type":"response_item"/ && $0 ~ /"payload":\{"type":"message","role":"user"/) { - is_user_message = 1 - } else if ($0 ~ /"type":"message","role":"user"/) { - is_user_message = 1 - } - - if (is_user_message && match($0, /"text":"([^"\\]|\\.)*"/)) { - value = substr($0, RSTART + 8, RLENGTH - 9) - gsub(/\\n/, " ", value) - gsub(/\\"/, "\"", value) - print value - } - } - ' "$path" \ - | dev_kit_learning_clean_prompt_lines \ - | awk 'NF && !seen[$0]++' - ;; - esac -} - -dev_kit_learning_claude_history_display_lines() { - local session_id="$1" - local repo_dir="${2:-$(pwd)}" - local history_path - - history_path="$(dev_kit_learning_claude_history_path)" - [ -f "$history_path" ] || return 1 - [ -n "$session_id" ] || return 1 - - awk -v project="$repo_dir" -v session_id="$session_id" ' - index($0, "\"project\":\"" project "\"") && index($0, "\"sessionId\":\"" session_id "\"") { - if (match($0, /"display":"([^"\\]|\\.)*"/)) { - value = substr($0, RSTART + 11, RLENGTH - 12) - gsub(/\\n/, " ", value) - gsub(/\\"/, "\"", value) - print value - } - } - ' "$history_path" -} - -dev_kit_learning_clean_prompt_lines() { - awk ' - function trim(value) { - sub(/^[[:space:]]+/, "", value) - sub(/[[:space:]]+$/, "", value) - return value - } - function normalize(value) { - gsub(/[[:space:]]+/, " ", value) - return trim(value) - } - function clip_before_markers(value, marker_pos) { - marker_pos = index(value, " fq // ") - if (marker_pos > 0) value = substr(value, 1, marker_pos - 1) - marker_pos = index(value, " jonyfq@") - if (marker_pos > 0) value = substr(value, 1, marker_pos - 1) - marker_pos = index(value, " $ ") - if (marker_pos > 0) value = substr(value, 1, marker_pos - 1) - marker_pos = index(value, " [summary]") - if (marker_pos > 0) value = substr(value, 1, marker_pos - 1) - return trim(value) - } - { - line = normalize($0) - line = clip_before_markers(line) - lower = tolower(line) - - if (line == "") next - if (lower ~ /^/) next - if (lower ~ //) next - if (lower ~ //) next - if (lower ~ //) next - if (lower ~ /^fq[[:space:]]*\/\//) next - if (lower ~ /^jonyfq@.*>[[:space:]]/) next - if (lower ~ /^[[]summary[]]$/) next - if (lower ~ /^[[]sources[]]$/) next - if (lower ~ /^[[]observed[]]$/) next - if (lower ~ /^[[]workflow[]]$/) next - if (lower ~ /^[[]learned[]]$/) next - if (lower ~ /^[[]artifact[]]$/) next - if (lower ~ /^[[]send to[]]$/) next - if (lower ~ /^dev\.kit learn$/) next - if (lower ~ /^installed dev\.kit$/) next - if (lower ~ /^find all codex sessions/) next - if (lower ~ /^find all claude sessions/) next - if (lower ~ /^human-first raw output/) next - if (lower ~ /^path:[[:space:]]*~/) next - if (lower ~ /^repo:[[:space:]]/) next - if (lower ~ /^available:[[:space:]]*$/) next - if (lower ~ /^\$[[:space:]]/) next - if (lower ~ /^-[[:space:]]+\//) next - if (lower ~ /^\/[a-z]/) next - if (length(line) > 320) line = substr(line, 1, 317) "..." - - print line - } - ' -} - -# ── URL extraction (works on raw text, same for both sources) ───────────────── - -dev_kit_learning_sanitize_url_lines() { - awk ' - { - # Split on literal \n (JSON-escaped newlines that join multiple URLs) - n = split($0, segs, /\\n/) - for (i = 1; i <= n; i++) { - url = segs[i] - gsub(/\\+$/, "", url) - gsub(/[")]+$/, "", url) - gsub(/^[[:space:]]+|[[:space:]]+$/, "", url) - # Skip placeholder/example URLs from prompts and docs - if (url ~ /github\.com\/(test|example|org|owner|user)\//) url = "" - if (url ~ /github\.com\/[^\/]+\/repo[\/"]/) url = "" - if (url != "" && !seen[url]++) print url - } - } - ' -} - -dev_kit_learning_session_issue_urls() { - local ref="$1" - local repo_dir="${2:-$(pwd)}" - local path - path="$(dev_kit_learning_session_path "$ref" "$repo_dir")" - [ -f "$path" ] || return 0 - awk ' - match($0, /https:\/\/github\.com\/[^"[:space:]]+\/issues\/[0-9]+/) { - print substr($0, RSTART, RLENGTH) - } - ' "$path" | dev_kit_learning_sanitize_url_lines -} - -dev_kit_learning_session_pr_urls() { - local ref="$1" - local repo_dir="${2:-$(pwd)}" - local path - path="$(dev_kit_learning_session_path "$ref" "$repo_dir")" - [ -f "$path" ] || return 0 - awk ' - match($0, /https:\/\/github\.com\/[^"[:space:]]+\/pull\/[0-9]+/) { - print substr($0, RSTART, RLENGTH) - } - ' "$path" | dev_kit_learning_sanitize_url_lines -} - -dev_kit_learning_session_release_urls() { - local ref="$1" - local repo_dir="${2:-$(pwd)}" - local path - path="$(dev_kit_learning_session_path "$ref" "$repo_dir")" - [ -f "$path" ] || return 0 - awk ' - match($0, /https:\/\/github\.com\/[^"[:space:]\\)]+\/releases\/tag\/[^"[:space:]\\)]+/) { - print substr($0, RSTART, RLENGTH) - } - ' "$path" | dev_kit_learning_sanitize_url_lines -} - -# ── merged multi-source output ───────────────────────────────────────────────── - -dev_kit_learning_merged_user_prompts() { - local refs="$1" - local repo_dir="${2:-$(pwd)}" - local ref source - - while IFS= read -r ref; do - [ -n "$ref" ] || continue - source="$(dev_kit_learning_ref_source "$ref")" - dev_kit_learning_session_user_prompts "$ref" "$repo_dir" \ - | sed "s|^|[${source}] |" - done </dev/null || true)" - if [[ "$origin_url" =~ github\.com[:/]([^/]+/[^/]+)(\.git)?$ ]]; then - local result="${BASH_REMATCH[1]}" - printf "%s" "${result%.git}" - fi -} - -# Fetch recent merged PR bodies as delimited blocks. -# Output: ---PR#number title---\nbody\n---PR_END--- per PR -dev_kit_learning_github_recent_pr_bodies() { - local repo_dir="${1:-$(pwd)}" - local sample_size="${2:-8}" - local owner_repo - owner_repo="$(dev_kit_learning_github_owner_repo "$repo_dir")" - [ -n "$owner_repo" ] || return 0 - dev_kit_sync_can_run_gh || return 0 - [ "$(dev_kit_sync_gh_auth_state)" = "available" ] || return 0 - - gh api "repos/${owner_repo}/pulls?state=closed&sort=updated&direction=desc&per_page=${sample_size}" \ - 2>/dev/null | jq -r --argjson n "$sample_size" ' - [.[]? | select(.merged_at != null and (.body // "" | length) > 30)] | - sort_by(.merged_at) | reverse | .[:$n][] | - "---PR#\(.number) \(.title)---\n\(.body)\n---PR_END---" - ' 2>/dev/null || true -} - -# Detect common ## headings across PR bodies — returns headings found in 2+ PRs. -dev_kit_learning_github_pr_heading_pattern() { - local pr_bodies="$1" - [ -n "$pr_bodies" ] || return 0 - printf '%s\n' "$pr_bodies" | awk ' - /^---PR#/ { pr_idx++; in_pr=1; next } - /^---PR_END---/ { in_pr=0; next } - in_pr && /^##[[:space:]]/ { - heading = $0 - sub(/^##[[:space:]]+/, "", heading) - sub(/[[:space:]]+$/, "", heading) - if (heading != "" && !seen[pr_idx,heading]++) count[heading]++ - } - END { - for (h in count) { - if (count[h] >= 2) printf "%d|%s\n", count[h], h - } - } - ' | sort -t'|' -k1,1rn | cut -d'|' -f2 -} - -# Find the best-structured PR body (most headings) as a reference example. -# Output: PR number on line 1, body on remaining lines. -dev_kit_learning_github_best_pr_example() { - local pr_bodies="$1" - [ -n "$pr_bodies" ] || return 0 - printf '%s\n' "$pr_bodies" | awk ' - /^---PR#/ { - if (heading_count > best_count) { - best_count = heading_count - best_title = current_title - best_body = current_body - } - sub(/^---PR#/, "") - sub(/---$/, "") - current_title = $0 - current_body = "" - heading_count = 0 - in_pr = 1 - next - } - /^---PR_END---/ { - if (heading_count > best_count) { - best_count = heading_count - best_title = current_title - best_body = current_body - } - in_pr = 0 - next - } - in_pr { - current_body = current_body $0 "\n" - if ($0 ~ /^##[[:space:]]/) heading_count++ - } - END { - if (best_count > 0) { - print best_title - n = split(best_body, lines, "\n") - limit = (n < 60) ? n : 60 - for (i = 1; i <= limit; i++) printf "%s\n", lines[i] - } - } - ' -} - -# Fetch recent issue comments by the authenticated user. -# Output: delimited comment blocks. -dev_kit_learning_github_recent_issue_comments() { - local repo_dir="${1:-$(pwd)}" - local sample_size="${2:-20}" - local owner_repo gh_user - owner_repo="$(dev_kit_learning_github_owner_repo "$repo_dir")" - [ -n "$owner_repo" ] || return 0 - dev_kit_sync_can_run_gh || return 0 - [ "$(dev_kit_sync_gh_auth_state)" = "available" ] || return 0 - - gh_user="$(gh api user --jq '.login' 2>/dev/null || true)" - [ -n "$gh_user" ] || return 0 - - gh api "repos/${owner_repo}/issues/comments?sort=created&direction=desc&per_page=${sample_size}" \ - 2>/dev/null | jq -r --arg user "$gh_user" --argjson n "$sample_size" ' - [.[]? | select(.user.login == $user and (.body | length) > 40)] | .[:$n][] | - "---COMMENT_START---\n\(.body)\n---COMMENT_END---" - ' 2>/dev/null || true -} - -# Detect common patterns in issue comments — checklists, status headers, structured updates. -# Returns a short description of the detected pattern. -dev_kit_learning_github_issue_update_detect() { - local comments="$1" - [ -n "$comments" ] || return 0 - printf '%s\n' "$comments" | awk ' - /^---COMMENT_START---/ { comment_count++; in_c=1; has_checklist=0; has_heading=0; has_status=0; next } - /^---COMMENT_END---/ { - if (has_checklist) checklist_count++ - if (has_heading) heading_count++ - if (has_status) status_count++ - in_c=0; next - } - in_c && /^- \[[ x]\]/ { has_checklist=1 } - in_c && /^##[[:space:]]/ { has_heading=1 } - in_c && /[Ss]tatus:|[Uu]pdate:|[Pp]rogress:|[Dd]one:|[Nn]ext:/ { has_status=1 } - END { - if (comment_count == 0) exit - if (checklist_count >= 2) printf "checklist-driven updates (%d/%d comments use task checklists)\n", checklist_count, comment_count - if (heading_count >= 2) printf "structured sections (%d/%d comments use markdown headings)\n", heading_count, comment_count - if (status_count >= 2) printf "status/progress tracking (%d/%d comments include status labels)\n", status_count, comment_count - } - ' -} - -# Extract a good issue comment example (longest with structure). -dev_kit_learning_github_best_issue_comment() { - local comments="$1" - [ -n "$comments" ] || return 0 - printf '%s\n' "$comments" | awk ' - /^---COMMENT_START---/ { - if (length(current_body) > length(best_body) && current_structure > 0) { - best_body = current_body - } - current_body = "" - current_structure = 0 - in_c = 1 - next - } - /^---COMMENT_END---/ { - if (length(current_body) > length(best_body) && current_structure > 0) { - best_body = current_body - } - in_c = 0 - next - } - in_c { - current_body = current_body $0 "\n" - if ($0 ~ /^##[[:space:]]/ || $0 ~ /^- \[[ x]\]/ || $0 ~ /[Ss]tatus:|[Uu]pdate:/) current_structure++ - } - END { - if (best_body != "") { - n = split(best_body, lines, "\n") - limit = (n < 60) ? n : 60 - for (i = 1; i <= limit; i++) printf "%s\n", lines[i] - } - } - ' -} - -# Extract lesson artifact workflow rules and templates for AGENTS.md injection. -dev_kit_learning_lesson_rules() { - local repo_dir="$1" - local lessons_dir="${repo_dir}/.rabbit/dev.kit" - [ -d "$lessons_dir" ] || return 0 - local latest - latest="$(find "$lessons_dir" -maxdepth 1 -type f -name 'lessons-*.md' 2>/dev/null | sort -r | head -1)" - [ -f "$latest" ] || return 0 - - awk ' - /^## Workflow rules/ { in_section=1; next } - /^## / && in_section { exit } - in_section && /^- / { - sub(/^- /, "") - print - } - ' "$latest" -} - -dev_kit_learning_lesson_templates() { - local repo_dir="$1" - local lessons_dir="${repo_dir}/.rabbit/dev.kit" - [ -d "$lessons_dir" ] || return 0 - local latest - latest="$(find "$lessons_dir" -maxdepth 1 -type f -name 'lessons-*.md' 2>/dev/null | sort -r | head -1)" - [ -f "$latest" ] || return 0 - - awk ' - /^## Ready templates/ { in_section=1; next } - /^## / && in_section { exit } - in_section && /^- / { - sub(/^- /, "") - if ($0 ~ /^[a-z0-9_-]+$/) next - print - } - ' "$latest" -} diff --git a/lib/modules/local_env.sh b/lib/modules/local_env.sh index 64f051e..517bfda 100644 --- a/lib/modules/local_env.sh +++ b/lib/modules/local_env.sh @@ -2,6 +2,87 @@ _DEV_KIT_ENV_NPM_ROOT="" +dev_kit_env_config_path() { + printf '%s/config/env.yaml' "$DEV_KIT_HOME" +} + +dev_kit_env_config_ensure() { + local config_path="" + local config_dir="" + + config_path="$(dev_kit_env_config_path)" + config_dir="$(dirname "$config_path")" + mkdir -p "$config_dir" + + if [ ! -f "$config_path" ]; then + cat > "$config_path" <<'EOF' +kind: envConfig +version: udx.dev/dev.kit/v1 + +config: + disabled_tools: [] + disabled_credentials: [] +EOF + fi +} + +dev_kit_env_config_list() { + local list_name="$1" + local config_path="" + + config_path="$(dev_kit_env_config_path)" + [ -f "$config_path" ] || return 0 + + awk -v list_name="$list_name" ' + $1 == "config:" { in_config = 1; next } + in_config && $1 == list_name ":" { + in_list = 1 + line = $0 + if (line ~ /\[\]/) exit + next + } + in_list && /^[[:space:]]*-/ { + sub(/^[[:space:]]*-[[:space:]]*/, "", $0) + print + next + } + in_list && /^[^[:space:]]|^ [A-Za-z0-9_-]+:/ { exit } + ' "$config_path" +} + +dev_kit_env_config_has_value() { + local list_name="$1" + local value="$2" + local item="" + + while IFS= read -r item; do + [ -n "$item" ] || continue + [ "$item" = "$value" ] && return 0 + done </dev/null 2>&1; then _DEV_KIT_ENV_NPM_ROOT="$(npm root -g 2>/dev/null)" @@ -43,6 +124,11 @@ dev_kit_env_tool_state() { local tool="$1" local version="" + if dev_kit_env_tool_disabled "$tool"; then + printf 'disabled by config' + return 0 + fi + case "$tool" in "@udx/"*) local npm_root="" @@ -68,6 +154,11 @@ dev_kit_env_tool_state() { case "$tool" in gh) + if dev_kit_env_credential_disabled "gh"; then + version="$(dev_kit_env_tool_version "$tool")" + printf 'available (%s, auth disabled by config)' "${version:-installed}" + return 0 + fi case "$(dev_kit_sync_gh_auth_state)" in available) version="$(dev_kit_env_tool_version "$tool")" @@ -83,6 +174,15 @@ dev_kit_env_tool_state() { esac ;; *) + case "$tool" in + aws|gcloud|az) + if dev_kit_env_credential_disabled "$tool"; then + version="$(dev_kit_env_tool_version "$tool")" + printf 'available (%s, auth disabled by config)' "${version:-installed}" + return 0 + fi + ;; + esac version="$(dev_kit_env_tool_version "$tool")" if [ -n "$version" ]; then printf 'available (%s)' "$version" @@ -205,6 +305,9 @@ dev_kit_env_tools_text() { fi case "$status" in + disabled*) + printf '%s|○ %s — %s\n' "$category" "$tool" "$enables" + ;; missing) printf '%s|✗ %s — %s\n' "$category" "$tool" "$enables" ;; diff --git a/lib/modules/output.sh b/lib/modules/output.sh index a336db5..2e6b756 100644 --- a/lib/modules/output.sh +++ b/lib/modules/output.sh @@ -117,9 +117,3 @@ dev_kit_output_status_row() { esac printf ' %-*s %s %s\n' "$DEV_KIT_OUTPUT_LABEL_WIDTH" "${label}:" "$icon" "$status" } - -# ── Navigation hint ─────────────────────────────────────────────────────────── - -dev_kit_output_hint() { - printf ' %s %s\n' "→" "$1" -} diff --git a/lib/modules/repo_archetypes.sh b/lib/modules/repo_archetypes.sh index fed8df3..a95d4a3 100644 --- a/lib/modules/repo_archetypes.sh +++ b/lib/modules/repo_archetypes.sh @@ -5,52 +5,42 @@ DEV_KIT_REPO_FACETS_CACHE_VALUE="" DEV_KIT_REPO_ARCHETYPES_CACHE_REPO="" DEV_KIT_REPO_ARCHETYPES_CACHE_VALUE="" -dev_kit_repo_has_any_dir_from_signal_list() { +dev_kit_repo_has_kubernetes_manifest() { local repo_dir="$1" - local list_name="$2" - local path="" + local file_path="" - while IFS= read -r path; do - [ -n "$path" ] || continue - if dev_kit_repo_has_dir "$repo_dir" "$path"; then + 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 || true)" + [ -n "$command_kind" ] || return 1 - if [ -n "$command" ]; then - printf "%s" "$command" - return 0 - fi + while IFS= read -r source_type; do + [ -n "$source_type" ] || continue + case "$source_type:$command_kind" in + make_targets:verify) + if dev_kit_repo_has_make_target "$repo_dir" "test"; then + printf '%s|%s|%s\n' "make_targets" "make test" "Makefile" + return 0 + fi + ;; + make_targets:build) + if dev_kit_repo_has_make_target "$repo_dir" "build"; then + printf '%s|%s|%s\n' "make_targets" "make build" "Makefile" + return 0 + fi + ;; + make_targets:run) + if dev_kit_repo_has_make_target "$repo_dir" "run"; then + printf '%s|%s|%s\n' "make_targets" "make run" "Makefile" + return 0 + fi + ;; + package_scripts:verify) + if dev_kit_repo_has_node_test_script "$repo_dir"; then + printf '%s|%s|%s\n' "package_scripts" "npm test" "package.json" + return 0 + fi + if dev_kit_repo_has_composer_test_script "$repo_dir"; then + printf '%s|%s|%s\n' "package_scripts" "composer test" "composer.json" + return 0 + fi + ;; + package_scripts:build) + if dev_kit_repo_has_node_build_script "$repo_dir"; then + printf '%s|%s|%s\n' "package_scripts" "npm run build" "package.json" + return 0 + fi + if dev_kit_repo_has_composer_build_script "$repo_dir"; then + printf '%s|%s|%s\n' "package_scripts" "composer build" "composer.json" + return 0 + fi + ;; + package_scripts:run) + if dev_kit_repo_has_node_start_script "$repo_dir"; then + printf '%s|%s|%s\n' "package_scripts" "npm start" "package.json" + return 0 + fi + ;; + documented_commands:verify) + local documented_command documented_source + documented_command="$(dev_kit_repo_documented_command "$repo_dir" "verification" || true)" + documented_source="$(dev_kit_repo_documented_command_source "$repo_dir" "verification" || true)" + if [ -n "$documented_command" ]; then + printf '%s|%s|%s\n' "documented_commands" "$documented_command" "$documented_source" + return 0 + fi + ;; + documented_commands:build) + local documented_command documented_source + documented_command="$(dev_kit_repo_documented_command "$repo_dir" "build" || true)" + documented_source="$(dev_kit_repo_documented_command_source "$repo_dir" "build" || true)" + if [ -n "$documented_command" ]; then + printf '%s|%s|%s\n' "documented_commands" "$documented_command" "$documented_source" + return 0 + fi + ;; + documented_commands:run) + local documented_command documented_source + documented_command="$(dev_kit_repo_documented_command "$repo_dir" "run" || true)" + documented_source="$(dev_kit_repo_documented_command_source "$repo_dir" "run" || true)" + if [ -n "$documented_command" ]; then + printf '%s|%s|%s\n' "documented_commands" "$documented_command" "$documented_source" + return 0 + fi + ;; + esac + done </dev/null || true)" + [ -n "$result" ] || return 1 + printf "%s" "$(printf '%s' "$result" | cut -d'|' -f2)" +} + dev_kit_repo_factor_ids() { printf '%s\n' documentation dependencies config pipeline } diff --git a/lib/modules/repo_navigation.sh b/lib/modules/repo_navigation.sh deleted file mode 100644 index f183874..0000000 --- a/lib/modules/repo_navigation.sh +++ /dev/null @@ -1,674 +0,0 @@ -#!/usr/bin/env bash - -DEV_KIT_REMOTE_LOOKUPS="${DEV_KIT_REMOTE_LOOKUPS:-0}" -DEV_KIT_REPO_DEFAULT_BRANCH_CACHE="" -DEV_KIT_MODULE_REPO_DIR_CACHE="" - -dev_kit_local_repos_root_path() { - local root="" - - root="$(dev_kit_knowledge_local_repos_root)" - [ -n "$root" ] || return 1 - printf "%s/%s" "$HOME" "$root" -} - -dev_kit_repo_workflow_ref_lines() { - local repo_dir="$1" - local workflow_file="" - local workflow_ref="" - - [ -d "$repo_dir/.github/workflows" ] || return 0 - - while IFS= read -r workflow_file; do - [ -n "$workflow_file" ] || continue - while IFS= read -r workflow_ref; do - [ -n "$workflow_ref" ] || continue - printf '%s\n' "$workflow_ref" - done <= 2) printf "%s/%s", $1, $2 }' -} - -dev_kit_repo_is_udx_slug() { - case "$1" in - udx/*) return 0 ;; - esac - return 1 -} - -dev_kit_cache_get() { - local cache_data="$1" - local cache_key="$2" - - printf '%s\n' "$cache_data" | awk -F'|' -v key="$cache_key" '$1 == key { print substr($0, index($0, "|") + 1); exit }' -} - -dev_kit_cache_put() { - local cache_name="$1" - local cache_key="$2" - local cache_value="$3" - local current="" - - current="$(eval "printf '%s' \"\${$cache_name:-}\"")" - current="$(printf '%s\n' "$current" | awk -F'|' -v key="$cache_key" '$1 != key')" - if [ -n "$current" ]; then - current="${current}"$'\n' - fi - eval "$cache_name=\$(printf '%s' \"\$current\$cache_key|$cache_value\")" -} - -dev_kit_repo_remote_default_branch() { - local repo_slug="$1" - local cached="" - local repo_path="" - local origin_head="" - - dev_kit_repo_is_udx_slug "$repo_slug" || return 1 - cached="$(dev_kit_cache_get "$DEV_KIT_REPO_DEFAULT_BRANCH_CACHE" "$repo_slug")" - if [ -n "$cached" ]; then - printf '%s' "$cached" - return 0 - fi - - repo_path="$(dev_kit_local_repo_path "$repo_slug" 2>/dev/null || true)" - if [ -d "$repo_path/.git" ]; then - origin_head="$(git -C "$repo_path" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)" - if [ -n "$origin_head" ]; then - origin_head="${origin_head##*/}" - dev_kit_cache_put "DEV_KIT_REPO_DEFAULT_BRANCH_CACHE" "$repo_slug" "$origin_head" - printf '%s' "$origin_head" - return 0 - fi - fi - - if [ "$DEV_KIT_REMOTE_LOOKUPS" != "1" ]; then - return 0 - fi - - command -v gh >/dev/null 2>&1 || return 1 - cached="$(gh api "repos/$repo_slug" --jq '.default_branch' 2>/dev/null || true)" - if [ -n "$cached" ]; then - dev_kit_cache_put "DEV_KIT_REPO_DEFAULT_BRANCH_CACHE" "$repo_slug" "$cached" - printf '%s' "$cached" - fi -} - -dev_kit_ref_path_without_repo() { - local ref="$1" - printf "%s" "$ref" | cut -d/ -f3- -} - -dev_kit_ref_without_version() { - local ref="$1" - printf "%s" "${ref%@*}" -} - -dev_kit_repo_ref_local_file() { - local ref="$1" - local repo_slug="" - local repo_path="" - local rel_path="" - - repo_slug="$(dev_kit_repo_slug_from_ref "$ref")" - [ -n "$repo_slug" ] || return 1 - repo_path="$(dev_kit_local_repo_path "$repo_slug" 2>/dev/null || true)" - rel_path="$(dev_kit_ref_path_without_repo "$(dev_kit_ref_without_version "$ref")")" - [ -n "$repo_path" ] || return 1 - [ -n "$rel_path" ] || return 1 - printf "%s/%s" "$repo_path" "$rel_path" -} - -dev_kit_repo_workflow_doc_candidate_paths() { - local ref="$1" - local workflow_name="" - local ref_path="" - - ref_path="$(dev_kit_ref_path_without_repo "$(dev_kit_ref_without_version "$ref")")" - workflow_name="$(basename "$ref_path")" - workflow_name="${workflow_name%.yml}" - workflow_name="${workflow_name%.yaml}" - - printf "docs/%s.md\n" "$workflow_name" - printf "docs/%s.md\n" "$(printf '%s' "$workflow_name" | tr '-' '_')" -} - -dev_kit_repo_workflow_dependency_lines() { - local repo_dir="$1" - local workflow_ref="" - local workflow_file="" - local dependency_ref="" - - while IFS= read -r workflow_ref; do - [ -n "$workflow_ref" ] || continue - printf 'workflow|%s\n' "$workflow_ref" - workflow_file="$(dev_kit_repo_ref_local_file "$workflow_ref" 2>/dev/null || true)" - [ -f "$workflow_file" ] || continue - while IFS= read -r dependency_ref; do - [ -n "$dependency_ref" ] || continue - printf 'dependency|%s|%s\n' "$workflow_ref" "$dependency_ref" - done </dev/null 2>&1; then - rg -o '"@udx/[^"]+"' "$repo_dir/package.json" | tr -d '"' | sed 's#^@udx/#udx/#' | awk '!seen[$0]++' - return 0 - fi - - grep -Eo '"@udx/[^"]+"' "$repo_dir/package.json" | tr -d '"' | sed 's#^@udx/#udx/#' | awk '!seen[$0]++' -} - -dev_kit_repo_docker_dependency_lines() { - local repo_dir="$1" - local docker_file="$repo_dir/Dockerfile" - - [ -f "$docker_file" ] || return 0 - - awk ' - toupper($1) == "FROM" { - image = $2 - sub(/^[[:space:]]+/, "", image) - sub(/[[:space:]]+$/, "", image) - sub(/@.*/, "", image) - split(image, parts, ":") - image = parts[1] - if (image ~ /^ghcr\.io\/udx\//) { - sub(/^ghcr\.io\//, "", image) - print image - } else if (image ~ /^usabilitydynamics\//) { - sub(/^usabilitydynamics\//, "udx/", image) - print image - } - } - ' "$docker_file" | awk '!seen[$0]++' -} - -dev_kit_repo_dependency_repo_lines() { - local repo_dir="$1" - local line="" - local repo_slug="" - - while IFS= read -r line; do - [ -n "$line" ] || continue - case "$line" in - workflow\|*) - repo_slug="$(dev_kit_repo_slug_from_ref "${line#workflow|}")" - ;; - dependency\|*) - repo_slug="$(dev_kit_repo_slug_from_ref "$(printf '%s' "$line" | cut -d'|' -f3)")" - ;; - *) - repo_slug="$line" - ;; - esac - [ -n "$repo_slug" ] || continue - dev_kit_repo_is_udx_slug "$repo_slug" || continue - printf '%s\n' "$repo_slug" - done </dev/null || true)" - [ -d "$repo_path" ] || continue - printf '%s\n' "$repo_path" - done </dev/null || true)" - [ -d "$module_root" ] || continue - printf '%s|%s|%s/readme.md|%s/configs/default.yml\n' "$module" "$source_path" "$module_root" "$module_root" - done < %s, %s\n' "$module" "$source_path" "$doc_path" "$default_path" - done </ (existing: %s)" "$envs" - return 0 - fi - - printf "%s" "./.rabbit/infra_configs/development//" -} - -dev_kit_repo_source_chain_text() { - local repo_dir="$1" - local line="" - local kind="" - local workflow_ref="" - local dependency_ref="" - local local_file="" - local doc_file="" - local candidate="" - local repo_slug="" - local default_branch="" - local repo_doc="" - - while IFS= read -r repo_doc; do - [ -n "$repo_doc" ] || continue - printf ' - repo docs: %s\n' "$repo_doc" - done </dev/null || true)" - repo_slug="$(dev_kit_repo_slug_from_ref "$workflow_ref")" - default_branch="$(dev_kit_repo_remote_default_branch "$repo_slug")" - if [ -f "$local_file" ]; then - printf ' - reusable workflow: %s -> %s' "$workflow_ref" "$local_file" - if [ -n "$default_branch" ]; then - printf ' [default branch: %s]' "$default_branch" - fi - printf '\n' - while IFS= read -r candidate; do - [ -n "$candidate" ] || continue - doc_file="$(dirname "$local_file")/../../$candidate" - if [ -f "$doc_file" ]; then - printf ' - workflow docs: %s\n' "$doc_file" - break - fi - done </dev/null || true)" - default_branch="$(dev_kit_repo_remote_default_branch "$repo_slug")" - if [ -f "$local_file" ]; then - printf ' - workflow dependency: %s -> %s' "$dependency_ref" "$local_file" - if [ -n "$default_branch" ]; then - printf ' [default branch: %s]' "$default_branch" - fi - printf '\n' - else - printf ' - workflow dependency: %s' "$dependency_ref" - if [ -n "$default_branch" ]; then - printf ' [default branch: %s]' "$default_branch" - fi - printf '\n' - fi - ;; - esac - done </dev/null || true)" - repo_slug="$(dev_kit_repo_slug_from_ref "$workflow_ref")" - default_branch="$(dev_kit_repo_remote_default_branch "$repo_slug")" - if [ "$first" -eq 0 ]; then - printf ", " - fi - printf '{ "kind": "reusable_workflow", "refs": ["%s"' "$(dev_kit_json_escape "$workflow_ref")" - if [ -f "$local_file" ]; then - printf ', "%s"' "$(dev_kit_json_escape "$local_file")" - while IFS= read -r candidate; do - [ -n "$candidate" ] || continue - doc_file="$(dirname "$local_file")/../../$candidate" - if [ -f "$doc_file" ]; then - printf ', "%s"' "$(dev_kit_json_escape "$doc_file")" - break - fi - done </dev/null || true)" - default_branch="$(dev_kit_repo_remote_default_branch "$repo_slug")" - if [ "$first" -eq 0 ]; then - printf ", " - fi - printf '{ "kind": "workflow_dependency", "refs": ["%s"' "$(dev_kit_json_escape "$dependency_ref")" - if [ -f "$local_file" ]; then - printf ', "%s"' "$(dev_kit_json_escape "$local_file")" - fi - printf '], "default_branch": "%s" }' "$(dev_kit_json_escape "$default_branch")" - emitted=1 - ;; - esac - if [ "$emitted" -eq 1 ]; then - first=0 - fi - done </dev/null || true)" + if [ -n "$backend" ]; then + backend_kind="${backend%%|*}" + backend_path="${backend#*|}" + printf ' backend:\n' + printf ' kind: %s\n' "$backend_kind" + printf ' module: %s\n' "$module_name" + printf ' path: %s\n' "$backend_path" + if [ -f "${repo_root}/${backend_path}/readme.md" ]; then + printf ' docs: %s/readme.md\n' "$backend_path" + elif [ -f "${repo_root}/${backend_path}/README.md" ]; then + printf ' docs: %s/README.md\n' "$backend_path" + fi + return 0 + fi + done </dev/null || true + } | awk ' + { + sub(/github\.com[\/:]+/, "") + sub(/\.git$/, "") + if ($0 == "org/repo" || $0 == "test/repo") next + if ($0 != "") print + } + ' | awk '!seen[$0]++' +) +EOF +} + +dev_kit_manifest_version_value() { + local manifest_path="$1" + + [ -f "$manifest_path" ] || return 0 + + awk ' + /^version:[[:space:]]*/ { + value = $0 + sub(/^version:[[:space:]]*/, "", value) + gsub(/["'\''"]/, "", value) + gsub(/[[:space:]]+$/, "", value) + if (value != "") print value + exit + } + ' "$manifest_path" +} + +dev_kit_manifest_comment_repo_refs() { + local manifest_path="$1" + + dev_kit_github_repo_refs_in_file "$manifest_path" +} + +dev_kit_manifest_usage_paths() { + local repo_root="$1" + local manifest_rel="$2" + local scan_file="" + local scan_rel="" + + [ -n "$manifest_rel" ] || return 0 + + 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 + + if grep -Fq -- "$manifest_rel" "$scan_file" 2>/dev/null; then + printf '%s\n' "$scan_rel" + fi + done </dev/null) +EOF +} + +dev_kit_manifest_source_repo() { + local manifest_path="$1" + local comment_repo="" + local version_value="" + local version_repo="" + + comment_repo="$(dev_kit_manifest_comment_repo_refs "$manifest_path" | head -n 1)" + if [ -n "$comment_repo" ]; then + printf '%s\n' "$comment_repo" + return 0 + fi + + version_value="$(dev_kit_manifest_version_value "$manifest_path")" + if [ -n "$version_value" ]; then + version_repo="$(dev_kit_dep_version_repo_slug "$version_value" 2>/dev/null || true)" + if [ -n "$version_repo" ]; then + printf '%s\n' "$version_repo" + return 0 + fi + fi + + return 0 +} + +dev_kit_manifest_provenance_yaml() { + local repo_root="$1" + local manifest_rel="$2" + local manifest_path="${repo_root}/${manifest_rel}" + local declared_as="" + local source_repo="" + local evidence_yaml="" + local evidence_item="" + local usage_paths="" + local usage_path="" + local comment_repo="" + + declared_as="$(dev_kit_manifest_version_value "$manifest_path")" + source_repo="$(dev_kit_manifest_source_repo "$manifest_path")" + usage_paths="$(dev_kit_manifest_usage_paths "$repo_root" "$manifest_rel" | awk '!seen[$0]++')" + + [ -n "$declared_as" ] && printf ' declared_as: %s\n' "$declared_as" + [ -n "$source_repo" ] && printf ' source_repo: %s\n' "$source_repo" + + if [ -n "$usage_paths" ]; then + printf ' used_by:\n' + while IFS= read -r usage_path; do + [ -n "$usage_path" ] || continue + printf ' - %s\n' "$usage_path" + done </dev/null || true)" + [ -z "$manifest_kind" ] && manifest_kind="${manifest_meta%%|*}" + if [ -z "$manifest_description" ]; then + manifest_description="${manifest_meta#*|}" + [ "$manifest_description" = "$manifest_meta" ] && manifest_description="" + fi + fi + + printf ' - path: %s\n' "$manifest_rel" + [ -n "$manifest_kind" ] && printf ' kind: %s\n' "$manifest_kind" + [ -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" +} + +dev_kit_version_uri() { + printf '%s' 'udx.dev/dev.kit/v1' +} + +dev_kit_context_section_comment_block() { + local section_id="$1" + local title="" + local summary="" + local note="" + local emitted=0 + + title="$(dev_kit_context_section_field "$section_id" "title" 2>/dev/null || true)" + summary="$(dev_kit_context_section_field "$section_id" "summary" 2>/dev/null || true)" + + if [ -n "$title" ] || [ -n "$summary" ]; then + if [ -n "$title" ] && [ -n "$summary" ]; then + printf '# %s — %s\n' "$title" "$summary" + elif [ -n "$title" ]; then + printf '# %s\n' "$title" + else + printf '# %s\n' "$summary" + fi + emitted=1 + fi + + while IFS= read -r note; do + [ -n "$note" ] || continue + printf '# Note: %s\n' "$note" + emitted=1 + done </dev/null || true)" + if [ -n "$factor_ids" ]; then + printf '%s' "$factor_ids" + return 0 + fi + + printf '%s\n' documentation dependencies config pipeline } # Path of the canonical context file @@ -40,7 +348,8 @@ dev_kit_scaffold_gaps_json() { local first=1 printf '[\n' - for factor in documentation dependencies config pipeline; do + while IFS= read -r factor; do + [ -n "$factor" ] || continue status="$(dev_kit_repo_factor_status "$repo_root" "$factor")" [ "$status" = "missing" ] || [ "$status" = "partial" ] || continue local rule_id message @@ -52,7 +361,9 @@ dev_kit_scaffold_gaps_json() { "$status" \ "$(dev_kit_json_escape "$message")" first=0 - done + done </dev/null || true)" - [ -z "$profile" ] && profile="$(dev_kit_repo_primary_profile "$sibling_dir" 2>/dev/null || true)" fi - printf '%s\t%s\t%s\t%s' "$resolved" "$archetype" "$profile" "$description" + printf '%s\t%s\t%s' "$resolved" "$archetype" "$description" } # Read structured dependencies from context.yaml and emit JSON array. @@ -194,10 +503,10 @@ dev_kit_deps_json() { open = 1; ub_count = 0; in_ub = 0 next } - /^ type:/ { sub(/.*type:[[:space:]]*/, ""); printf ", \"type\": \"%s\"", json_esc($0); in_ub=0; next } + /^ kind:/ { sub(/.*kind:[[:space:]]*/, ""); printf ", \"kind\": \"%s\"", json_esc($0); in_ub=0; next } /^ resolved:/ { sub(/.*resolved:[[:space:]]*/, ""); printf ", \"resolved\": %s", $0; in_ub=0; next } /^ archetype:/ { sub(/.*archetype:[[:space:]]*/, ""); printf ", \"archetype\": \"%s\"", json_esc($0); in_ub=0; next } - /^ profile:/ { sub(/.*profile:[[:space:]]*/, ""); printf ", \"profile\": \"%s\"", json_esc($0); in_ub=0; next } + /^ declared_as:/ { sub(/.*declared_as:[[:space:]]*/, ""); printf ", \"declared_as\": \"%s\"", json_esc($0); in_ub=0; next } /^ source_repo:/ { sub(/.*source_repo:[[:space:]]*/, ""); printf ", \"source_repo\": \"%s\"", json_esc($0); in_ub=0; next } /^ description:/ { sub(/.*description:[[:space:]]*/, ""); printf ", \"description\": \"%s\"", json_esc($0); in_ub=0; next } /^ used_by:/ { in_ub = 1; next } @@ -216,29 +525,27 @@ dev_kit_context_yaml_write() { local context_path="${repo_root}/.rabbit/context.yaml" mkdir -p "${repo_root}/.rabbit" 2>/dev/null || true - local _repo _arch _arch_desc _profile + local _repo _arch _arch_desc _repo="$(dev_kit_repo_name "$repo_root")" _arch="$(dev_kit_repo_primary_archetype "$repo_root")" _arch_desc="$(dev_kit_archetype_description "$_arch")" - _profile="$(dev_kit_repo_primary_profile "$repo_root")" { printf '# Generated by dev.kit repo — do not edit manually.\n' printf '# Run `dev.kit repo` to refresh.\n' printf 'kind: repoContext\n' - printf 'version: udx.io/dev.kit/v1\n' + printf 'version: %s\n' "$(dev_kit_version_uri)" printf 'generated: %s\n\n' "$(date +%Y-%m-%d)" printf 'repo:\n' printf ' name: %s\n' "$_repo" printf ' archetype: %s\n' "$_arch" - printf ' profile: %s\n' "$_profile" printf '\n' - # Refs — only files/dirs that must be read directly; metadata is inlined below local _refs _refs="$(dev_kit_repo_priority_refs "$repo_root")" if [ -n "$_refs" ]; then + dev_kit_context_section_comment_block "refs" printf 'refs:\n' printf '%s\n' "$_refs" | while IFS= read -r ref; do [ -n "$ref" ] || continue @@ -247,92 +554,66 @@ dev_kit_context_yaml_write() { printf '\n' fi - # Commands - local _ep_json _verify _build _run + local _ep_json _verify _build _run _verify_source _build_source _run_source _command_kind _ep_json="$(dev_kit_repo_entrypoints_json "$repo_root")" _verify="$(printf '%s' "$_ep_json" | jq -r '.verify // empty' 2>/dev/null)" _build="$(printf '%s' "$_ep_json" | jq -r '.build // empty' 2>/dev/null)" _run="$(printf '%s' "$_ep_json" | jq -r '.run // empty' 2>/dev/null)" + _verify_source="$(dev_kit_repo_entrypoint_source "$repo_root" "verification" 2>/dev/null || true)" + _build_source="$(dev_kit_repo_entrypoint_source "$repo_root" "build_release_run" 2>/dev/null || true)" + _run_source="$(dev_kit_repo_entrypoint_source "$repo_root" "runtime" 2>/dev/null || true)" if [ -n "$_verify" ] || [ -n "$_build" ] || [ -n "$_run" ]; then + dev_kit_context_section_comment_block "commands" printf 'commands:\n' - [ -n "$_verify" ] && printf ' verify: %s\n' "$_verify" - [ -n "$_build" ] && printf ' build: %s\n' "$_build" - [ -n "$_run" ] && printf ' run: %s\n' "$_run" + while IFS= read -r _command_kind; do + [ -n "$_command_kind" ] || continue + case "$_command_kind" in + verify) + if [ -n "$_verify" ]; then + printf ' verify:\n' + printf ' run: %s\n' "$_verify" + [ -n "$_verify_source" ] && printf ' source: %s\n' "$_verify_source" + fi + ;; + build) + if [ -n "$_build" ]; then + printf ' build:\n' + printf ' run: %s\n' "$_build" + [ -n "$_build_source" ] && printf ' source: %s\n' "$_build_source" + fi + ;; + run) + if [ -n "$_run" ]; then + printf ' run:\n' + printf ' run: %s\n' "$_run" + [ -n "$_run_source" ] && printf ' source: %s\n' "$_run_source" + fi + ;; + esac + done </dev/null 2>&1 && dev_kit_sync_has_git_repo "$repo_root"; then - local _gh_origin_url _gh_owner_repo="" - _gh_origin_url="$(git -C "$repo_root" remote get-url origin 2>/dev/null || true)" - if [[ "$_gh_origin_url" =~ github\.com[:/]([^/]+/[^/]+)(\.git)?$ ]]; then - _gh_owner_repo="${BASH_REMATCH[1]}" - _gh_owner_repo="${_gh_owner_repo%.git}" - fi - if [ -n "$_gh_owner_repo" ]; then - # jq yaml_safe: escape backslashes, strip CR/LF/tabs, escape inner quotes - local _jq_safe='def yaml_safe: gsub("\\\\"; "\\\\\\\\") | gsub("\\r"; " ") | gsub("\\n"; " ") | gsub("\\t"; " ") | gsub("\""; "\\\"");' - - # Open issues (up to 10, most recent) - local _gh_issues - _gh_issues="$(gh api "repos/${_gh_owner_repo}/issues?state=open&per_page=10&sort=updated&direction=desc" 2>/dev/null | jq -r "${_jq_safe}"' - .[]? | select(.pull_request == null) | - " - \"#\(.number) \(.title | yaml_safe)\(if (.labels | length) > 0 then " [" + ([.labels[].name | yaml_safe] | join(", ")) + "]" else "" end)\"" - ' 2>/dev/null || true)" - - # Recent merged PRs (last 5) - local _gh_prs - _gh_prs="$(gh api "repos/${_gh_owner_repo}/pulls?state=closed&sort=updated&direction=desc&per_page=10" 2>/dev/null | jq -r "${_jq_safe}"' - [.[]? | select(.merged_at != null)] | sort_by(.merged_at) | reverse | .[:5][] | - " - \"#\(.number) \(.title | yaml_safe)\"" - ' 2>/dev/null || true)" - - # Open PRs - local _gh_open_prs - _gh_open_prs="$(gh api "repos/${_gh_owner_repo}/pulls?state=open&per_page=5&sort=updated&direction=desc" 2>/dev/null | jq -r "${_jq_safe}"' - .[]? | " - \"#\(.number) \(.title | yaml_safe)\(if .draft then " (draft)" else "" end)\"" - ' 2>/dev/null || true)" - - # Security alerts (Dependabot) - local _gh_alerts - _gh_alerts="$(gh api "repos/${_gh_owner_repo}/dependabot/alerts?state=open&per_page=5" 2>/dev/null | jq -r "${_jq_safe}"' - .[]? | " - \"\(.security_advisory.severity | yaml_safe): \((.security_advisory.summary // .dependency.package.name) | yaml_safe)\"" - ' 2>/dev/null || true)" - - if [ -n "$_gh_issues" ] || [ -n "$_gh_prs" ] || [ -n "$_gh_open_prs" ] || [ -n "$_gh_alerts" ]; then - printf '# GitHub context — development signals from repo history\n' - printf 'github:\n' - printf ' repo: %s\n' "$_gh_owner_repo" - [ -n "$_gh_issues" ] && printf ' open_issues:\n%s\n' "$_gh_issues" - [ -n "$_gh_prs" ] && printf ' recent_prs:\n%s\n' "$_gh_prs" - [ -n "$_gh_open_prs" ] && printf ' open_prs:\n%s\n' "$_gh_open_prs" - [ -n "$_gh_alerts" ] && printf ' security_alerts:\n%s\n' "$_gh_alerts" - printf '\n' - fi - fi - fi - - # Factor gaps local _gaps_yaml _gaps_yaml="$(dev_kit_repo_factor_summary_json "$repo_root" | jq -r ' to_entries[] | select(.value.status == "missing" or .value.status == "partial") | - " - " + .key + " (" + .value.status + ")" + [ + " - factor: " + .key, + " status: " + .value.status, + (if (.value.message // "") != "" then " message: " + .value.message else empty end), + (if ((.value.evidence // []) | length) > 0 then " evidence:" else empty end), + ((.value.evidence // [])[]? | " - " + .) + ] | .[] ' 2>/dev/null)" if [ -n "$_gaps_yaml" ]; then - printf '# Gaps — factors that are missing or partial\n' + dev_kit_context_section_comment_block "gaps" printf 'gaps:\n' printf '%s\n\n' "$_gaps_yaml" fi - # ── External dependencies — structured cross-repo tracing ────────────── - # Collect (dep_id|type|source_file) triples from multiple sources, - # then group by dep and resolve same-org repos for rich metadata. - # All scan locations are config-driven from detection-signals.yaml. local _current_org="" if dev_kit_sync_has_git_repo "$repo_root"; then _current_org="$(dev_kit_repo_org_from_remote "$repo_root")" @@ -401,7 +682,7 @@ EOF $(find "${repo_root}/${_dep_dir}" -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null) EOF done <> "$_dep_triples_file" done <> "$_dep_triples_file" done <= 3 && p[1] ~ /\./) { - repo=p[2] - if (n >= 4) printf "%s|versioned config (%s)|%s\n", repo, p[3], src - else printf "%s|versioned config|%s\n", repo, src - } - exit - } - ' "$_vf" >> "$_dep_triples_file" + local _vf_version _vf_module + _vf_version="$(dev_kit_manifest_version_value "$_vf")" + if [ -n "$_vf_version" ]; then + _vf_module="$(printf '%s' "$_vf_version" | cut -d/ -f3)" + if [ -n "$_vf_module" ] && [ "$_vf_module" != "$_vf_version" ]; then + printf '%s|versioned config (%s)|%s\n' "$_vf_version" "$_vf_module" "$_vf_rel" >> "$_dep_triples_file" + else + printf '%s|versioned config|%s\n' "$_vf_version" "$_vf_rel" >> "$_dep_triples_file" + fi + fi done </dev/null) EOF done <> "$_dep_triples_file" + else + printf '%s|manifest contract|%s\n' "$_vf_version" "$_vf_rel" >> "$_dep_triples_file" + fi + fi + done </dev/null | sort) +EOF + done <> "$_dep_triples_file" + else + printf '%s|manifest contract|%s\n' "$_vf_version" "$_vf_rel" >> "$_dep_triples_file" + fi + fi + done </dev/null | \ - awk -v src="$_uf_rel" -v self_repo="$_repo" '{ - sub(/github\.com[\/:]+/, "") - sub(/\.git$/, "") - # Skip self-references and file-path-like matches - if ($0 == self_repo || $0 ~ /\.(md|yml|yaml|json|sh|txt)$/) next - printf "%s|github reference|%s\n", $0, src - }' >> "$_dep_triples_file" + while IFS= read -r _repo_ref; do + [ -n "$_repo_ref" ] || continue + [ "$_repo_ref" = "$_repo" ] && continue + case "$_repo_ref" in + *.md|*.yml|*.yaml|*.json|*.sh|*.txt) continue ;; + esac + printf '%s|github reference|%s\n' "$_repo_ref" "$_uf_rel" >> "$_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 + _dep_norm_file="$(mktemp "${TMPDIR:-/tmp}/dev-kit-deps-norm.XXXXXX")" + + while IFS='|' read -r _dep_id _dep_kind _dep_src; do + [ -n "${_dep_id:-}" ] || continue + + local _dep_key="$_dep_id" + local _dep_declared_as="" + + case "$_dep_kind" in + versioned\ config*|manifest\ contract*) + _dep_declared_as="$_dep_id" + _dep_key="$(dev_kit_dep_version_repo_slug "$_dep_id" 2>/dev/null || true)" + [ -n "$_dep_key" ] || _dep_key="$_dep_id" + ;; + esac + + printf '%s|%s|%s|%s\n' "$_dep_key" "$_dep_kind" "$_dep_src" "$_dep_declared_as" >> "$_dep_norm_file" + done < "$_dep_triples_file" + # ── Group triples by dep, resolve same-org, emit structured YAML ───── - if [ -s "$_dep_triples_file" ]; then - printf '# External dependencies — cross-repo and upstream references\n' - printf '# Trace these to find infrastructure, deployment, and build logic outside this repo.\n' - printf '# Same-org deps are resolved with metadata. External deps listed for agent reference.\n' + if [ -s "$_dep_norm_file" ]; then + dev_kit_context_section_comment_block "dependencies" printf 'dependencies:\n' # Get unique dep identifiers in discovery order - awk -F'|' '!seen[$1]++ {print $1}' "$_dep_triples_file" | while IFS= read -r _udep; do + awk -F'|' '!seen[$1]++ {print $1}' "$_dep_norm_file" | while IFS= read -r _udep; do [ -n "$_udep" ] || continue # Primary type (first seen) local _dep_type - _dep_type="$(awk -F'|' -v k="$_udep" '$1 == k {print $2; exit}' "$_dep_triples_file")" + _dep_type="$(awk -F'|' -v k="$_udep" '$1 == k {print $2; exit}' "$_dep_norm_file")" # Unique used_by files local _used_by - _used_by="$(awk -F'|' -v k="$_udep" '$1 == k {print $3}' "$_dep_triples_file" | sort -u)" + _used_by="$(awk -F'|' -v k="$_udep" '$1 == k {print $3}' "$_dep_norm_file" | sort -u)" - printf ' - repo: %s\n' "$_udep" - printf ' type: %s\n' "$_dep_type" + local _declared_as="" + _declared_as="$(awk -F'|' -v k="$_udep" '$1 == k && $4 != "" {print $4; exit}' "$_dep_norm_file")" + + local _display_repo="$_udep" + + printf ' - repo: %s\n' "$_display_repo" + printf ' kind: %s\n' "$_dep_type" # Resolve same-org dependencies local _resolve_target="" - if dev_kit_dep_is_same_org "$_udep" "$_current_org"; then - _resolve_target="$_udep" + if dev_kit_dep_is_same_org "$_display_repo" "$_current_org"; then + _resolve_target="$_display_repo" else # For Docker images: try matching image name to a same-org repo case "$_dep_type" in *image*|*docker*) - _resolve_target="$(dev_kit_dep_match_image_to_org "$_udep" "$_current_org" "$repo_root" "$_gh_auth_state")" + _resolve_target="$(dev_kit_dep_match_image_to_org "$_display_repo" "$_current_org" "$repo_root" "$_gh_auth_state")" ;; esac fi if [ -n "$_resolve_target" ]; then - local _resolve_out _r_resolved _r_arch _r_profile _r_desc + local _resolve_out _r_resolved _r_arch _r_desc _resolve_out="$(dev_kit_dep_resolve "$_resolve_target" "$repo_root" "$_gh_auth_state" "$force")" - IFS=$'\t' read -r _r_resolved _r_arch _r_profile _r_desc <<< "$_resolve_out" + IFS=$'\t' read -r _r_resolved _r_arch _r_desc <<< "$_resolve_out" printf ' resolved: %s\n' "$_r_resolved" - [ -n "$_resolve_target" ] && [ "$_resolve_target" != "$_udep" ] && printf ' source_repo: %s\n' "$_resolve_target" + [ -n "$_declared_as" ] && printf ' declared_as: %s\n' "$_declared_as" + [ -n "$_resolve_target" ] && [ "$_resolve_target" != "$_display_repo" ] && printf ' source_repo: %s\n' "$_resolve_target" [ -n "$_r_arch" ] && printf ' archetype: %s\n' "$_r_arch" - [ -n "$_r_profile" ] && printf ' profile: %s\n' "$_r_profile" [ -n "$_r_desc" ] && printf ' description: %s\n' "$_r_desc" else printf ' resolved: false\n' + [ -n "$_declared_as" ] && printf ' declared_as: %s\n' "$_declared_as" fi # Emit used_by @@ -549,78 +925,48 @@ EOF fi rm -f "$_dep_triples_file" + rm -f "$_dep_norm_file" - # Config manifests — traceable workflow and tooling dependencies. - # Sources read from detection-signals.yaml: manifest_workflow_dirs, manifest_root_files. local _manifests_yaml="" - local _yaml_file _yaml_rel _yaml_kind _yaml_desc - # src/configs — dev.kit's own config catalog (only present in dev.kit repos) - if [ -d "${repo_root}/src/configs" ]; then - while IFS='|' read -r _yaml_rel _yaml_kind; do - [ -n "$_yaml_rel" ] || continue - _yaml_desc="$(dev_kit_manifest_kind_description "$_yaml_kind")" - if [ -n "$_yaml_desc" ]; then - _manifests_yaml="${_manifests_yaml} - ${_yaml_rel} — ${_yaml_desc}\n" - elif [ -n "$_yaml_kind" ]; then - _manifests_yaml="${_manifests_yaml} - ${_yaml_rel} (${_yaml_kind})\n" - else - _manifests_yaml="${_manifests_yaml} - ${_yaml_rel}\n" - fi + 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 | while IFS= read -r f; do - [ -f "$f" ] || continue - rel="${f#"${repo_root}/"}" - kind="$(awk '/^kind:/ { sub(/^kind:[[:space:]]*/, ""); print; exit }' "$f")" - printf '%s|%s\n' "$rel" "$kind" -done) +$(find "${repo_root}/${_manifest_dir}" -maxdepth 1 \( -name '*.yaml' -o -name '*.yml' \) -print 2>/dev/null | sort) +EOF + done </dev/null | sort) EOF done </dev/null | sort -r | head -3) -EOF - [ "$first_lesson" -eq 0 ] && printf '\n' - fi - } > "$context_path" printf "%s" "$context_path" diff --git a/lib/modules/repo_signals.sh b/lib/modules/repo_signals.sh index a5bd73c..1f9f43b 100644 --- a/lib/modules/repo_signals.sh +++ b/lib/modules/repo_signals.sh @@ -25,12 +25,6 @@ dev_kit_detection_list() { dev_kit_yaml_config_list "$(dev_kit_detection_signals_path)" "$list_name" } -dev_kit_detection_scalar() { - local key="$1" - - dev_kit_yaml_config_scalar "$(dev_kit_detection_signals_path)" "$key" -} - dev_kit_repo_name() { basename "${1:-$(pwd)}" } @@ -169,19 +163,6 @@ EOF printf "%s" "$DEV_KIT_REPO_MARKERS_CACHE_VALUE" } -dev_kit_repo_markers_text() { - local repo_dir="${1:-$(pwd)}" - local markers="" - - markers="$(dev_kit_repo_marker_lines "$repo_dir")" - if [ -z "$markers" ]; then - printf "%s" "none" - return 0 - fi - - printf "%s" "$markers" | dev_kit_lines_to_csv -} - dev_kit_repo_markers_json() { local repo_dir="${1:-$(pwd)}" local markers="" @@ -236,6 +217,9 @@ dev_kit_repo_has_glob() { local repo_dir="$1" local pattern="$2" + pattern="${pattern#\"}" + pattern="${pattern%\"}" + if [[ "$pattern" == */* ]]; then dev_kit_repo_find "$repo_dir" -type f -path "$repo_dir/$pattern" -print -quit | grep -q . return $? @@ -251,6 +235,8 @@ dev_kit_repo_find_from_glob_list() { while IFS= read -r pattern; do [ -n "$pattern" ] || continue + pattern="${pattern#\"}" + pattern="${pattern%\"}" if [[ "$pattern" == */* ]]; then dev_kit_repo_find "$repo_dir" -type f -path "$repo_dir/$pattern" -print else @@ -267,6 +253,25 @@ dev_kit_repo_markdown_files() { dev_kit_repo_find_from_glob_list "$repo_dir" "markdown_file_globs" | sort -u } +dev_kit_repo_command_doc_files() { + local repo_dir="$1" + local ref="" + + while IFS= read -r ref; do + [ -n "$ref" ] || continue + ref="${ref#./}" + [ -f "$repo_dir/$ref" ] || continue + case "$ref" in + AGENTS.md|CLAUDE.md|.rabbit/*) continue ;; + esac + case "$ref" in + *.md|*.markdown) printf '%s/%s\n' "$repo_dir" "$ref" ;; + esac + 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" } +dev_kit_repo_has_composer_build_script() { + dev_kit_repo_manifest_has_script "$1" "composer.json" "build" +} + dev_kit_repo_documented_env_var() { local repo_dir="$1" + + [ -n "$(dev_kit_repo_documented_env_var_sources "$repo_dir")" ] +} + +dev_kit_repo_documented_env_var_sources() { + local repo_dir="$1" local doc_file="" local regex="" @@ -452,13 +526,11 @@ dev_kit_repo_documented_env_var() { while IFS= read -r doc_file; do [ -n "$doc_file" ] || continue if dev_kit_repo_pattern_in_file "$doc_file" "$regex"; then - return 0 + printf '%s\n' "${doc_file#"${repo_dir}/"}" fi done </dev/null || true)" + [ -n "$result" ] || return 1 + printf "%s" "$(printf '%s' "$result" | cut -d'|' -f3)" +} + dev_kit_repo_workflow_steps() { local repo_dir="$1" local verify_cmd="" @@ -39,8 +49,6 @@ dev_kit_repo_workflow_steps() { printf "run|Use the canonical runtime command instead of ad hoc startup paths|%s\n" "$run_cmd" fi - printf "learn|Review lessons-learned and follow-up outputs after changes stabilize|dev.kit learn\n" - } diff --git a/lib/modules/utils.sh b/lib/modules/utils.sh index 7800adf..83e1d6e 100644 --- a/lib/modules/utils.sh +++ b/lib/modules/utils.sh @@ -140,29 +140,6 @@ dev_kit_yaml_config_list() { ' "$file_path" } -dev_kit_yaml_config_scalar() { - local file_path="$1" - local key="$2" - - awk -v key="$key" ' - $1 == "config:" { - in_config = 1 - next - } - - in_config && $0 ~ /^ [A-Za-z0-9_-]+:/ { - current = $1 - sub(":", "", current) - if (current != key) { - next - } - sub(/^[[:space:]]*[A-Za-z0-9_-]+:[[:space:]]*/, "", $0) - print - exit - } - ' "$file_path" -} - dev_kit_yaml_mapping_list() { local file_path="$1" local section="$2" diff --git a/package-lock.json b/package-lock.json index c5fcf6d..d71969c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@udx/dev-kit", - "version": "0.6.0", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@udx/dev-kit", - "version": "0.6.0", + "version": "0.8.0", "license": "MIT", "bin": { "dev-kit": "bin/dev-kit", diff --git a/src/configs/archetype-signals.yaml b/src/configs/archetype-signals.yaml deleted file mode 100644 index ababf38..0000000 --- a/src/configs/archetype-signals.yaml +++ /dev/null @@ -1,35 +0,0 @@ -kind: archetypeSignals -version: udx.io/dev.kit/v1 - -config: - wordpress_files: - - wp-config.php - wordpress_dirs: - - wp-content - - wp-content/plugins - - wp-content/themes - - wp-content/mu-plugins - kubernetes_files: - - Chart.yaml - - helmfile.yaml - kubernetes_globs: - - charts/*/Chart.yaml - - k8s/*.yaml - - k8s/*.yml - - k8s/**/*.yaml - - k8s/**/*.yml - kubernetes_dirs: - - k8s - - charts - - helm - terraform_files: - - main.tf - - variables.tf - terraform_dirs: - - terraform - terraform_globs: - - terraform/*.tf - - terraform/**/*.tf - - "*.tf" - workflow_primary_dirs: - - .github/workflows diff --git a/src/configs/archetype-rules.yaml b/src/configs/archetypes.yaml similarity index 58% rename from src/configs/archetype-rules.yaml rename to src/configs/archetypes.yaml index 6f7fb42..075f346 100644 --- a/src/configs/archetype-rules.yaml +++ b/src/configs/archetypes.yaml @@ -1,5 +1,6 @@ -kind: archetypeRules -version: udx.io/dev.kit/v1 +kind: repoArchetypes +version: udx.dev/dev.kit/v1 +description: Repo archetype definitions and matching rules config: archetypes: @@ -17,28 +18,40 @@ config: required: - workflow:github - repo:workflow-primary - infra-pipeline: - description: Infrastructure-as-code pipeline — Terraform or Kubernetes manifests managed through a CI/CD workflow + package-cli: + description: Library or CLI tool — reusable code published as a package or standalone command-line tool required: - - workflow:github - - deploy:terraform + - package:cli supporting: - - repo:workflow-primary - - deploy:kubernetes-manifests - - lifecycle:deploy - - runtime:container - runtime-image: - description: Container image — a Dockerfile-based service built and published as a container image + - package:node + - package:composer + - lifecycle:build + docker: + description: Dockerized application or runtime — Dockerfile is the primary build and artifact boundary required: - runtime:container supporting: - lifecycle:build - lifecycle:runtime - lifecycle:deploy - library-cli: - description: Library or CLI tool — reusable code published as a package or standalone command-line tool - # No required facets — catch-all for code repos without a stronger match - supporting: - package:node - package:composer + app: + description: Application bundle — source builds into an app artifact such as a Next.js bundle + 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 diff --git a/src/configs/audit-rules.yaml b/src/configs/audit-rules.yaml index d6efdfc..42edef9 100644 --- a/src/configs/audit-rules.yaml +++ b/src/configs/audit-rules.yaml @@ -1,5 +1,6 @@ kind: auditRules -version: udx.io/dev.kit/v1 +version: udx.dev/dev.kit/v1 +description: Gap messages and guidance for missing or partial repo factors config: rules: @@ -18,11 +19,11 @@ config: - id: partial-config-contract factor: config status: partial - message: Config signals exist, but the environment contract is only partially documented. Add an example env file or clearer config documentation. + 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. - id: missing-config-contract factor: config status: missing - message: Externalize configuration and document the environment contract so the repo can move cleanly across environments. + message: No explicit configuration contract was detected. Add runtime config docs or an env example when the repo requires environment configuration. - id: partial-pipeline factor: pipeline status: partial diff --git a/src/configs/context-config.yaml b/src/configs/context-config.yaml index 0a4ebc2..fadc072 100644 --- a/src/configs/context-config.yaml +++ b/src/configs/context-config.yaml @@ -1,7 +1,75 @@ kind: contextConfig -version: udx.io/dev.kit/v1 +version: udx.dev/dev.kit/v1 +description: Repo root markers, direct-read refs, and documentation priority order config: + context_sections: + refs: + 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. + - Prefer README, focused docs, workflows, manifests, and explicit operational files. + - Exclude broad implementation directories unless they are the contract themselves. + source_lists: + - priority_paths + commands: + title: Commands + summary: Canonical repo entrypoints detected from strong repo signals. + notes: + - Prefer declared make targets and package scripts before regex matches in docs. + - Emit only commands that can be traced to a concrete source. + - Record the source path so the command can be reviewed and corrected. + command_kinds: + - verify + - build + - run + prefer_sources: + - make_targets + - package_scripts + - documented_commands + gaps: + title: Gaps + summary: Factors that are missing or only partially supported by current repo signals. + notes: + - Base the result on explicit factor rules, not free-form judgment. + - Include message and evidence so the status can be reviewed. + - Prefer traceable refs and missing signals over vague advice. + factor_ids: + - documentation + - dependencies + - config + - pipeline + dependencies: + title: Dependencies + summary: External repos, actions, images, or versioned manifests this repo relies on. + notes: + - Capture behavior defined outside the current checkout. + - 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: + - dependency_trace_container_files + compose_files: + - 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. + notes: + - Include manifests that materially shape repo behavior or agent understanding. + - Prefer structured kind and description metadata from the manifest itself. + - Include eval and workflow manifests, not only deploy manifests. + workflow_dirs: + - manifest_workflow_dirs + config_dirs: + - manifest_config_dirs + root_files: + - manifest_root_files repo_root_files: - README.md - readme.md @@ -15,57 +83,28 @@ config: - .git - .github - .rabbit - repo_note_files: - - CLAUDE.md - - AGENTS.md - - todo.md - - TODO.md - - context.md - - CONTEXT.md - - refs.md - - REFS.md priority_paths: - - README.md - - readme.md - - docs/installation.md - - docs/context.md - - docs/agents.md - - docs/integration.md - - docs/lessons-learned.md - - docs - - .rabbit + - README* + - readme* + - changes.md + - CHANGELOG.md + - "*.yaml" + - "*.yml" - .github/workflows - Makefile - - package.json - - composer.json - Dockerfile - - docker-compose.yml - - docker-compose.yaml - - deploy.yml - - deploy.yaml - wp-config.php - - Chart.yaml - - helmfile.yaml - - k8s - - charts - - terraform - - lib - - src - - scripts - - tests + - examples + - docs repo_doc_paths: - .rabbit/context.yaml - CLAUDE.md - AGENTS.md - - README.md - - readme.md - - docs/installation.md - - docs/context.md - - docs/agents.md - - docs/integration.md - - docs/lessons-learned.md + - README* + - readme* + - docs/*.md + - docs/*/*.md - docs - - .rabbit/dev.kit marker_groups: git_root: prefix: git @@ -132,11 +171,6 @@ config: kind: dir paths: - .rabbit - infra_configs: - prefix: infra - kind: dir - paths: - - .rabbit/infra_configs docs_dir: prefix: docs kind: dir diff --git a/src/configs/detection-patterns.yaml b/src/configs/detection-patterns.yaml index 1e0449d..843f307 100644 --- a/src/configs/detection-patterns.yaml +++ b/src/configs/detection-patterns.yaml @@ -1,11 +1,14 @@ kind: detectionPatterns -version: udx.io/dev.kit/v1 +version: udx.dev/dev.kit/v1 +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) build: (npm run build|pnpm build|yarn build|bun run build|make build|docker build[^`]*|go build|cargo build|composer build) - run: (npm start|pnpm start|yarn start|bun run start|make run|docker compose up[^`]*|docker run[^`]*|php -S [^`]*|python -m [^`[:space:]]+|node [^`[:space:]]+) + 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,}\}) workflow_contract: (workflow_call:|workflow_dispatch:|inputs:|outputs:) documentation_sections: (##[[:space:]]+(Quick Start|Usage|Development|Examples|Resources|Docs|Documentation)) + yaml_api_version: ^[[:space:]]*apiVersion:[[:space:]]*[^[:space:]] + yaml_kind: ^[[:space:]]*kind:[[:space:]]*[^[:space:]] diff --git a/src/configs/detection-signals.yaml b/src/configs/detection-signals.yaml index 11c549e..1e59e7a 100644 --- a/src/configs/detection-signals.yaml +++ b/src/configs/detection-signals.yaml @@ -1,5 +1,6 @@ kind: detectionSignals -version: udx.io/dev.kit/v1 +version: udx.dev/dev.kit/v1 +description: File, directory, and glob signals for factor and dependency detection config: documentation_files: @@ -25,10 +26,8 @@ config: - .venv - venv - .terraform - - dist - build - coverage - - .next - .nuxt - .turbo - .cache @@ -40,12 +39,17 @@ config: - Makefile - makefile - GNUmakefile - node_files: - - package.json - php_files: - - composer.json - - phpunit.xml - - phpunit.xml.dist + 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 @@ -66,6 +70,7 @@ config: - .env.example - .env.sample - .env.template + config_runtime_files: - docker-compose.yml - docker-compose.yaml - deploy.yml @@ -73,12 +78,6 @@ config: runtime_files: - Dockerfile - Procfile - shell_dirs: - - bin - - scripts - - lib - shell_globs: - - *.sh test_dirs: - tests - test @@ -101,24 +100,24 @@ config: - terraform - k8s - cd - verification_files: - - phpunit.xml - - phpunit.xml.dist - verification_globs: - - *.bats - - *test*.sh + terraform_files: + - main.tf + - variables.tf + terraform_dirs: + - terraform + terraform_globs: + - terraform/*.tf + - terraform/**/*.tf + - "*.tf" + yaml_manifest_globs: + - "*.yaml" + - "*.yml" # External dependency tracing — files and dirs to scan for cross-repo references. # Shell code reads these lists and applies pattern-specific extraction. dependency_trace_workflow_dirs: - .github/workflows dependency_trace_container_files: - Dockerfile - dependency_trace_deploy_files: - - deploy.yml - - deploy.yaml - dependency_trace_infra_dirs: - - terraform - - k8s # 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 @@ -139,6 +138,14 @@ config: # Listed as config manifests in context.yaml output. manifest_workflow_dirs: - .github/workflows + manifest_config_dirs: + - src/configs + - configs + - cd/configs + manifest_backend_module_dirs: + - cd/terraform/modules + - terraform/modules + - modules manifest_root_files: - deploy.yml - deploy.yaml @@ -146,3 +153,4 @@ config: - docker-compose.yaml - Chart.yaml - helmfile.yaml + - evals/promptfooconfig.yaml diff --git a/src/configs/development-practices.yaml b/src/configs/development-practices.yaml deleted file mode 100644 index 183e57a..0000000 --- a/src/configs/development-practices.yaml +++ /dev/null @@ -1,47 +0,0 @@ -kind: developmentPractices -version: udx.io/dev.kit/v1 - -config: - practices: - - id: context-driven-engineering - message: Keep the repository as the primary source of truth so context-driven engineering comes from repo contracts, docs, tests, config, and repo-native notes instead of agent memory. - - id: repo-centric - message: Prefer repo-centric mechanisms that discover workflows, tools, formats, and refs dynamically instead of hardcoding per-agent assumptions. - - id: self-contained - message: Keep markdown, yaml, diagrams, tests, and command contracts self-contained in the repo so local and remote UDX workflows stay aligned. - - id: strict-agent-boundary - message: Keep deterministic workflow logic in repo config and scripts, and reserve AI agents for reading that contract, generating grounded summaries, and handling non-deterministic judgment without inventing hidden rules. - - id: context-over-memory - message: Operate from repo-declared context at all times. Do not carry assumptions across sessions or rely on prompt history when the repo contract is available on disk. - - id: github-history-primary - message: After loading the repo contract, prefer current GitHub issues, pull requests, review state, and commit history as the primary dynamic source for agent decisions. Use repo workflow and practice catalogs as fallback defaults when live GitHub signal is missing or thin. - - id: github-style-reuse - message: Reuse the repo's current GitHub patterns for branch names, PR titles and descriptions, issue writeups, and follow-up comments instead of inventing a new style for each change. - - id: manifests-before-code - message: When understanding behavior, read the YAML manifest that defines it before reading the code that implements it. Manifests are the interface — code is the mechanism. - - id: reuse-over-invention - message: Check existing org patterns, configs, workflows, and templates before creating new ones. Reuse declared patterns instead of inventing alternatives. - - id: workflow-first-verification - message: Detect the repo's canonical verification surface from context, but prefer GitHub workflow executions and monitor their outcomes when the repo already has CI coverage. Use local verification for quick scoped debugging, missing CI coverage, or reproducing workflow failures. - - id: bot-feedback-loop - message: After a PR exists, read automated review feedback, fix actionable findings, reply to false positives, resolve threads, and repeat until workflow state and bot feedback are clean. - - id: delivery-chain-traceability - message: Keep the delivery chain explicit — branch, PR, and issue must be connected before close-out. Do not treat any step as done until the link to the next step is visible. - - id: structured-outcome-reporting - message: Report outcomes with exact URLs, versions, findings deltas, and next steps so follow-up can be reused by humans and agents without drift. - - id: docs-first-alignment - message: Use README, docs, and tests as the first alignment surface before broad refactors. Read the declared workflow before changing the implementation. - - id: standard-repo-first - message: Do not require custom repo files for dev.kit to work. Prefer standard engineering signals such as README, docs, tests, manifests, workflows, and deployment config, with dev.kit-owned continuity treated as optional acceleration only. - - id: config-over-code - message: Express repo rules in YAML manifests first, then keep shell glue thin and composable. Policy belongs in config, not buried in imperative scripts. - - id: legacy-reduction - message: When a new direction is accepted, archive or delete conflicting old modules and configs instead of carrying both models forward. - - id: verification-scope - message: If local verification is needed, run the smallest local check that proves the current change. Prefer broader or slower coverage in GitHub workflows, monitor the workflow run, and call the tradeoff out explicitly. - - id: incremental-development - message: Make sure to develop and test incrementally, so it is easier to detect problems early and build on verified behavior. - - id: scoped-execution - message: Make sure to protect development executions with scoped and limited tasks, so failures are easier to isolate and blast radius stays low. - - id: cross-repo-issue-context - message: Use the linked GitHub issue as the cross-repo context root. When work spans multiple repos, the issue URL anchors scope, acceptance criteria, and follow-up across all of them. diff --git a/src/configs/development-workflows.yaml b/src/configs/development-workflows.yaml deleted file mode 100644 index 10ee35d..0000000 --- a/src/configs/development-workflows.yaml +++ /dev/null @@ -1,138 +0,0 @@ -kind: developmentWorkflows -version: udx.io/dev.kit/v1 - -config: - defaults: - workflow: action-git - behavior: evaluation-only - hooks_dir: .githooks - text_max_next_steps: 4 - branch_roles: - base: base - feature: feature - base_branch_names: - - latest - - main - - master - - development - - staging - - trunk - workflows: - action-git: - name: dev.action git - description: Evaluate local git workflow state without changing branches, remotes, or pull requests - capabilities: - required_commands: - - git - optional_commands: - - gh - steps: - - id: context_refresh - label: Refresh repo context - check: context_refresh - optional: true - note: > - Run `dev.kit` → `dev.kit repo` → `dev.kit agent` before starting work. - Each command guides to the next required step. This resyncs repo context, - environment state, and the AGENTS.md execution contract. A current - context.yaml is the source of truth for refs, commands, gaps, and lessons. - Do not rely on ad hoc prompt memory when the repo contract can be read - from disk. When GitHub context is available, prefer current issues, - pull requests, review state, and commit history over stale memory or - generic workflow defaults. - - id: issue_scope - label: Read linked GitHub issue and confirm scope - check: issue_scope - optional: true - note: > - If a GitHub issue URL is available, read the full body and comments, - confirm the repo matches the issue scope, and map acceptance criteria - before writing any code. Use the issue URL as the cross-repo context root. - - id: github_pattern_reuse - label: Reuse GitHub repo patterns - check: github_pattern_reuse - optional: true - note: > - Before creating a branch, PR, issue, or status update, inspect the - repo's recent GitHub history and reuse its established naming and - writing patterns. Treat existing branch names, PR titles and bodies, - issue templates, and close-out comments as the default style guide. - - id: worktree_status - label: Inspect git status - check: git_status - - id: change_analysis - label: Analyze local changes - check: change_analysis - - id: branch_analysis - label: Analyze branch state - check: branch_analysis - - id: logical_commits - label: Group logical commits - check: logical_commits - - id: release_metadata - label: Bump version and changelog if supported - check: release_metadata - - id: branch_prepare - label: Create or validate feature branch - check: branch_prepare - - id: remote_push - label: Push branch to remote - check: remote_push - - id: pr_prepare - label: Generate pull request description - check: pr_prepare - note: > - Pick the PR template type from src/configs/github-prs.yaml (feature, - deployment, ops, hotfix). Fill every required section. Include "Closes #N" - for linked issues. Add a "Backlog from this investigation" section for any - new gaps found. Use github-prs.yaml and current GitHub PR/body patterns - in this repo as the base guidance. - - id: pr_create - label: Create pull request - check: pr_create - - id: workflow_monitor - label: Monitor related workflow executions - check: workflow_monitor - optional: true - note: > - After PR creation, monitor the related GitHub workflow runs and status - checks. Open the run details when needed, watch for failed or stuck jobs, - and treat GitHub workflow execution as the primary verification path when - the repo already has CI coverage. - - id: bot_review - label: Loop automated review feedback - check: bot_review - optional: true - note: > - After PR creation, wait for Copilot, Devin, and CodeQL reviews. - Read each review from github-prs.yaml bot guidance. Address actionable - findings with code changes, reply to each bot comment, and resolve the - thread when handled. Repeat this loop after each push until bot feedback - is clean. Do not request human review while bot findings are unaddressed. - - id: checks_verify - label: Verify required status checks - check: checks_verify - optional: true - note: > - All required checks must pass before requesting human review. - For infra PRs, open check details and review the Terraform plan output. - For CodeQL, review findings in the Security tab. When the repo already - has GitHub workflow coverage, prefer those executions as the primary - verification path and monitor them closely. Use local verification to - reproduce failures or cover gaps, not as a universal gate. - - id: issue_update - label: Post close-out comment on linked issue - check: issue_update - optional: true - note: > - After PR is created, post a brief comment on the linked issue with the PR URL, - what changed, and any follow-up items. GitHub auto-closes the issue on merge - when "Closes #N" is in the PR body — do not close manually. - - id: post_merge - label: Post-merge close-out and backlog - check: post_merge - optional: true - note: > - After merge: verify issue auto-closed, post close-out comment, open issues - for any backlog items from the PR, verify monitoring changes are live. - See post_merge steps in github-prs.yaml. diff --git a/src/configs/github-issues.yaml b/src/configs/github-issues.yaml deleted file mode 100644 index 36e2a33..0000000 --- a/src/configs/github-issues.yaml +++ /dev/null @@ -1,159 +0,0 @@ -kind: githubIssues -version: udx.io/dev.kit/v1 - -config: - - # ── Label taxonomy ────────────────────────────────────────────────────────── - # Apply consistently across repos so agents and humans share the same signal. - labels: - type: - - name: "type:bug" - color: "d73a4a" - description: "Something isn't working" - - name: "type:feature" - color: "a2eeef" - description: "New capability or enhancement" - - name: "type:infra" - color: "e4e669" - description: "Infrastructure or deployment task" - - name: "type:ops" - color: "f9d71c" - description: "Operational runbook or maintenance" - - name: "type:security" - color: "b60205" - description: "Security or compliance concern" - - name: "type:docs" - color: "0075ca" - description: "Documentation improvement" - priority: - - name: "priority:critical" - color: "b60205" - description: "Blocking production or a release" - - name: "priority:high" - color: "e4e669" - description: "Should be resolved this sprint" - - name: "priority:normal" - color: "0075ca" - description: "Standard backlog priority" - status: - - name: "status:blocked" - color: "d93f0b" - description: "Cannot proceed — waiting on something external" - - name: "status:in-progress" - color: "fbca04" - description: "Actively being worked" - - # ── Issue templates ───────────────────────────────────────────────────────── - # Each template defines required sections and optional hints. - # Agents use these as the schema when writing or evaluating issue bodies. - templates: - bug: - description: "Something that used to work (or should work) is broken." - sections: - - id: summary - label: "Summary" - required: true - hint: "One sentence: what is broken and where." - - id: reproduce - label: "Steps to reproduce" - required: true - hint: "Minimal set of commands or actions that trigger the problem." - - id: expected - label: "Expected behavior" - required: true - - id: actual - label: "Actual behavior" - required: true - - id: context - label: "Context" - hint: "Relevant files, logs, environment details, related issue or PR URLs." - - feature: - description: "New capability, improvement, or work item." - sections: - - id: goal - label: "Goal" - required: true - hint: "What capability is being added and why it matters." - - id: acceptance - label: "Acceptance criteria" - required: true - hint: "Bullet list — each item must be verifiable and map to a testable condition." - - id: context - label: "Context" - hint: "Relevant files, constraints, related issues or PRs, cross-repo links." - - infra: - description: "Infrastructure, deployment, or operational change." - sections: - - id: goal - label: "Goal" - required: true - hint: "What is being changed and what outcome is expected." - - id: scope - label: "Scope" - hint: "Which services, environments, clusters, or repos are affected." - - id: acceptance - label: "Acceptance criteria" - required: true - hint: "Observable evidence that the change is live and stable." - - id: context - label: "Context" - hint: "Deploy config, workflow files, infra docs, runbook links." - - # ── Agent workflow ────────────────────────────────────────────────────────── - # Rules for agents working from a GitHub issue. - # These are the minimum expectations before, during, and after a task. - agent_workflow: - before_starting: - - id: read-issue - message: > - Read the full issue body, labels, and comments before writing code or running - commands. The issue is the scope contract — do not extend it without flagging. - - id: verify-scope - message: > - Confirm the repo and environment match the issue scope. - Use the issue URL as the cross-repo context root when work spans multiple repos. - - id: map-acceptance - message: > - Identify the acceptance criteria explicitly. These define done and gate the PR. - If criteria are missing or ambiguous, add a clarifying comment before starting. - - while_working: - - id: commit-ref - message: > - Reference the issue number in every commit message (e.g. "refs #42") and in the - PR title. This keeps the delivery chain traceable. - - id: scope-change - message: > - Post an update comment on the issue if scope changes, blockers emerge, or the - approach diverges from the description. Do not silently expand scope. - - on_completion: - - id: close-link - message: > - Link the closing PR to the issue using "Closes #N" in the PR body. - GitHub will auto-close on merge — do not close the issue manually. - - id: verify-criteria - message: > - Verify all acceptance criteria are met before requesting review. - Map each criterion to evidence (test output, deploy log, screenshot). - - id: close-comment - message: > - Post a brief close-out comment with what changed, artifact links (PR, deploy, - metrics), and any follow-up items. Keep it under 150 words. - - id: backlog-followup - message: > - If investigation surfaced new gaps, constraints, or follow-up work that falls - outside the original scope, open separate issues for each item and reference - them in the close-out comment. Do not silently absorb unplanned work. - - # ── Cross-repo linking ────────────────────────────────────────────────────── - cross_repo: - note: > - Use the GitHub issue URL as the shared context root when work spans multiple repos - or multiple agents. All agents working the same issue should read the same issue - URL before starting and reference it in their outputs. - pattern: "https://github.com/{owner}/{repo}/issues/{number}" - pr_close_keyword: "Closes" - pr_ref_keyword: "refs" diff --git a/src/configs/github-prs.yaml b/src/configs/github-prs.yaml deleted file mode 100644 index d6ff27d..0000000 --- a/src/configs/github-prs.yaml +++ /dev/null @@ -1,241 +0,0 @@ -kind: githubPullRequests -version: udx.io/dev.kit/v1 - -config: - - # ── PR description templates ──────────────────────────────────────────────── - # Each template defines required sections. Agents pick the type that matches - # the nature of the change. Sections marked required must always be present. - templates: - feature: - description: "New capability, improvement, or bug fix." - sections: - - id: closes - label: "Closes" - required: true - hint: "Closes #N for every linked issue. GitHub auto-closes on merge." - - id: changes - label: "Changes" - required: true - hint: "Bullet list of what changed and why — not a git log." - - id: pre_merge_gates - label: "Pre-merge gates" - hint: "Any cross-repo dependencies that must land first. Be explicit." - - id: review_notes - label: "Review notes" - hint: "Anything reviewers need to know: risk areas, test coverage, flags." - - id: backlog - label: "Backlog from this investigation" - hint: "New issues discovered — reference by #N if already created." - - deployment: - description: "Branch promotion (e.g. staging → production)." - sections: - - id: related - label: "Related" - required: true - hint: "Link the source issue, the prior staging merge, and any cross-repo PRs." - - id: summary - label: "Summary" - required: true - hint: "What is being promoted and what it includes." - - id: blocked_by - label: "Blocked by" - hint: "Cross-repo or cross-service dependencies that must land before merge." - - id: review_notes - label: "Review notes" - required: true - hint: > - State whether PR creation triggers a plan-mode deployment for review. - Reviewers must see the infrastructure plan before approving. - - ops: - description: "Config, infra, or operational change without a linked feature." - sections: - - id: changes - label: "Changes" - required: true - hint: "Bullet list of what changed. Reference affected services and environments." - - id: related - label: "Related" - hint: "Linked issues, related PRs, or cross-repo context." - - id: rollback - label: "Rollback" - hint: "How to revert if the change causes issues post-merge." - - hotfix: - description: "Urgent fix for a production incident." - sections: - - id: closes - label: "Closes" - required: true - - id: root_cause - label: "Root cause" - required: true - hint: "One paragraph: what broke, why, how it was found." - - id: fix - label: "Fix" - required: true - hint: "What was changed and why this is the right fix." - - id: verification - label: "Verification" - required: true - hint: "How the fix was verified locally and what runtime evidence confirms it." - - # ── Bot reviewers ─────────────────────────────────────────────────────────── - # Known automated reviewers active across UDX/ICAMiami repos. - # Agents must read and respond to each before requesting human review. - bots: - copilot: - login: copilot-pull-request-reviewer - role: PR overview summary generated from the diff. - guidance: > - Copilot auto-summarizes based on the diff. A clear, complete PR body produces - a more accurate summary. If Copilot flags a specific issue in a review comment, - assess it: fix actionable findings, or reply with a clear explanation of why the - flag is a false positive. Do not ignore Copilot comments without responding. - After addressing each comment, resolve the conversation thread — unresolved - Copilot threads block merge. - devin: - login: devin-ai-integration - role: Code review focused on potential bugs. - guidance: > - "No Issues Found" requires no action. If Devin reports specific findings, review - each one. Fix actionable bugs before requesting human review. Dismiss false - positives with a reply comment explaining the reasoning. Do not merge with - unaddressed Devin findings unless they are confirmed false positives. - github-advanced-security: - login: github-advanced-security - role: Security scanning (CodeQL, secret scanning, Dependabot). - guidance: > - All HIGH and CRITICAL findings block merge. Investigate and resolve before - opening for human review. MEDIUM and LOW findings should be triaged — document - suppressions inline with a comment explaining the exception. - codeql: - login: CodeQL - role: Static analysis on JavaScript, Actions, Shell, Dockerfiles, YAML. - guidance: > - Fix all true findings. If suppressing a false positive, add an inline comment - in the source file explaining the exception. Do not merge with unresolved - CodeQL alerts unless they are documented suppressions. - - # ── Workflow check verification ───────────────────────────────────────────── - # Named check families seen across UDX/ICAMiami repos. - # Agents must verify these before marking a PR ready. - workflow_checks: - required_to_merge: - message: > - All required checks must pass before merge. Do not merge or request human - review while any required check is failing — investigate the root cause first. - infra_validation: - name_pattern: "infrastructure / Configuration & Validation" - message: > - This is a Terraform plan. Open the check details and review the plan output - before approving an infra or deployment PR. A plan with unexpected destroys - or drift is a signal to investigate before merging. - infra_deployment: - name_pattern: "infrastructure / Deployment" - message: > - This is Terraform apply. Verify the deployment succeeded in the check details. - Check for drift, resource errors, or timeout failures — do not treat a green - check as success without reviewing the apply output. - infra_report: - name_pattern: "infrastructure / Process Results & Report" - message: > - Post-apply report. Review for warnings, partial failures, or resources left - in unknown state. - codeql_check: - name_pattern: "CodeQL" - message: Review findings in the Security tab before merging. - mirror: - name_pattern: "Mirror to Bitbucket" - message: > - Cross-SCM mirror. Failure here means the repo is out of sync with Bitbucket. - Investigate but do not block merge on mirror failures alone. - - # ── Post-merge actions ────────────────────────────────────────────────────── - # Steps an agent should take after a PR is merged. - post_merge: - - id: issue-autoclose - message: > - Verify the linked issue auto-closed via "Closes #N" in the PR body. - If the issue is still open after merge, close it manually with a comment - linking the merged PR and a brief summary of what shipped. - - id: close-out-comment - message: > - Post a close-out comment on the linked issue with the merged PR URL, - exact versions or release tags, a one-line summary of what shipped, - findings delta (what changed vs. what was expected), and any outstanding - follow-up items as linked issues. Use exact URLs — not descriptions. - - id: backlog-issues - message: > - For each item listed under "Backlog from this investigation", verify a GitHub - issue exists. If not, create one. Reference all backlog issues in the close-out - comment so the follow-up chain is explicit. - - id: monitoring-verify - message: > - If the PR included alert policy, dashboard, or monitoring config changes, - verify within 24h that alerts are firing correctly and thresholds are sound. - Open a follow-up issue if tuning is needed. - - id: release-tag - message: > - If the repo follows versioned releases, create a release tag after merging to - production. Update the changelog with the changes from this PR. - - # ── Agent workflow for PRs ────────────────────────────────────────────────── - agent_workflow: - before_creating: - - id: verify-surface - message: > - Detect the canonical verify command from repo context and note it in your - working plan, but prefer GitHub workflow executions when the repo already - has CI coverage. Use local verification for scoped debugging, reproducing - workflow failures, or covering gaps in workflow coverage. - - id: pick-template - message: > - Pick the PR template type (feature, deployment, ops, hotfix) that matches - the change. Fill every required section. Do not leave sections empty — - if a section truly does not apply, remove it rather than leaving it blank. - - id: close-link - message: > - Include "Closes #N" for every linked issue. If there are pre-merge gates - (cross-repo dependencies), call them out explicitly in the PR body. - - after_creating: - - id: monitor-workflows - message: > - After the PR is created, monitor the related GitHub workflow runs and - status checks. Open failing or slow runs, review the logs, and treat - workflow execution as the primary verification path when the repo already - has CI coverage. - - id: read-bot-reviews - message: > - After the PR is created, wait for automated reviews: Copilot, Devin, - CodeQL, GitHub Advanced Security. Read each review. Address actionable - findings with code fixes. Reply to false positives and design decisions - with brief explanations. Resolve every conversation thread after - addressing it — unresolved threads block merge even when all checks - pass. Loop through bot feedback after each push until all threads are - resolved, comments are answered, and merge state is CLEAN. - - id: check-status - message: > - Verify all required status checks pass. For infrastructure PRs, open - the check details and review the Terraform plan output before requesting - human review. Do not rely on a green checkmark alone. - - id: update-description - message: > - If bot reviews or check outputs reveal additional context (e.g. a finding - that widens scope or a plan that shows unexpected drift), update the PR - body to reflect the new information. The PR body is the source of truth - for reviewers. - - on_merge: - - id: post-close-comment - message: > - Post a close-out comment on the linked issue with the merged PR URL, what - shipped, and any follow-up issues. This is the signal that the delivery - chain is complete. - - id: open-backlog - message: > - Create GitHub issues for any backlog items surfaced during the PR. - Reference them in the close-out comment so the follow-up chain is explicit. diff --git a/src/configs/knowledge-base.yaml b/src/configs/knowledge-base.yaml deleted file mode 100644 index d3284c7..0000000 --- a/src/configs/knowledge-base.yaml +++ /dev/null @@ -1,36 +0,0 @@ -kind: knowledgeBase -version: udx.io/dev.kit/v1 - -config: - hierarchy: - local_repos_root: git/udx - remote_org_root: github.com/udx - agent_experience: - order: - - repo_contract - - github_history - - repo_defaults - - lessons - notes: - - Start from the current repo contract on disk. - - Use live GitHub issues, pull requests, review state, and commit history as the primary dynamic source. - - Fall back to repo-owned workflow and practice catalogs when GitHub history is missing, sparse, or not applicable. - - Treat lessons as supporting memory, not as a source of truth. - sources: - preferred: - items: - - repo docs, configs, templates, and tests - - repo TODO, context, and refs files when present - - remote repos, issues, pull requests, and commit history under github.com/udx/* - - local repos under git/udx - - codex sessions under ~/.codex/sessions when available - github: - description: GitHub experience history is the primary dynamic input for agent decisions once the repo contract is loaded - method: gh api (prefer REST API via gh api over gh subcommands for consistency and reliability) - items: - - open issues (scope, labels, assignees) - - recent pull requests (merged, open, review state) - - security alerts (Dependabot, CodeQL, secret scanning) - - active branches and staleness - - release history and deployment tags - - commit activity and contributor patterns diff --git a/src/configs/learning-workflows.yaml b/src/configs/learning-workflows.yaml deleted file mode 100644 index 780ac28..0000000 --- a/src/configs/learning-workflows.yaml +++ /dev/null @@ -1,170 +0,0 @@ -kind: learningWorkflows -version: udx.io/dev.kit/v1 - -config: - source_discovery: - local_session_roots: - - $CODEX_HOME/sessions - claude_projects_root: ~/.claude/projects - enabled_sources: - - claude - - codex - max_recent_sessions: 6 - workflows: - pr-lessons: - name: dev.learn pr - description: Generate lightweight lessons-learned from recent pull requests and agent sessions, then route reusable workflow patterns into durable repo artifacts - sources: - - recent pull requests - - docs/pull-requests.md - - docs/engineering-guide.md - - TODO.md - - refs.md - destinations: - - lessons_report - - gh_issue - - wiki - - slack - session_flow: - issue-scope: - threshold: 2 - message: Read the linked GitHub issue, verify the repo/workspace match, and lock the scope before changing code. - patterns: - - /issues/ - - issue details - - affected code path - verify-before-sync: - threshold: 2 - message: Prefer GitHub workflow verification when the repo has CI coverage, monitor the run, and use local verification only when you need a quick scoped check or a reproduction path. - patterns: - - make build - - confirm build - - verify - pr-chain: - threshold: 2 - message: Commit or sync the branch, prepare the PR in the repo’s style, and link the related issue. - patterns: - - sync to remote - - brief pr - - related github issue - github-style-reuse: - threshold: 2 - message: Before creating a branch, PR, issue, or GitHub update, inspect recent repo history and reuse the repo's established naming and writing patterns. - patterns: - - previous prs - - repo style - - branch name - - title and description - post-follow-up: - threshold: 2 - message: After merge or release, collect workflow and artifact evidence, then post a concise linked update with next steps. - patterns: - - merged - - release - - artifacts - - update comment - docs-first-alignment: - threshold: 2 - message: Review README, docs, and tests before refactoring. Restate the target workflow, then simplify code and remove mismatched legacy paths. - patterns: - - readme - - docs - - refactor - - cleanup - workflow-tracing: - threshold: 2 - message: Locate the actual workflow or deploy file first, then trace the commands and supporting docs that drive execution. - patterns: - - deploy.yml - - .github/workflows - - workflow file - - workflow gaps - - what deploy.yml actually does - - what actually runs - - trace - verification-scope: - threshold: 2 - message: If local verification is needed, run the smallest local check that proves the current change. Prefer broader or slower coverage in GitHub workflows, monitor the run, and call the tradeoff out explicitly. - patterns: - - make test - - local check - - CI - - defer - legacy-reduction: - threshold: 2 - message: When a new direction is accepted, archive or delete conflicting old modules/configs instead of carrying both models forward. - patterns: - - cleanup - - legacy - - remove - - consolidate - agent-handoff: - threshold: 2 - message: Refresh repo context, manifests, and AGENTS.md before deeper agent work so the repo contract is the source of truth. - patterns: - - dev.kit repo - - dev.kit agent - - refresh context - - agents.md - history-debugging: - threshold: 2 - message: When debugging or widening scope, use related GitHub issues, PRs, and recent repo history first to understand prior changes and possible regressions. - patterns: - - previous changes - - breaking change - - related prs - - debug - session_rules: - verify-before-deploy: - threshold: 2 - message: Prefer the repo's GitHub workflow executions for build or runtime verification before deploy-oriented steps, and use local verification when you need to reproduce or isolate a failure. - patterns: - - make build - - confirm build - - deploy.yml - workflow-file-first: - threshold: 2 - message: Use repo workflow assets like deploy.yml, workflow files, and repo docs as the execution contract instead of inventing an ad hoc deploy path. - patterns: - - deploy.yml - - workflow - - readme.md - sync-pr-issue-chain: - threshold: 2 - message: Keep the delivery chain explicit: create or sync the branch, prepare the PR, and connect the related GitHub issue before close-out. - patterns: - - sync to remote - - brief pr - - related github issue - bot-review-loop: - threshold: 2 - message: After a PR exists, read automated review feedback, fix actionable findings, reply to false positives, resolve the threads, and repeat until bot feedback is clean. - patterns: - - codeql - - copilot - - devin - - review comment - source-backed-updates: - threshold: 3 - message: Report outcomes with exact URLs, versions, findings deltas, and next steps so the follow-up can be reused by humans and agents without drift. - patterns: - - release - - findings - - next step - - add url - config-over-code: - threshold: 2 - message: Express repo rules in YAML/manifests first, then keep shell glue thin and composable. - patterns: - - yaml - - manifest - - config - - thin wrapper - agent-context-refresh: - threshold: 2 - message: Refresh repo context and AGENTS.md from repo artifacts before deeper agent work so the workflow stays repo-centric. - patterns: - - dev.kit - - context.yaml - - agents.md - - refresh diff --git a/src/configs/repo-scaffold.yaml b/src/configs/repo-scaffold.yaml deleted file mode 100644 index a91e5ef..0000000 --- a/src/configs/repo-scaffold.yaml +++ /dev/null @@ -1,75 +0,0 @@ -kind: repoScaffold -version: udx.io/dev.kit/v1 -config: - - # Scaffold applied to every repo regardless of archetype - baseline: - dirs: - - docs - - .github/ISSUE_TEMPLATE - files: - - .gitignore - - AGENTS.md - - .github/PULL_REQUEST_TEMPLATE.md - - # Additional scaffold items per archetype - archetypes: - shell-cli: - dirs: - - bin - - lib/commands - - lib/modules - - src/configs - - src/templates - - tests - files: - - Makefile - - README.md - - node-app: - dirs: - - src - - tests - files: - - package.json - - README.md - - runtime-image: - dirs: - - bin - - tests - files: - - Dockerfile - - Makefile - - README.md - - wordpress-site: - dirs: - - docs - files: - - README.md - - infra-pipeline: - dirs: - - .github/workflows - - terraform - - docs - files: - - README.md - - # Factor-driven scaffold: what to create when a factor is missing or partial - factors: - documentation: - files: - - README.md - - docs/integration.md - - docs/context.md - config: - files: - - .env.example - pipeline: - dirs: - - tests - - .github/workflows - files: - - Makefile diff --git a/src/templates/agent.json b/src/templates/agent.json index 98f4b3f..d649587 100644 --- a/src/templates/agent.json +++ b/src/templates/agent.json @@ -3,7 +3,6 @@ "repo": "{{repo}}", "path": "{{path}}", "archetype": "{{archetype}}", - "profile": "{{profile}}", "agents_md": "{{agents_md}}", "context": "{{context}}", "priority_refs": {{priority_refs}}, diff --git a/src/templates/learn.json b/src/templates/learn.json deleted file mode 100644 index ae8fae3..0000000 --- a/src/templates/learn.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "command": "{{command}}", - "repo": "{{repo}}", - "workflow": { - "id": "{{workflow_id}}", - "name": "{{workflow_name}}" - }, - "description": "{{description}}", - "sources": {{sources}}, - "observed_sources": {{observed_sources}}, - "destinations": {{destinations}}, - "session": {{session}}, - "flow": {{flow}}, - "shared_context": {{shared_context}}, - "knowledge_base": {{knowledge_base}}, - "knowledge_sources": {{knowledge_sources}} -} diff --git a/src/templates/repo.json b/src/templates/repo.json index 7d100f7..39c2b79 100644 --- a/src/templates/repo.json +++ b/src/templates/repo.json @@ -4,7 +4,7 @@ "path": "{{path}}", "mode": "{{mode}}", "archetype": "{{archetype}}", - "profile": "{{profile}}", + "profile": null, "markers": {{markers}}, "factors": {{factors}}, "gaps": {{gaps}}, diff --git a/tests/fixtures/codex-home/sessions/2026/04/02/rollout-2026-04-02T20-54-19-019d4f54-eddc-7350-a757-3bb578d24f99.jsonl b/tests/fixtures/codex-home/sessions/2026/04/02/rollout-2026-04-02T20-54-19-019d4f54-eddc-7350-a757-3bb578d24f99.jsonl deleted file mode 100644 index 916e685..0000000 --- a/tests/fixtures/codex-home/sessions/2026/04/02/rollout-2026-04-02T20-54-19-019d4f54-eddc-7350-a757-3bb578d24f99.jsonl +++ /dev/null @@ -1,10 +0,0 @@ -{"timestamp":"2026-04-02T17:54:23.549Z","type":"session_meta","payload":{"id":"019d4f54-eddc-7350-a757-3bb578d24f99","timestamp":"2026-04-02T17:54:19.235Z","cwd":"/Users/jonyfq/git/udx/worker-tooling","originator":"codex-tui","cli_version":"0.118.0","source":"cli","model_provider":"openai"}} -{"timestamp":"2026-04-02T17:54:23.553Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"https://github.com/icamiami/icamiami.org/issues/1897"}]}} -{"timestamp":"2026-04-02T17:54:29.875Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"I’m pulling the issue details first and checking whether this workspace matches that repository before making any changes. After that I’ll trace the affected code path and implement the fix locally if the code is here."}],"phase":"commentary"}} -{"timestamp":"2026-04-02T17:56:56.484Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"let's do make build and confirm build"}]}} -{"timestamp":"2026-04-02T17:57:30.573Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"also, use deploy.yml to deploy and see if tf really upgraded"}]}} -{"timestamp":"2026-04-02T18:00:01.549Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"let's create github branch and sync to remote"}]}} -{"timestamp":"2026-04-02T18:48:40.260Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"ok, let's prepare brief PR"}]}} -{"timestamp":"2026-04-02T18:50:41.200Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"let's add related github issue"}]}} -{"timestamp":"2026-04-03T10:51:58.211Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"nice, I merged and new release was completed, let's see if you can find related github action workflow and releases artifacts there were created, vulnerabilities reduce that happened and so on, so basically collect some data so we can plan brief update comment to related github issue, right?"}]}} -{"timestamp":"2026-04-03T11:04:21.403Z","type":"event_msg","payload":{"type":"exec_command_end","aggregated_output":"{\"html_url\":\"https://github.com/icamiami/icamiami.org/issues/1897#issuecomment-4183019047\",\"body\":\"✅ Update: [udx/worker-tooling#57](https://github.com/udx/worker-tooling/pull/57) was merged and released as [`0.19.0`](https://github.com/udx/worker-tooling/releases/tag/0.19.0) on 2026-04-02. Findings improvement in code scanning. ⚠️ Next step: update rabbit-automation-action.\"}"}} diff --git a/tests/fixtures/php-repo/composer.json b/tests/fixtures/php-repo/composer.json deleted file mode 100644 index b7a0278..0000000 --- a/tests/fixtures/php-repo/composer.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "acme/php-repo", - "require-dev": { - "phpunit/phpunit": "^10.0" - } -} diff --git a/tests/fixtures/php-repo/phpunit.xml b/tests/fixtures/php-repo/phpunit.xml deleted file mode 100644 index e5694d6..0000000 --- a/tests/fixtures/php-repo/phpunit.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - tests - - - diff --git a/tests/fixtures/php-repo/tests/ExampleTest.php b/tests/fixtures/php-repo/tests/ExampleTest.php deleted file mode 100644 index cdb39bf..0000000 --- a/tests/fixtures/php-repo/tests/ExampleTest.php +++ /dev/null @@ -1,5 +0,0 @@ -> "$GITHUB_OUTPUT" diff --git a/tests/fixtures/workflow-repo/README.md b/tests/fixtures/workflow-repo/README.md deleted file mode 100644 index 73c9daa..0000000 --- a/tests/fixtures/workflow-repo/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Workflow Repo - -## Usage - -This repository publishes reusable GitHub workflow contracts. - -## Architecture - -Workflow contracts live in `.github/workflows/` and documentation lives in `README.md`. - -## Development - -Run verification with: - -```bash -npm test -``` - -Environment inputs are documented in the workflow interface and examples. diff --git a/tests/local-udx.sh b/tests/local-udx.sh deleted file mode 100644 index 1bbeb5c..0000000 --- a/tests/local-udx.sh +++ /dev/null @@ -1,317 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -DEV_KIT_BIN="${DEV_KIT_BIN:-$REPO_DIR/bin/dev-kit}" -LOCAL_ROOT="${DEV_KIT_LOCAL_REPOS_ROOT:-$HOME/git/udx}" -MAX_REPOS="${DEV_KIT_LOCAL_REPOS_MAX:-0}" -ONLY_REPOS="${DEV_KIT_LOCAL_REPOS_ONLY:-}" -COMMANDS="${DEV_KIT_LOCAL_REPOS_COMMANDS:-repo,agent}" -FAIL_ON_WEAK="${DEV_KIT_LOCAL_REPOS_FAIL_ON_WEAK:-0}" - -REPO_COUNT=0 -TOTAL_CHECKS=0 -TOTAL_PASS=0 -TOTAL_WARN=0 -TOTAL_FAIL=0 - -usage() { - cat <<'EOF' -Usage: bash tests/local-udx.sh - -Environment: - DEV_KIT_LOCAL_REPOS_ROOT Root to scan for repos (default: $HOME/git/udx) - DEV_KIT_LOCAL_REPOS_MAX Limit the number of repos checked - DEV_KIT_LOCAL_REPOS_ONLY Comma-separated repo names to check - DEV_KIT_LOCAL_REPOS_COMMANDS Comma-separated commands: repo,agent[,learn] - learn requires CODEX_HOME or agent session source - DEV_KIT_LOCAL_REPOS_FAIL_ON_WEAK Exit non-zero when weak findings exist (default: 0) -EOF -} - -print_block() { - local title="$1" - local content="$2" - - printf '%s\n' "--- ${title} ---" - printf '%s\n' "$content" - printf '%s\n' "--- end ${title} ---" -} - -has_command() { - local name="$1" - - case ",$COMMANDS," in - *,"$name",*) return 0 ;; - esac - - return 1 -} - -list_repos() { - local repo_path="" - local repo_name="" - local count=0 - - [ -d "$LOCAL_ROOT" ] || return 0 - - while IFS= read -r repo_path; do - [ -d "$repo_path/.git" ] || continue - repo_name="$(basename "$repo_path")" - if [ -n "$ONLY_REPOS" ]; then - case ",$ONLY_REPOS," in - *,"$repo_name",*) ;; - *) continue ;; - esac - fi - printf '%s\n' "$repo_path" - count=$((count + 1)) - if [ "$MAX_REPOS" -gt 0 ] && [ "$count" -ge "$MAX_REPOS" ]; then - break - fi - done </dev/null 2>&1; then - note_pass "$repo_name" "$command_name" "$pass_message" - else - if [ "$severity" = "fail" ]; then - note_fail "$repo_name" "$command_name" "$warn_message" - else - note_warn "$repo_name" "$command_name" "$warn_message" - fi - fi -} - -check_json_contract() { - local repo_name="$1" - local command_name="$2" - local repo_path="$3" - local json="$4" - - check_json_field "$repo_name" "$command_name" "$json" '.command == "'"$command_name"'"' \ - "command contract is stable" \ - "command contract missing or changed" \ - "fail" - - check_json_field "$repo_name" "$command_name" "$json" '.path == "'"$repo_path"'" or .repo == "'"$repo_path"'"' \ - "repo path is reported" \ - "repo path is missing or changed" \ - "fail" - - case "$command_name" in - repo) - check_json_field "$repo_name" "$command_name" "$json" '.markers | type == "array" and length > 0' \ - "markers detected" \ - "no repo markers detected" - check_json_field "$repo_name" "$command_name" "$json" '.archetype | type == "string" and length > 0' \ - "archetype classified" \ - "archetype missing" - check_json_field "$repo_name" "$command_name" "$json" '.factors | type == "object"' \ - "factors emitted" \ - "factors missing" - ;; - agent) - check_json_field "$repo_name" "$command_name" "$json" '.archetype | type == "string" and length > 0' \ - "archetype classified" \ - "archetype missing" - check_json_field "$repo_name" "$command_name" "$json" '.workflow_contract | type == "array" and length > 0' \ - "workflow contract emitted" \ - "workflow contract missing" - check_json_field "$repo_name" "$command_name" "$json" '.priority_refs | type == "array" and length > 0' \ - "priority refs emitted" \ - "priority refs missing" - check_json_field "$repo_name" "$command_name" "$json" '.entrypoints | type == "object"' \ - "entrypoints emitted" \ - "entrypoints missing" - ;; - learn) - check_json_field "$repo_name" "$command_name" "$json" '.workflow.id | type == "string" and length > 0' \ - "workflow id emitted" \ - "workflow id missing" - check_json_field "$repo_name" "$command_name" "$json" '.sources | type == "array" and length > 0' \ - "learning sources emitted" \ - "learning sources missing" - check_json_field "$repo_name" "$command_name" "$json" '.destinations | type == "array" and length > 0' \ - "learning destinations emitted" \ - "learning destinations missing" - ;; - esac -} - -run_repo_command() { - local repo_path="$1" - local repo_name="$2" - local command_name="$3" - local output="" - local command_exit=0 - - printf ' [%s]\n' "$command_name" - - set +e - output="$(run_json "$command_name" "$repo_path" 2>&1)" - command_exit=$? - set -e - - if [ "$command_exit" -ne 0 ]; then - note_fail "$repo_name" "$command_name" "command exited with code $command_exit" - print_block "$repo_name $command_name output" "$output" - return - fi - - if ! printf '%s\n' "$output" | jq -e . >/dev/null 2>&1; then - note_fail "$repo_name" "$command_name" "output is not valid json" - print_block "$repo_name $command_name output" "$output" - return - fi - - check_json_contract "$repo_name" "$command_name" "$repo_path" "$output" -} - -run_repo_checks() { - local repo_path="$1" - local repo_name="" - local start_checks=0 - local start_pass=0 - local start_warn=0 - local start_fail=0 - local repo_checks=0 - local repo_pass=0 - local repo_warn=0 - local repo_fail=0 - - repo_name="$(basename "$repo_path")" - REPO_COUNT=$((REPO_COUNT + 1)) - - start_checks=$TOTAL_CHECKS - start_pass=$TOTAL_PASS - start_warn=$TOTAL_WARN - start_fail=$TOTAL_FAIL - - printf '\n# %s\n' "$repo_path" - - if has_command "repo"; then - run_repo_command "$repo_path" "$repo_name" "repo" - fi - - if has_command "agent"; then - run_repo_command "$repo_path" "$repo_name" "agent" - fi - - if has_command "learn"; then - run_repo_command "$repo_path" "$repo_name" "learn" - fi - - repo_checks=$((TOTAL_CHECKS - start_checks)) - repo_pass=$((TOTAL_PASS - start_pass)) - repo_warn=$((TOTAL_WARN - start_warn)) - repo_fail=$((TOTAL_FAIL - start_fail)) - - printf ' score: pass=%s warn=%s fail=%s checks=%s\n' \ - "$repo_pass" "$repo_warn" "$repo_fail" "$repo_checks" -} - -main() { - local repo_path="" - local matched=0 - - if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then - usage - exit 0 - fi - - if [ ! -x "$DEV_KIT_BIN" ]; then - printf 'dev.kit binary not found: %s\n' "$DEV_KIT_BIN" >&2 - exit 1 - fi - - if ! command -v jq >/dev/null 2>&1; then - printf 'jq is required for tests/local-udx.sh\n' >&2 - exit 1 - fi - - if [ ! -d "$LOCAL_ROOT" ]; then - printf 'local repo root not found: %s\n' "$LOCAL_ROOT" >&2 - exit 1 - fi - - printf 'local root: %s\n' "$LOCAL_ROOT" - [ "$MAX_REPOS" -gt 0 ] && printf 'max repos: %s\n' "$MAX_REPOS" - [ -n "$ONLY_REPOS" ] && printf 'only repos: %s\n' "$ONLY_REPOS" - printf 'commands: %s\n' "$COMMANDS" - [ "$FAIL_ON_WEAK" -eq 1 ] && printf 'fail on weak findings: enabled\n' - - while IFS= read -r repo_path; do - [ -n "$repo_path" ] || continue - matched=1 - run_repo_checks "$repo_path" - done <&2 - exit 1 - fi - - printf '\nsummary: repos=%s pass=%s warn=%s fail=%s checks=%s\n' \ - "$REPO_COUNT" "$TOTAL_PASS" "$TOTAL_WARN" "$TOTAL_FAIL" "$TOTAL_CHECKS" - - if [ "$TOTAL_FAIL" -gt 0 ]; then - exit 1 - fi - - if [ "$FAIL_ON_WEAK" -eq 1 ] && [ "$TOTAL_WARN" -gt 0 ]; then - exit 1 - fi -} - -main "$@" diff --git a/tests/real-repos.sh b/tests/real-repos.sh new file mode 100644 index 0000000..6036ada --- /dev/null +++ b/tests/real-repos.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TEST_HOME="${DEV_KIT_TEST_HOME:-$(mktemp -d "${TMPDIR:-/tmp}/dev-kit-real-repos.XXXXXX")}" +DEV_KIT_BIN_DIR="$TEST_HOME/.local/bin" +KEEP_HOME="${DEV_KIT_TEST_KEEP_HOME:-0}" +REAL_REPOS_CSV="${DEV_KIT_TEST_REAL_REPOS:-}" + +usage() { + cat <<'EOF' +Usage: bash tests/real-repos.sh [/abs/path/to/repo ...] + +Runs the current dev.kit working tree against real local repos using a temporary +plain `dev.kit` shim, without changing the global install. + +Options via environment: + DEV_KIT_TEST_REAL_REPOS Colon-separated repo paths when no CLI args are given + DEV_KIT_TEST_HOME Temp home for the test run + DEV_KIT_TEST_KEEP_HOME Keep temp home after exit (default: 0) + +Examples: + bash tests/real-repos.sh ~/git/udx/reusable-workflows ~/git/udx/www.peakclt.com + DEV_KIT_TEST_REAL_REPOS="$HOME/git/udx/reusable-workflows:$HOME/git/udx/www.peakclt.com" bash tests/real-repos.sh +EOF +} + +cleanup() { + if [ "$KEEP_HOME" != "1" ]; then + rm -rf "$TEST_HOME" + fi +} + +trap cleanup EXIT + +if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then + usage + exit 0 +fi + +repo_paths=() +if [ "$#" -gt 0 ]; then + while [ "$#" -gt 0 ]; do + repo_paths+=("$1") + shift + done +elif [ -n "$REAL_REPOS_CSV" ]; then + while IFS= read -r repo_path; do + [ -n "$repo_path" ] || continue + repo_paths+=("$repo_path") + done <&2 + exit 1 +fi + +mkdir -p "$DEV_KIT_BIN_DIR" +ln -sfn "$REPO_DIR/bin/dev-kit" "$DEV_KIT_BIN_DIR/dev.kit" + +export HOME="$TEST_HOME" +export PATH="$DEV_KIT_BIN_DIR:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +unset DEV_KIT_HOME +unset DEV_KIT_BIN_DIR + +for repo_path in "${repo_paths[@]}"; do + repo_path="$(cd "$repo_path" 2>/dev/null && pwd || true)" + [ -n "$repo_path" ] || { printf 'repo not found\n' >&2; exit 1; } + [ -d "$repo_path/.git" ] || { printf 'not a git repo: %s\n' "$repo_path" >&2; exit 1; } + run_out="$(mktemp "$TEST_HOME/dev-kit-real.XXXXXX.out")" + + printf '\n===== %s =====\n' "$repo_path" + ( + cd "$repo_path" + if ! dev.kit >"$run_out"; then + cat "$run_out" >&2 + exit 1 + fi + [ -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' + awk '/^gaps:/{flag=1;next} /^# Dependencies/{if(flag) exit} flag{print}' .rabbit/context.yaml || true + printf '\ndependencies:\n' + awk '/^dependencies:/{flag=1;next} /^# Manifests/{if(flag) exit} flag{print}' .rabbit/context.yaml | sed -n '1,80p' + ) +done diff --git a/tests/suite.sh b/tests/suite.sh index f02ce66..f277ba6 100644 --- a/tests/suite.sh +++ b/tests/suite.sh @@ -10,9 +10,10 @@ BASE_PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" SIMPLE_REPO="$REPO_DIR/tests/fixtures/simple-repo" DOCUMENTED_SHELL_REPO="$REPO_DIR/tests/fixtures/documented-shell-repo" DOCKER_REPO="$REPO_DIR/tests/fixtures/docker-repo" -WORDPRESS_REPO="$REPO_DIR/tests/fixtures/wordpress-repo" SIMPLE_ACTION_REPO="$TEST_HOME/simple-action-repo" -AVAILABLE_TEST_GROUPS="core archetypes learn install" +HOME_ACTION_REPO="$TEST_HOME/home-action-repo" +DOCKER_ACTION_REPO="$TEST_HOME/docker-action-repo" +AVAILABLE_TEST_GROUPS="core" TEST_ONLY="${DEV_KIT_TEST_ONLY:-}" cleanup() { @@ -22,12 +23,10 @@ trap cleanup EXIT usage() { cat <<'EOF' -Usage: bash tests/suite.sh [--only group1,group2] [--list] +Usage: bash tests/suite.sh [--only core] [--list] Groups: - core home + repo + agent (fast, no install required) - archetypes fixture archetype detection - install full install + uninstall flow (slow, CI) + core minimal happy-path smoke checks EOF } @@ -63,9 +62,6 @@ while [ "$#" -gt 0 ]; do shift done -# ── Setup ────────────────────────────────────────────────────────────────────── -# Point directly at the repo — no tar, no install needed for core/archetypes. - mkdir -p "$TEST_HOME" export HOME="$TEST_HOME" export PATH="$BASE_PATH" @@ -77,234 +73,62 @@ DEV_KIT_BIN_DIR="$TEST_HOME/.local/bin" mkdir -p "$DEV_KIT_BIN_DIR" ln -sf "$REPO_DIR/bin/dev-kit" "$DEV_KIT_BIN_DIR/dev.kit" export PATH="$DEV_KIT_BIN_DIR:$PATH" + # shellcheck disable=SC1090 . "$DEV_KIT_HOME/bin/env/dev-kit.sh" - -# ── core ─────────────────────────────────────────────────────────────────────── +while IFS= read -r module_file; do + [ -n "$module_file" ] || continue + [ "$module_file" = "$REPO_DIR/lib/modules/bootstrap.sh" ] && continue + # shellcheck disable=SC1090 + . "$module_file" +done < "$TEST_CODEX_HOME/sessions/2026/04/12/rollout-2026-04-12T10-00-00-${CODEX_UUID}.jsonl" < hidden bootstrap content "}]}} -{"type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"check deploy.yml and github actions workflow security gaps"}]}} -{"type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"https://github.com/test/repo/issues/42 related issue"}]}} -EOF - - # -- claude fixture (project dir derived from LEARN_REPO path) -- - CLAUDE_UUID="11111111-2222-3333-4444-555555555555" - CLAUDE_PROJECT_ID="$(printf "%s" "$LEARN_REPO" | sed -E 's|/|-|g; s|[^[:alnum:]_-]|-|g; s|-+|-|g')" - TEST_CLAUDE_PROJECTS="$TEST_HOME/.claude-projects" - TEST_CLAUDE_HISTORY="$TEST_HOME/.claude-history.jsonl" - mkdir -p "$TEST_CLAUDE_PROJECTS/$CLAUDE_PROJECT_ID" - cat > "$TEST_CLAUDE_PROJECTS/$CLAUDE_PROJECT_ID/${CLAUDE_UUID}.jsonl" < "$TEST_CLAUDE_HISTORY" </dev/null 2>&1 - artifact="$(ls "$LEARN_REPO/.rabbit/dev.kit/lessons-"*.md 2>/dev/null | head -1)" - assert_file_exists "$artifact" "learn: artifact file written" - assert_contains "$(cat "$artifact")" "## Workflow rules" "learn: artifact has workflow rules section" - assert_contains "$(cat "$artifact")" "## Ready templates" "learn: artifact has ready templates section" - assert_contains "$(cat "$artifact")" "## Evidence highlights" "learn: artifact has evidence highlights section" - assert_contains "$(cat "$artifact")" "github.com" "learn: artifact includes referenced URLs" - assert_not_contains "$(cat "$artifact")" "AGENTS.md instructions" "learn: artifact excludes bootstrap prompt noise" - assert_contains "$(cat "$artifact")" "Use repo workflow assets like deploy.yml" "learn: artifact packages rule guidance" - assert_contains "$(cat "$artifact")" '`Workflow tracing`' "learn: artifact packages reusable templates" - assert_contains "$(cat "$artifact")" "claude history prefers this compact prompt" "learn: artifact keeps compact evidence highlights" - - # 5. incremental: sessions older than last-run are skipped - touch -t 203001010000 "$LEARN_REPO/.rabbit/dev.kit/learn-last-run" - learn_incr="$(cd "$LEARN_REPO" && dev.kit learn --json)" - assert_not_contains "$learn_incr" "$CODEX_UUID" "learn: incremental skips old codex session" - assert_not_contains "$learn_incr" "$CLAUDE_UUID" "learn: incremental skips old claude session" - - # 6. deleting the artifact resets the incremental baseline and rebuilds from all sessions - rm -f "$artifact" - learn_rebuild="$(cd "$LEARN_REPO" && dev.kit learn --json)" - assert_contains "$learn_rebuild" "$CODEX_UUID" "learn: missing artifact rebuilds codex history" - assert_contains "$learn_rebuild" "$CLAUDE_UUID" "learn: missing artifact rebuilds claude history" - rm -f "$LEARN_REPO/.rabbit/dev.kit/learn-last-run" - - # 7. incremental artifact merges previous lessons with new session-derived deltas - (cd "$LEARN_REPO" && dev.kit learn) >/dev/null 2>&1 - sleep 1 - SECOND_CODEX_UUID="ffffffff-1111-2222-3333-444444444444" - mkdir -p "$TEST_CODEX_HOME/sessions/2026/04/13" - cat > "$TEST_CODEX_HOME/sessions/2026/04/13/rollout-2026-04-13T12-00-00-${SECOND_CODEX_UUID}.jsonl" </dev/null 2>&1 - merged_artifact="$(ls "$LEARN_REPO/.rabbit/dev.kit/lessons-"*.md 2>/dev/null | head -1)" - merged_artifact_text="$(cat "$merged_artifact")" - assert_contains "$merged_artifact_text" "https://github.com/test/repo/issues/42" "learn: merged artifact retains prior references" - assert_contains "$merged_artifact_text" "https://github.com/test/repo/pull/9" "learn: merged artifact adds new references" - assert_contains "$merged_artifact_text" '`Config-over-code`' "learn: merged artifact adds new reusable template" - - # 8. no sessions — use env to ensure override reaches the subprocess - learn_empty="$(cd "$LEARN_REPO" && \ - env CODEX_HOME="$TEST_HOME/no-codex" CLAUDE_PROJECTS_ROOT="$TEST_HOME/no-claude" \ - dev.kit learn)" - assert_contains "$learn_empty" "no new agent sessions found since the latest lessons artifact" "learn: handles empty incremental runs gracefully" - - unset CODEX_HOME CLAUDE_PROJECTS_ROOT CLAUDE_HISTORY_FILE -fi - -# ── install ──────────────────────────────────────────────────────────────────── -# Full tar+install+uninstall — slow, run in CI or with --only install. - -if should_run "install" && [ -n "${CI:-}" ]; then - INSTALLER_COPY="$TEST_HOME/install.sh" - ARCHIVE_FILE="$TEST_HOME/dev-kit-main.tar.gz" - FAKE_BIN_DIR="$TEST_HOME/fake-bin" - FAKE_NPM_LOG="$TEST_HOME/fake-npm.log" - cp "$REPO_DIR/bin/scripts/install.sh" "$INSTALLER_COPY" - tar -czf "$ARCHIVE_FILE" --exclude=".git" --exclude="node_modules" --exclude="vendor" \ - -C "$(dirname "$REPO_DIR")" "$(basename "$REPO_DIR")" - mkdir -p "$FAKE_BIN_DIR" - cat > "$FAKE_BIN_DIR/npm" <> "$FAKE_NPM_LOG" -if [ "\${1:-}" = "list" ] && [ "\${2:-}" = "-g" ] && [ "\${3:-}" = "@udx/dev-kit" ] && [ "\${4:-}" = "--depth=0" ]; then - exit 0 -fi -if [ "\${1:-}" = "uninstall" ] && [ "\${2:-}" = "-g" ] && [ "\${3:-}" = "@udx/dev-kit" ]; then - exit 0 -fi -exit 1 -EOF - chmod +x "$FAKE_BIN_DIR/npm" - - unset DEV_KIT_HOME DEV_KIT_BIN_DIR - INSTALL_OUTPUT="$(PATH="$FAKE_BIN_DIR:$PATH" DEV_KIT_INSTALL_ARCHIVE_URL="file://$ARCHIVE_FILE" HOME="$TEST_HOME" bash "$INSTALLER_COPY")" - DEV_KIT_HOME="$TEST_HOME/.udx/dev.kit" - DEV_KIT_BIN_DIR="$TEST_HOME/.local/bin" + 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_contains "$INSTALL_OUTPUT" "detected previous npm installation" "install: detects npm install" - assert_contains "$INSTALL_OUTPUT" "curl version is now the single install" "install: removes npm install" - assert_contains "$INSTALL_OUTPUT" "Installed dev.kit" "install: reports success" - assert_file_exists "$DEV_KIT_HOME/bin/dev-kit" "install: command binary present" - assert_file_exists "$DEV_KIT_HOME/lib/commands/repo.sh" "install: repo command present" - assert_symlink_target "$DEV_KIT_BIN_DIR/dev.kit" "$DEV_KIT_HOME/bin/dev-kit" "install: global symlink correct" - assert_contains "$(cat "$FAKE_NPM_LOG")" "list -g @udx/dev-kit --depth=0" "install: checks npm install" - assert_contains "$(cat "$FAKE_NPM_LOG")" "uninstall -g @udx/dev-kit" "install: uninstalls npm package" + cp -R "$DOCKER_REPO" "$DOCKER_ACTION_REPO" + rm -rf "$DOCKER_ACTION_REPO/.rabbit" "$DOCKER_ACTION_REPO/AGENTS.md" - UNINSTALL_OUTPUT="$(HOME="$TEST_HOME" "$DEV_KIT_HOME/bin/dev-kit" uninstall --yes)" - assert_contains "$UNINSTALL_OUTPUT" "Removed dev.kit" "uninstall: reports removal" - assert_file_missing "$DEV_KIT_BIN_DIR/dev.kit" "uninstall: removes symlink" - assert_file_missing "$DEV_KIT_HOME" "uninstall: removes home" + docker_repo_json="$(cd "$DOCKER_ACTION_REPO" && dev.kit repo --json)" + assert_contains "$docker_repo_json" "\"context\":" "docker repo: reports context path" - : > "$FAKE_NPM_LOG" - INSTALL_OUTPUT_STDIN="$(PATH="$FAKE_BIN_DIR:$PATH" DEV_KIT_INSTALL_ARCHIVE_URL="file://$ARCHIVE_FILE" HOME="$TEST_HOME" bash < "$INSTALLER_COPY")" - assert_contains "$INSTALL_OUTPUT_STDIN" "detected previous npm installation" "install stdin: detects npm install" - assert_contains "$INSTALL_OUTPUT_STDIN" "Installed dev.kit" "install stdin: reports success" - assert_file_exists "$DEV_KIT_HOME/bin/dev-kit" "install stdin: command binary present" - assert_symlink_target "$DEV_KIT_BIN_DIR/dev.kit" "$DEV_KIT_HOME/bin/dev-kit" "install stdin: global symlink correct" - assert_contains "$(cat "$FAKE_NPM_LOG")" "uninstall -g @udx/dev-kit" "install stdin: uninstalls npm package" -elif should_run "install"; then - pass "install group skipped outside CI (run with CI=1 to enable)" + docker_context_yaml="${DOCKER_ACTION_REPO}/.rabbit/context.yaml" + assert_contains "$(cat "$docker_context_yaml")" "path: deploy.yml" "docker repo: includes deploy manifest" + assert_contains "$(cat "$docker_context_yaml")" "source_repo: udx/worker" "docker repo: traces manifest owner from version" fi printf "ok - dev.kit suite completed\n" diff --git a/tests/worker-smoke.sh b/tests/worker-smoke.sh new file mode 100755 index 0000000..1214084 --- /dev/null +++ b/tests/worker-smoke.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +IMAGE="${DEV_KIT_TEST_IMAGE:-usabilitydynamics/udx-worker:latest}" +SOURCE_REPO="${DEV_KIT_TEST_SOURCE_REPO:-$REPO_DIR}" +TARGET_REPO="${DEV_KIT_TEST_TARGET_REPO:-$REPO_DIR}" +TARGET_REPO_NAME="${DEV_KIT_TEST_TARGET_REPO_NAME:-target-repo}" +KEEP_HOME="${DEV_KIT_TEST_KEEP_HOME:-0}" +TEST_HOME="${DEV_KIT_TEST_HOME:-$(mktemp -d "${TMPDIR:-/tmp}/dev-kit-worker-home.XXXXXX")}" +DISABLED_TOOLS="${DEV_KIT_TEST_DISABLED_TOOLS:-}" +DISABLED_CREDS="${DEV_KIT_TEST_DISABLED_CREDS:-}" +SCRATCH_MODE="${DEV_KIT_TEST_SCRATCH_MODE:-copy}" +PREPARE_CMD="${DEV_KIT_TEST_PREPARE_CMD:-}" + +usage() { + cat <<'EOF' +Usage: bash tests/worker-smoke.sh + +Runs dev.kit inside the published udx/worker image after installing the current +repo with `npm install -g /workspace`. + +Environment: + DEV_KIT_TEST_IMAGE Worker image to use + DEV_KIT_TEST_SOURCE_REPO Repo containing dev.kit source (default: current repo) + DEV_KIT_TEST_TARGET_REPO Repo to run dev.kit against (default: current repo) + DEV_KIT_TEST_TARGET_REPO_NAME Mount name inside container (default: target-repo) + DEV_KIT_TEST_HOME Host temp dir mounted as container HOME + DEV_KIT_TEST_KEEP_HOME Keep the temp home after exit (default: 0) + DEV_KIT_TEST_DISABLED_TOOLS Comma-separated tools to disable in env config + DEV_KIT_TEST_DISABLED_CREDS Comma-separated credentials to disable in env config + DEV_KIT_TEST_SCRATCH_MODE copy or direct (default: copy) + DEV_KIT_TEST_PREPARE_CMD Shell command to run against the test repo copy before dev.kit +EOF +} + +cleanup() { + if [ "$KEEP_HOME" != "1" ]; then + rm -rf "$TEST_HOME" + fi +} + +trap cleanup EXIT + +if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then + usage + exit 0 +fi + +if ! command -v docker >/dev/null 2>&1; then + printf 'docker is required for tests/worker-smoke.sh\n' >&2 + exit 1 +fi + +if [ ! -d "$SOURCE_REPO" ]; then + printf 'source repo not found: %s\n' "$SOURCE_REPO" >&2 + exit 1 +fi + +if [ ! -d "$TARGET_REPO" ]; then + printf 'target repo not found: %s\n' "$TARGET_REPO" >&2 + exit 1 +fi + +mkdir -p "$TEST_HOME" + +build_yaml_list() { + local csv="$1" + local line="" + + if [ -z "$csv" ]; then + printf '[]' + return 0 + fi + + printf '\n' + printf '%s' "$csv" | tr ',' '\n' | while IFS= read -r line; do + line="$(printf '%s' "$line" | awk '{gsub(/^[[:space:]]+|[[:space:]]+$/, ""); print}')" + [ -n "$line" ] || continue + printf ' - %s\n' "$line" + done +} + +write_env_config() { + local config_dir="$TEST_HOME/.udx/dev.kit/config" + local tools_yaml creds_yaml + + tools_yaml="$(build_yaml_list "$DISABLED_TOOLS")" + creds_yaml="$(build_yaml_list "$DISABLED_CREDS")" + + mkdir -p "$config_dir" + cat > "$config_dir/env.yaml" </tmp/dev-kit-install.log + rm -rf $CONTAINER_TARGET_WORK + if [ \"\$DEV_KIT_TEST_SCRATCH_MODE\" = \"copy\" ]; then + cp -R $CONTAINER_TARGET_SOURCE $CONTAINER_TARGET_WORK + else + ln -s $CONTAINER_TARGET_SOURCE $CONTAINER_TARGET_WORK + fi + cd $CONTAINER_TARGET_WORK + if [ -n \"\$DEV_KIT_TEST_PREPARE_CMD\" ]; then + eval \"\$DEV_KIT_TEST_PREPARE_CMD\" + fi + printf '\n== dev.kit ==\n' + dev.kit + printf '\n== dev.kit env --json ==\n' + 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 + "