contextdevkit 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +592 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/docs/AGENT-PACKAGE-FORMAT.md +140 -0
- package/docs/ARCHITECTURE.md +258 -0
- package/docs/CHANGELOG.md +559 -0
- package/docs/CUSTOMIZING.md +211 -0
- package/docs/LEVELS.md +151 -0
- package/docs/ROADMAP.md +385 -0
- package/docs/SQUAD-PIPELINE-FORMAT.md +258 -0
- package/docs/SQUADS/agent-forge.md +65 -0
- package/docs/SQUADS/design-team.md +161 -0
- package/docs/token-economy-plan.md +135 -0
- package/install.mjs +273 -0
- package/instrucoes.md +274 -0
- package/package.json +46 -0
- package/templates/CLAUDE.md.tpl +133 -0
- package/templates/claude/agents/_TEMPLATE.md +52 -0
- package/templates/claude/agents/accessibility.md +36 -0
- package/templates/claude/agents/agent-architect.md +37 -0
- package/templates/claude/agents/architect.md +39 -0
- package/templates/claude/agents/code-reviewer.md +43 -0
- package/templates/claude/agents/code-security.md +59 -0
- package/templates/claude/agents/context-keeper.md +40 -0
- package/templates/claude/agents/devops.md +40 -0
- package/templates/claude/agents/eval-designer.md +40 -0
- package/templates/claude/agents/forge-orchestrator.md +42 -0
- package/templates/claude/agents/governance-officer.md +45 -0
- package/templates/claude/agents/growth.md +92 -0
- package/templates/claude/agents/infra-security.md +53 -0
- package/templates/claude/agents/landing-architect.md +154 -0
- package/templates/claude/agents/model-router.md +34 -0
- package/templates/claude/agents/packager.md +38 -0
- package/templates/claude/agents/privacy-lgpd.md +64 -0
- package/templates/claude/agents/product-owner.md +51 -0
- package/templates/claude/agents/prompt-engineer.md +33 -0
- package/templates/claude/agents/qa-e2e.md +52 -0
- package/templates/claude/agents/qa-fuzzer.md +24 -0
- package/templates/claude/agents/qa-integration.md +21 -0
- package/templates/claude/agents/qa-orchestrator.md +40 -0
- package/templates/claude/agents/qa-perf.md +40 -0
- package/templates/claude/agents/qa-unit.md +39 -0
- package/templates/claude/agents/rag-designer.md +54 -0
- package/templates/claude/agents/retention.md +85 -0
- package/templates/claude/agents/security.md +48 -0
- package/templates/claude/agents/seo-specialist.md +106 -0
- package/templates/claude/agents/test-engineer.md +48 -0
- package/templates/claude/agents/tool-designer.md +32 -0
- package/templates/claude/agents/ui-designer.md +37 -0
- package/templates/claude/agents/ux-designer.md +38 -0
- package/templates/claude/commands/README.md +95 -0
- package/templates/claude/commands/advise.md +80 -0
- package/templates/claude/commands/audit/analyze-code-ia-practices.md +75 -0
- package/templates/claude/commands/audit/audit.md +35 -0
- package/templates/claude/commands/audit/contract-check.md +21 -0
- package/templates/claude/commands/audit/deep-analysis.md +48 -0
- package/templates/claude/commands/audit/deps-audit.md +49 -0
- package/templates/claude/commands/audit/security-setup.md +35 -0
- package/templates/claude/commands/audit/seo-audit.md +63 -0
- package/templates/claude/commands/audit/tech-debt-sweep.md +35 -0
- package/templates/claude/commands/bug-hunt.md +42 -0
- package/templates/claude/commands/claude-md.md +36 -0
- package/templates/claude/commands/close-version.md +25 -0
- package/templates/claude/commands/context-refresh.md +19 -0
- package/templates/claude/commands/context-stats.md +15 -0
- package/templates/claude/commands/dashboard.md +66 -0
- package/templates/claude/commands/distill-apply.md +19 -0
- package/templates/claude/commands/distill-sessions.md +26 -0
- package/templates/claude/commands/fleet.md +47 -0
- package/templates/claude/commands/forge/forge-audit.md +16 -0
- package/templates/claude/commands/forge/forge-budget.md +16 -0
- package/templates/claude/commands/forge/forge-deprecate.md +16 -0
- package/templates/claude/commands/forge/forge-doctor.md +17 -0
- package/templates/claude/commands/forge/forge-eval.md +16 -0
- package/templates/claude/commands/forge/forge-fallback-test.md +17 -0
- package/templates/claude/commands/forge/forge-killswitch.md +17 -0
- package/templates/claude/commands/forge/forge-list.md +17 -0
- package/templates/claude/commands/forge/forge-new.md +41 -0
- package/templates/claude/commands/forge/forge-policy.md +16 -0
- package/templates/claude/commands/forge/forge-redteam.md +17 -0
- package/templates/claude/commands/forge/forge-refresh-matrix.md +20 -0
- package/templates/claude/commands/forge/forge-route.md +17 -0
- package/templates/claude/commands/forge/forge-show.md +16 -0
- package/templates/claude/commands/landing-page.md +71 -0
- package/templates/claude/commands/log-session.md +59 -0
- package/templates/claude/commands/media-gen.md +93 -0
- package/templates/claude/commands/new-adr.md +30 -0
- package/templates/claude/commands/pipeline/dev-start.md +64 -0
- package/templates/claude/commands/pipeline/pipeline.md +36 -0
- package/templates/claude/commands/pipeline/resume.md +70 -0
- package/templates/claude/commands/pipeline/retro.md +34 -0
- package/templates/claude/commands/pipeline/runs.md +63 -0
- package/templates/claude/commands/pipeline/ship.md +54 -0
- package/templates/claude/commands/pipeline/workflow.md +85 -0
- package/templates/claude/commands/playbook.md +27 -0
- package/templates/claude/commands/predictions-review.md +28 -0
- package/templates/claude/commands/qa/qa-signoff.md +24 -0
- package/templates/claude/commands/qa/scaffold-tests.md +27 -0
- package/templates/claude/commands/qa/test-plan.md +26 -0
- package/templates/claude/commands/qa/visual-test.md +42 -0
- package/templates/claude/commands/roadmap.md +48 -0
- package/templates/claude/commands/setup/aidevtool-from0.md +104 -0
- package/templates/claude/commands/setup/context-config.md +25 -0
- package/templates/claude/commands/setup/context-doctor.md +21 -0
- package/templates/claude/commands/setup/context-level.md +17 -0
- package/templates/claude/commands/setup/setupcontextdevkit.md +121 -0
- package/templates/claude/commands/simulate-impact.md +32 -0
- package/templates/claude/commands/squad.md +44 -0
- package/templates/claude/commands/state.md +21 -0
- package/templates/claude/commands/token-report.md +29 -0
- package/templates/claude/commands/tune-agents.md +35 -0
- package/templates/claude/commands/vcs/claim.md +18 -0
- package/templates/claude/commands/vcs/git.md +83 -0
- package/templates/claude/commands/vcs/release.md +15 -0
- package/templates/claude/commands/vcs/worktree-new.md +18 -0
- package/templates/claude/commands/watch.md +47 -0
- package/templates/contextkit/.env.example +36 -0
- package/templates/contextkit/CLAUDE.child.md.tpl +38 -0
- package/templates/contextkit/README.md +74 -0
- package/templates/contextkit/behaviors-examples.md +183 -0
- package/templates/contextkit/behaviors.md +116 -0
- package/templates/contextkit/best-practices.md +323 -0
- package/templates/contextkit/config.json +66 -0
- package/templates/contextkit/detectors/README.md +45 -0
- package/templates/contextkit/detectors/example-detector.mjs.example +25 -0
- package/templates/contextkit/instrucoes.md +114 -0
- package/templates/contextkit/memory/GLOSSARY.md +13 -0
- package/templates/contextkit/memory/SESSIONS.md +9 -0
- package/templates/contextkit/memory/WORKSPACE.md +7 -0
- package/templates/contextkit/memory/business-rules/_TEMPLATE.md +33 -0
- package/templates/contextkit/memory/decisions/0000-record-architecture-decisions.md +34 -0
- package/templates/contextkit/memory/decisions/_TEMPLATE.md +25 -0
- package/templates/contextkit/memory/predictions/.gitkeep +0 -0
- package/templates/contextkit/memory/roadmap.md +28 -0
- package/templates/contextkit/memory/sessions/.gitkeep +0 -0
- package/templates/contextkit/memory/workflows/.gitkeep +0 -0
- package/templates/contextkit/pipeline/backlog/.gitkeep +0 -0
- package/templates/contextkit/pipeline/conclusion/.gitkeep +0 -0
- package/templates/contextkit/pipeline/devpipeline.md +9 -0
- package/templates/contextkit/pipeline/testing/.gitkeep +0 -0
- package/templates/contextkit/pipeline/working/.gitkeep +0 -0
- package/templates/contextkit/review-protocol.md +214 -0
- package/templates/contextkit/runtime/config/defaults.mjs +215 -0
- package/templates/contextkit/runtime/config/levels.mjs +42 -0
- package/templates/contextkit/runtime/config/load.mjs +105 -0
- package/templates/contextkit/runtime/config/paths.mjs +92 -0
- package/templates/contextkit/runtime/config/presets.mjs +47 -0
- package/templates/contextkit/runtime/config/schema.mjs +88 -0
- package/templates/contextkit/runtime/config/settings-compose.mjs +55 -0
- package/templates/contextkit/runtime/git-hooks/commit-msg.mjs +55 -0
- package/templates/contextkit/runtime/git-hooks/pre-commit.mjs +47 -0
- package/templates/contextkit/runtime/git-hooks/pre-push.mjs +102 -0
- package/templates/contextkit/runtime/hooks/boot-context-readers.mjs +111 -0
- package/templates/contextkit/runtime/hooks/boot-signals.mjs +135 -0
- package/templates/contextkit/runtime/hooks/check-registration.mjs +228 -0
- package/templates/contextkit/runtime/hooks/concurrency-guard.mjs +110 -0
- package/templates/contextkit/runtime/hooks/ledger.mjs +231 -0
- package/templates/contextkit/runtime/hooks/md-extract.mjs +65 -0
- package/templates/contextkit/runtime/hooks/path-classification.mjs +62 -0
- package/templates/contextkit/runtime/hooks/safe-io.mjs +84 -0
- package/templates/contextkit/runtime/hooks/session-digest-core.mjs +85 -0
- package/templates/contextkit/runtime/hooks/session-start.mjs +248 -0
- package/templates/contextkit/runtime/hooks/simulate-gate.mjs +108 -0
- package/templates/contextkit/runtime/hooks/track-edits.mjs +154 -0
- package/templates/contextkit/runtime/providers/media/_adapter.mjs +120 -0
- package/templates/contextkit/runtime/providers/media/nano-banana.mjs +110 -0
- package/templates/contextkit/runtime/providers/media/veo.mjs +162 -0
- package/templates/contextkit/runtime/providers/review/_adapter.mjs +71 -0
- package/templates/contextkit/runtime/providers/review/detect.mjs +115 -0
- package/templates/contextkit/runtime/providers/review/gh.mjs +103 -0
- package/templates/contextkit/runtime/state/state-io.mjs +172 -0
- package/templates/contextkit/runtime/statusline.mjs +51 -0
- package/templates/contextkit/squads/README.md +115 -0
- package/templates/contextkit/squads/_BRIEFING.md.tpl +27 -0
- package/templates/contextkit/squads/agent-forge/README.md +69 -0
- package/templates/contextkit/squads/agent-forge/ROADMAP.md +108 -0
- package/templates/contextkit/squads/agent-forge/best-practices.md +89 -0
- package/templates/contextkit/squads/agent-forge/cli/forge-admin.mjs +132 -0
- package/templates/contextkit/squads/agent-forge/cli/forge-eval-cli.mjs +163 -0
- package/templates/contextkit/squads/agent-forge/cli/forge-new.mjs +97 -0
- package/templates/contextkit/squads/agent-forge/cli/forge-ops.mjs +177 -0
- package/templates/contextkit/squads/agent-forge/lib/architect.mjs +112 -0
- package/templates/contextkit/squads/agent-forge/lib/eval-designer.mjs +133 -0
- package/templates/contextkit/squads/agent-forge/lib/eval-runner.mjs +167 -0
- package/templates/contextkit/squads/agent-forge/lib/governance-officer.mjs +178 -0
- package/templates/contextkit/squads/agent-forge/lib/package-ops.mjs +101 -0
- package/templates/contextkit/squads/agent-forge/lib/packager.mjs +219 -0
- package/templates/contextkit/squads/agent-forge/lib/prompt-gen.mjs +122 -0
- package/templates/contextkit/squads/agent-forge/lib/rag-designer.mjs +102 -0
- package/templates/contextkit/squads/agent-forge/lib/router.mjs +165 -0
- package/templates/contextkit/squads/agent-forge/lib/tool-gen.mjs +113 -0
- package/templates/contextkit/squads/agent-forge/lib/yaml.mjs +47 -0
- package/templates/contextkit/squads/agent-forge/pipeline.yaml +65 -0
- package/templates/contextkit/squads/agent-forge/router/capability-matrix.json +112 -0
- package/templates/contextkit/squads/agent-forge/router/decision-rules.json +120 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/.agentforgerc +12 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/CHANGELOG.md +13 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/LICENSE +5 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/README.md +39 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/go/README.md +10 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/go/agent.go +14 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/go/go.mod +3 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/node/README.md +11 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/node/index.js +53 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/node/package.json +9 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/python/README.md +10 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/python/agent.py +16 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/python/pyproject.toml +10 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/golden.jsonl +1 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/red-team.jsonl +3 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/rubric.yaml +14 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/run-eval.md +17 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/thresholds.yaml +18 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/examples/basic.node.md +17 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/examples/with-fallback.node.md +24 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/examples/with-rag.python.md +20 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/audit.schema.json +23 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/compliance.policy.yaml +43 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/cost.policy.yaml +36 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/fallback-chain.yaml +16 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/quality.policy.yaml +43 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/manifest.yaml +91 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.anthropic.md +19 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.canonical.md +25 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.deepseek.md +21 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.google.md +19 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.ollama.md +21 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.openai.md +20 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/config.yaml +17 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/index/.gitkeep +3 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/ingestion/chunker.config.yaml +6 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/ingestion/sources.yaml +8 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/retrieval/query-template.md +16 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/retrieval/rerank.config.yaml +6 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/anthropic.tools.json +11 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/deepseek.tools.json +14 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/google.tools.json +11 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/ollama.tools.json +14 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/openai.tools.json +14 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/schemas.canonical.json +25 -0
- package/templates/contextkit/starters/tanstack/README.md +86 -0
- package/templates/contextkit/starters/tanstack/index.html +12 -0
- package/templates/contextkit/starters/tanstack/package.json +25 -0
- package/templates/contextkit/starters/tanstack/src/main.tsx +40 -0
- package/templates/contextkit/starters/tanstack/src/router.tsx +12 -0
- package/templates/contextkit/starters/tanstack/src/routes/__root.tsx +10 -0
- package/templates/contextkit/starters/tanstack/src/routes/index.tsx +17 -0
- package/templates/contextkit/starters/tanstack/tsconfig.json +19 -0
- package/templates/contextkit/starters/tanstack/vite.config.ts +10 -0
- package/templates/contextkit/tools/scripts/adr-digest-core.mjs +42 -0
- package/templates/contextkit/tools/scripts/adr-digest.mjs +78 -0
- package/templates/contextkit/tools/scripts/agent-tuning.mjs +74 -0
- package/templates/contextkit/tools/scripts/aiso-audit.mjs +174 -0
- package/templates/contextkit/tools/scripts/audit-shared.mjs +129 -0
- package/templates/contextkit/tools/scripts/claim.mjs +133 -0
- package/templates/contextkit/tools/scripts/claude-md.mjs +123 -0
- package/templates/contextkit/tools/scripts/clean-drive.mjs +78 -0
- package/templates/contextkit/tools/scripts/context-config.mjs +111 -0
- package/templates/contextkit/tools/scripts/context-level.mjs +98 -0
- package/templates/contextkit/tools/scripts/context-pack.mjs +120 -0
- package/templates/contextkit/tools/scripts/contract-scan.mjs +186 -0
- package/templates/contextkit/tools/scripts/dashboard-data.mjs +198 -0
- package/templates/contextkit/tools/scripts/dashboard-html.mjs +215 -0
- package/templates/contextkit/tools/scripts/dashboard-server.mjs +129 -0
- package/templates/contextkit/tools/scripts/dashboard.mjs +107 -0
- package/templates/contextkit/tools/scripts/deep-analysis.mjs +62 -0
- package/templates/contextkit/tools/scripts/deps-audit.mjs +201 -0
- package/templates/contextkit/tools/scripts/detect-stack.mjs +164 -0
- package/templates/contextkit/tools/scripts/distill-detect.mjs +90 -0
- package/templates/contextkit/tools/scripts/doctor.mjs +165 -0
- package/templates/contextkit/tools/scripts/fleet.mjs +170 -0
- package/templates/contextkit/tools/scripts/generate-context.mjs +142 -0
- package/templates/contextkit/tools/scripts/gh-alerts.mjs +117 -0
- package/templates/contextkit/tools/scripts/git.mjs +97 -0
- package/templates/contextkit/tools/scripts/home.mjs +106 -0
- package/templates/contextkit/tools/scripts/mark-simulation.mjs +78 -0
- package/templates/contextkit/tools/scripts/media-gen.mjs +154 -0
- package/templates/contextkit/tools/scripts/pipeline-board.mjs +74 -0
- package/templates/contextkit/tools/scripts/pipeline-prioritize.mjs +68 -0
- package/templates/contextkit/tools/scripts/pipeline-session.mjs +99 -0
- package/templates/contextkit/tools/scripts/pipeline-validate.mjs +136 -0
- package/templates/contextkit/tools/scripts/pipeline.mjs +302 -0
- package/templates/contextkit/tools/scripts/playbook.mjs +123 -0
- package/templates/contextkit/tools/scripts/predictions-review.mjs +113 -0
- package/templates/contextkit/tools/scripts/release.mjs +60 -0
- package/templates/contextkit/tools/scripts/resume.mjs +114 -0
- package/templates/contextkit/tools/scripts/roadmap.mjs +86 -0
- package/templates/contextkit/tools/scripts/runs.mjs +116 -0
- package/templates/contextkit/tools/scripts/seo-audit.mjs +150 -0
- package/templates/contextkit/tools/scripts/session-digest.mjs +89 -0
- package/templates/contextkit/tools/scripts/session-reindex.mjs +91 -0
- package/templates/contextkit/tools/scripts/setup-complete.mjs +69 -0
- package/templates/contextkit/tools/scripts/squad-meta.mjs +23 -0
- package/templates/contextkit/tools/scripts/squad-pipeline-condition.mjs +192 -0
- package/templates/contextkit/tools/scripts/squad-pipeline.mjs +301 -0
- package/templates/contextkit/tools/scripts/squad.mjs +80 -0
- package/templates/contextkit/tools/scripts/stats.mjs +138 -0
- package/templates/contextkit/tools/scripts/sync-check.mjs +235 -0
- package/templates/contextkit/tools/scripts/tech-debt-detectors.mjs +76 -0
- package/templates/contextkit/tools/scripts/tech-debt-scan.mjs +164 -0
- package/templates/contextkit/tools/scripts/token-report.mjs +153 -0
- package/templates/contextkit/tools/scripts/visual-test.mjs +132 -0
- package/templates/contextkit/tools/scripts/watch.mjs +106 -0
- package/templates/contextkit/tools/scripts/workflow.mjs +136 -0
- package/templates/contextkit/tools/scripts/workspace-sync.mjs +220 -0
- package/templates/contextkit/tools/scripts/worktree-new.mjs +50 -0
- package/templates/contextkit/workflows/L1-static-loading.md +59 -0
- package/templates/contextkit/workflows/L2-session-ledger.md +86 -0
- package/templates/contextkit/workflows/L3-multi-session.md +80 -0
- package/templates/contextkit/workflows/L4-squads.md +68 -0
- package/templates/contextkit/workflows/L5-proactive.md +88 -0
- package/templates/contextkit/workflows/README.md +47 -0
- package/templates/contextkit/workflows/playbooks/distillation-cycle.md +74 -0
- package/templates/contextkit/workflows/playbooks/landing-page.md +197 -0
- package/templates/contextkit/workflows/playbooks/security-batch.md +68 -0
- package/templates/contextkit/workflows/playbooks/seo-aiso.md +288 -0
- package/templates/contextkit/workflows/playbooks/simulate-impact.md +83 -0
- package/templates/contextkit/workflows/playbooks/tanstack.md +164 -0
- package/templates/contextkit/workflows/playbooks/tech-debt-sweep.md +77 -0
- package/templates/docs/CHANGELOG.md.tpl +11 -0
- package/templates/gitattributes +3 -0
- package/templates/github/ISSUE_TEMPLATE/bug_report.md +30 -0
- package/templates/github/ISSUE_TEMPLATE/feature_request.md +22 -0
- package/templates/github/PULL_REQUEST_TEMPLATE.md +27 -0
- package/templates/github/dependabot.yml +27 -0
- package/templates/github/workflows/quality.yml +36 -0
- package/templates/github/workflows/security.yml +54 -0
- package/tools/install/cli.mjs +62 -0
- package/tools/install/fs.mjs +56 -0
- package/tools/install/git.mjs +114 -0
- package/tools/install/project.mjs +51 -0
- package/tools/install/uninstall.mjs +54 -0
- package/tools/integration-test-compozy.mjs +88 -0
- package/tools/integration-test-guards.mjs +269 -0
- package/tools/integration-test-tooling-agent-forge.mjs +189 -0
- package/tools/integration-test-tooling-pipeline.mjs +164 -0
- package/tools/integration-test-tooling.mjs +172 -0
- package/tools/integration-test.mjs +228 -0
- package/tools/it-helpers.mjs +60 -0
- package/tools/selfcheck-agent-forge-ops.mjs +107 -0
- package/tools/selfcheck-agent-forge.mjs +304 -0
- package/tools/selfcheck-config.mjs +80 -0
- package/tools/selfcheck-runtime.mjs +135 -0
- package/tools/selfcheck-source.mjs +326 -0
- package/tools/selfcheck.mjs +268 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevPipeline metadata v2 validators (ticket 040, ADR-0022 follow-through).
|
|
3
|
+
*
|
|
4
|
+
* Two concerns kept together because they share a single mental model — the
|
|
5
|
+
* `dependencies: []` DAG over ticket ids:
|
|
6
|
+
*
|
|
7
|
+
* 1. `detectCycles(tasks)` — DFS-based cycle detector. Returns the offending
|
|
8
|
+
* cycle (array of ids in order) or `null`. Pure; no I/O.
|
|
9
|
+
* 2. `blockedBy(task, tasks)` — counts how many of `task.dependencies` are
|
|
10
|
+
* *still open* (stage ≠ conclusion). Used by the board renderer to show
|
|
11
|
+
* the "↘ blocked by N" hint.
|
|
12
|
+
*
|
|
13
|
+
* Also exports the valid enums (`VALID_TYPES`, `VALID_COMPLEXITY`) so the CLI
|
|
14
|
+
* + selfcheck + render layer single-source them.
|
|
15
|
+
*
|
|
16
|
+
* Pure ESM, zero-dep. See [ADR-0022](../../memory/decisions/0022-run-dispatcher-task-dependencies.md)
|
|
17
|
+
* for the DAG semantics this ships even though no dispatcher reads it yet.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export const VALID_TYPES = new Set(['bug', 'chore', 'feature', 'increment', 'spike', 'docs', 'task']);
|
|
21
|
+
export const VALID_COMPLEXITY = new Set(['S', 'M', 'L', 'XL']);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parses a YAML inline array — `[040, 041, 042]` — into a string array.
|
|
25
|
+
* Trims, drops empty entries, returns `[]` for absent / empty / malformed.
|
|
26
|
+
*
|
|
27
|
+
* @param {string | undefined | null} raw
|
|
28
|
+
* @returns {string[]}
|
|
29
|
+
*/
|
|
30
|
+
export function parseInlineArray(raw) {
|
|
31
|
+
if (typeof raw !== 'string') return [];
|
|
32
|
+
const trimmed = raw.trim();
|
|
33
|
+
if (trimmed === '' || trimmed === '[]') return [];
|
|
34
|
+
const stripped = trimmed.startsWith('[') && trimmed.endsWith(']') ? trimmed.slice(1, -1) : trimmed;
|
|
35
|
+
return stripped.split(',').map((s) => s.replace(/['"]/g, '').trim()).filter(Boolean);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validates a single ticket's metadata v2 fields. Returns `{ ok, errors }`.
|
|
40
|
+
* Permissive: missing fields are accepted (backward-compat with v1 tickets).
|
|
41
|
+
*
|
|
42
|
+
* @param {object} task — shape from `listTasks()` (id, type, complexity, dependencies)
|
|
43
|
+
* @returns {{ ok: boolean, errors: string[] }}
|
|
44
|
+
*/
|
|
45
|
+
export function validateTaskV2(task) {
|
|
46
|
+
const errors = [];
|
|
47
|
+
if (task.type && !VALID_TYPES.has(task.type)) errors.push(`${task.id}: unknown type "${task.type}"`);
|
|
48
|
+
if (task.complexity && !VALID_COMPLEXITY.has(task.complexity)) errors.push(`${task.id}: complexity must be S | M | L | XL (got "${task.complexity}")`);
|
|
49
|
+
if (Array.isArray(task.dependencies)) {
|
|
50
|
+
for (const dep of task.dependencies) if (dep === task.id) errors.push(`${task.id}: self-dependency`);
|
|
51
|
+
}
|
|
52
|
+
return { ok: errors.length === 0, errors };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* DFS cycle detector over the task graph.
|
|
57
|
+
*
|
|
58
|
+
* Returns the offending cycle as an ordered array of ids (`['040', '041', '040']`)
|
|
59
|
+
* when one exists, or `null` when the graph is acyclic. An edge to an unknown
|
|
60
|
+
* id (`042` depends on `999` which doesn't exist) is ignored — that's a
|
|
61
|
+
* dangling reference, not a cycle. The validator should surface those
|
|
62
|
+
* separately if/when it grows.
|
|
63
|
+
*
|
|
64
|
+
* @param {Array<{ id: string, dependencies?: string[] }>} tasks
|
|
65
|
+
* @returns {string[] | null}
|
|
66
|
+
*/
|
|
67
|
+
export function detectCycles(tasks) {
|
|
68
|
+
const graph = new Map();
|
|
69
|
+
for (const t of tasks) graph.set(String(t.id), (t.dependencies || []).map(String));
|
|
70
|
+
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
71
|
+
const colour = new Map();
|
|
72
|
+
for (const id of graph.keys()) colour.set(id, WHITE);
|
|
73
|
+
const stack = [];
|
|
74
|
+
|
|
75
|
+
function dfs(node) {
|
|
76
|
+
colour.set(node, GRAY);
|
|
77
|
+
stack.push(node);
|
|
78
|
+
for (const next of graph.get(node) || []) {
|
|
79
|
+
if (!graph.has(next)) continue; // dangling reference — not a cycle
|
|
80
|
+
if (colour.get(next) === GRAY) {
|
|
81
|
+
const start = stack.indexOf(next);
|
|
82
|
+
return [...stack.slice(start), next];
|
|
83
|
+
}
|
|
84
|
+
if (colour.get(next) === WHITE) {
|
|
85
|
+
const cycle = dfs(next);
|
|
86
|
+
if (cycle) return cycle;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
colour.set(node, BLACK);
|
|
90
|
+
stack.pop();
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const id of graph.keys()) {
|
|
95
|
+
if (colour.get(id) !== WHITE) continue;
|
|
96
|
+
const cycle = dfs(id);
|
|
97
|
+
if (cycle) return cycle;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Lints every task's type / complexity / dependencies and refuses on cycles.
|
|
104
|
+
* Returns the list of error strings (empty when clean). Pure; the CLI prints +
|
|
105
|
+
* exits. Lives here (not in pipeline.mjs) so pipeline.mjs stays under budget.
|
|
106
|
+
*
|
|
107
|
+
* @param {Array<{ id: string, type?: string, complexity?: string, dependencies?: string[] }>} tasks
|
|
108
|
+
* @returns {string[]}
|
|
109
|
+
*/
|
|
110
|
+
export function runValidate(tasks) {
|
|
111
|
+
const errors = tasks.flatMap((t) => validateTaskV2(t).errors);
|
|
112
|
+
const cycle = detectCycles(tasks);
|
|
113
|
+
if (cycle) errors.push(`dependency cycle: ${cycle.join(' → ')}`);
|
|
114
|
+
return errors;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Counts how many of `task.dependencies` are still open — stage is `backlog`,
|
|
119
|
+
* `working`, or `testing`. Used by the board renderer to show "↘ blocked by N".
|
|
120
|
+
* Dangling references (deps that don't exist in the task set) are silently
|
|
121
|
+
* ignored.
|
|
122
|
+
*
|
|
123
|
+
* @param {{ dependencies?: string[] }} task
|
|
124
|
+
* @param {Array<{ id: string, stage: string }>} allTasks
|
|
125
|
+
* @returns {number}
|
|
126
|
+
*/
|
|
127
|
+
export function blockedBy(task, allTasks) {
|
|
128
|
+
if (!Array.isArray(task.dependencies) || task.dependencies.length === 0) return 0;
|
|
129
|
+
const byId = new Map(allTasks.map((t) => [String(t.id), t.stage]));
|
|
130
|
+
let blocked = 0;
|
|
131
|
+
for (const dep of task.dependencies) {
|
|
132
|
+
const stage = byId.get(String(dep));
|
|
133
|
+
if (stage && stage !== 'conclusion') blocked += 1;
|
|
134
|
+
}
|
|
135
|
+
return blocked;
|
|
136
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* DevPipeline engine — execution board (≠ roadmap). Stages: backlog → working
|
|
4
|
+
* (ADR-0015 §B) → testing → conclusion. Renderer in pipeline-board.mjs,
|
|
5
|
+
* scoring in pipeline-prioritize.mjs, session-coupled start/stop in
|
|
6
|
+
* pipeline-session.mjs, schema-v2 validators in pipeline-validate.mjs.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { resolve } from 'node:path';
|
|
10
|
+
import { loadConfigSync } from '../../runtime/config/load.mjs';
|
|
11
|
+
import { pathsFor } from '../../runtime/config/paths.mjs';
|
|
12
|
+
import { writeFileAtomicSync } from '../../runtime/hooks/safe-io.mjs';
|
|
13
|
+
import { wsjfScore, wsjfToPriority, severityToPriority, bugSeverityToPriority, slaDue, DEFAULTS } from './pipeline-prioritize.mjs';
|
|
14
|
+
import { renderBoard, renderKnownBugs } from './pipeline-board.mjs';
|
|
15
|
+
import { parseInlineArray, runValidate } from './pipeline-validate.mjs';
|
|
16
|
+
|
|
17
|
+
const ROOT = process.cwd();
|
|
18
|
+
const PIPE = pathsFor(ROOT).pipeline;
|
|
19
|
+
const STAGES = { backlog: 'backlog', working: 'working', testing: 'testing', conclusion: 'conclusion' };
|
|
20
|
+
const STATUS = { backlog: 'backlog', working: 'working', testing: 'testing', conclusion: 'done' };
|
|
21
|
+
const CFG = loadConfigSync(ROOT).pipeline || {};
|
|
22
|
+
const BANDS = CFG.wsjfBands || DEFAULTS.wsjfBands;
|
|
23
|
+
const SEVMAP = CFG.severityPriority || DEFAULTS.severityPriority;
|
|
24
|
+
const SLADAYS = CFG.slaDays || DEFAULTS.slaDays;
|
|
25
|
+
|
|
26
|
+
function ensureDirs() {
|
|
27
|
+
for (const s of Object.keys(STAGES)) mkdirSync(resolve(PIPE, s), { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseFrontmatter(text) {
|
|
31
|
+
const m = text.match(/^---\n([\s\S]*?)\n---/);
|
|
32
|
+
const fm = {};
|
|
33
|
+
if (m) for (const line of m[1].split('\n')) {
|
|
34
|
+
const i = line.indexOf(':');
|
|
35
|
+
if (i > 0) fm[line.slice(0, i).trim()] = line.slice(i + 1).trim();
|
|
36
|
+
}
|
|
37
|
+
return fm;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function listTasks() {
|
|
41
|
+
ensureDirs();
|
|
42
|
+
const tasks = [];
|
|
43
|
+
for (const stage of Object.keys(STAGES)) {
|
|
44
|
+
let files = [];
|
|
45
|
+
try {
|
|
46
|
+
files = readdirSync(resolve(PIPE, stage)).filter((f) => f.endsWith('.md'));
|
|
47
|
+
} catch {
|
|
48
|
+
/* none */
|
|
49
|
+
}
|
|
50
|
+
for (const f of files) {
|
|
51
|
+
const fm = parseFrontmatter(readFileSync(resolve(PIPE, stage, f), 'utf-8'));
|
|
52
|
+
tasks.push({ stage, file: f, id: fm.id || f.split('-')[0], title: fm.title || f, type: fm.type || 'task', priority: fm.priority || 'P2', severity: fm.severity || '', wsjf: fm.wsjf || '', bugType: fm.bugType || '', sla: fm.sla || '', roadmap: fm.roadmap || '', source: fm.source || '', created: fm.created || '', complexity: fm.complexity || '', dependencies: parseInlineArray(fm.dependencies) });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return tasks.sort((a, b) => (a.priority + a.id).localeCompare(b.priority + b.id));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function nextId() {
|
|
59
|
+
const ids = listTasks().map((t) => parseInt(t.id, 10)).filter((n) => !Number.isNaN(n));
|
|
60
|
+
return String((ids.length ? Math.max(...ids) : 0) + 1).padStart(3, '0');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function slug(s) {
|
|
64
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40) || 'task';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getArg(name) {
|
|
68
|
+
const i = process.argv.indexOf(`--${name}`);
|
|
69
|
+
return i !== -1 ? process.argv[i + 1] : undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Writes one backlog task file and returns its id. Shared by add + ingest. */
|
|
73
|
+
function writeTask({ type = 'task', priority = 'P2', title, sla = '', roadmap = '', source = '', context = '', severity = '', wsjf = '', bugType = '', complexity = '', dependencies = [] }) {
|
|
74
|
+
ensureDirs();
|
|
75
|
+
const created = new Date().toISOString().slice(0, 10);
|
|
76
|
+
const deps = Array.isArray(dependencies) ? `[${dependencies.join(', ')}]` : '[]';
|
|
77
|
+
const buildBody = (id) => [
|
|
78
|
+
'---',
|
|
79
|
+
`id: ${id}`,
|
|
80
|
+
`title: ${title}`,
|
|
81
|
+
`type: ${type}`,
|
|
82
|
+
`priority: ${priority}`,
|
|
83
|
+
`severity: ${severity}`,
|
|
84
|
+
`wsjf: ${wsjf}`,
|
|
85
|
+
`bugType: ${bugType}`,
|
|
86
|
+
`complexity: ${complexity}`,
|
|
87
|
+
`dependencies: ${deps}`,
|
|
88
|
+
`status: backlog`,
|
|
89
|
+
`created: ${created}`,
|
|
90
|
+
`sla: ${sla || slaDue(priority, created, SLADAYS)}`,
|
|
91
|
+
`roadmap: ${roadmap}`,
|
|
92
|
+
`source: ${source}`,
|
|
93
|
+
'---',
|
|
94
|
+
'',
|
|
95
|
+
`## ${title}`,
|
|
96
|
+
'',
|
|
97
|
+
`**Context / why:** ${context}`,
|
|
98
|
+
'',
|
|
99
|
+
'**Acceptance criteria:**',
|
|
100
|
+
'- [ ] ',
|
|
101
|
+
'',
|
|
102
|
+
].join('\n');
|
|
103
|
+
// Collision-safe id allocation: claim the file with an exclusive create (`wx`).
|
|
104
|
+
// If two concurrent `add`s race for the same id, the loser sees EEXIST (or finds
|
|
105
|
+
// the id already taken) and retries with the next id — two tasks never share one.
|
|
106
|
+
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
107
|
+
const id = nextId();
|
|
108
|
+
if (listTasks().some((t) => t.id === id)) continue;
|
|
109
|
+
try {
|
|
110
|
+
writeFileSync(resolve(PIPE, 'backlog', `${id}-${slug(title)}.md`), buildBody(id), { encoding: 'utf-8', flag: 'wx' });
|
|
111
|
+
return id;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err?.code === 'EEXIST') continue;
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
throw new Error('pipeline: could not allocate a unique task id (50 attempts exhausted)');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function add() {
|
|
121
|
+
const type = getArg('type') || 'task';
|
|
122
|
+
const title = getArg('title');
|
|
123
|
+
if (!title) {
|
|
124
|
+
console.error('Usage: pipeline.mjs add --type <bug|feature|chore> --title "..." [--priority P0-P3] [--severity S1-S4] [--wsjf uv,tc,rr,js] [--bug-type <t>]');
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
const sev = getArg('severity');
|
|
128
|
+
const wsjfArg = getArg('wsjf');
|
|
129
|
+
let priority = getArg('priority');
|
|
130
|
+
let wsjf = '';
|
|
131
|
+
if (wsjfArg) {
|
|
132
|
+
const [uv, tc, rr, js] = wsjfArg.split(',').map(Number);
|
|
133
|
+
wsjf = wsjfScore({ userValue: uv, timeCriticality: tc, riskReduction: rr, jobSize: js });
|
|
134
|
+
priority = priority || wsjfToPriority(wsjf, BANDS);
|
|
135
|
+
} else if (sev) {
|
|
136
|
+
priority = priority || bugSeverityToPriority(sev, SEVMAP);
|
|
137
|
+
}
|
|
138
|
+
priority = priority || 'P2';
|
|
139
|
+
const id = writeTask({ type, priority, title, sla: getArg('sla') || '', roadmap: getArg('roadmap') || '', source: getArg('source') || '', severity: sev || '', wsjf, bugType: getArg('bug-type') || '', complexity: getArg('complexity') || '', dependencies: parseInlineArray(getArg('depends-on')) });
|
|
140
|
+
sync();
|
|
141
|
+
console.log(`✅ Added ${type} ${id} (${priority}${wsjf ? `, WSJF ${wsjf}` : ''}${sev ? `, ${sev}` : ''}) to backlog: ${title}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Bulk-import findings from a JSON file (e.g. `tech-debt-scan --json`) into the
|
|
146
|
+
* backlog — one task per finding, priority auto-mapped from `severity`. Idempotent:
|
|
147
|
+
* a finding whose `source` fingerprint already has a non-concluded task is skipped,
|
|
148
|
+
* so re-running an analysis never spams duplicates. The user re-prioritizes freely.
|
|
149
|
+
*/
|
|
150
|
+
function ingest() {
|
|
151
|
+
const fileArg = process.argv[3] && !process.argv[3].startsWith('--') ? process.argv[3] : getArg('file');
|
|
152
|
+
if (!fileArg) {
|
|
153
|
+
console.error('Usage: pipeline.mjs ingest <findings.json> [--type chore]');
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
let data;
|
|
157
|
+
try {
|
|
158
|
+
data = JSON.parse(readFileSync(resolve(ROOT, fileArg), 'utf-8').replace(/^/, ''));
|
|
159
|
+
} catch (err) {
|
|
160
|
+
console.error(`Could not read findings JSON: ${err?.message ?? err}`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
const findings = Array.isArray(data) ? data : data.findings || [];
|
|
164
|
+
const type = getArg('type') || 'chore';
|
|
165
|
+
const taken = new Set(listTasks().filter((t) => t.stage !== 'conclusion').map((t) => t.source).filter(Boolean));
|
|
166
|
+
let added = 0;
|
|
167
|
+
let skipped = 0;
|
|
168
|
+
for (const f of findings) {
|
|
169
|
+
const where = f.path ? `${f.path}${f.line ? ':' + f.line : ''}` : '';
|
|
170
|
+
const source = f.source || `${f.kind || 'finding'}:${where}`;
|
|
171
|
+
if (taken.has(source)) {
|
|
172
|
+
skipped += 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const priority = /^P[0-3]$/.test(f.priority || '') ? f.priority : severityToPriority(f.severity);
|
|
176
|
+
const title = String(f.title || `${f.kind ? f.kind + ': ' : ''}${where || 'finding'}`).slice(0, 80);
|
|
177
|
+
writeTask({ type, priority, title, source, context: f.message || '' });
|
|
178
|
+
taken.add(source);
|
|
179
|
+
added += 1;
|
|
180
|
+
}
|
|
181
|
+
sync();
|
|
182
|
+
console.log(`✅ Ingested ${added} finding(s) into backlog (${skipped} already present). Re-prioritize any with: pipeline.mjs prioritize <id> <P0-P3>`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** User override: change a task's auto-assigned priority. */
|
|
186
|
+
function prioritize() {
|
|
187
|
+
const id = process.argv[3];
|
|
188
|
+
const priority = process.argv[4];
|
|
189
|
+
if (!id || !/^P[0-3]$/.test(priority || '')) {
|
|
190
|
+
console.error('Usage: pipeline.mjs prioritize <id> <P0|P1|P2|P3>');
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
const task = listTasks().find((t) => t.id === id.padStart(3, '0') || t.id === id);
|
|
194
|
+
if (!task) {
|
|
195
|
+
console.error(`No task with id ${id}.`);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
const p = resolve(PIPE, task.stage, task.file);
|
|
199
|
+
writeFileAtomicSync(p, readFileSync(p, 'utf-8').replace(/^priority:.*$/m, `priority: ${priority}`));
|
|
200
|
+
sync();
|
|
201
|
+
console.log(`✅ ${task.id} priority → ${priority}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** (Re)score a task with WSJF and re-derive its priority + SLA due date. */
|
|
205
|
+
function setWsjf() {
|
|
206
|
+
const id = process.argv[3];
|
|
207
|
+
const [uv, tc, rr, js] = process.argv.slice(4).map(Number);
|
|
208
|
+
if (!id || [uv, tc, rr, js].some((n) => Number.isNaN(n))) {
|
|
209
|
+
console.error('Usage: pipeline.mjs wsjf <id> <userValue> <timeCriticality> <riskReduction> <jobSize> (each 1-10)');
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
const task = listTasks().find((t) => t.id === id.padStart(3, '0') || t.id === id);
|
|
213
|
+
if (!task) {
|
|
214
|
+
console.error(`No task with id ${id}.`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
const score = wsjfScore({ userValue: uv, timeCriticality: tc, riskReduction: rr, jobSize: js });
|
|
218
|
+
const pr = wsjfToPriority(score, BANDS);
|
|
219
|
+
const due = slaDue(pr, task.created, SLADAYS);
|
|
220
|
+
const p = resolve(PIPE, task.stage, task.file);
|
|
221
|
+
writeFileAtomicSync(p, readFileSync(p, 'utf-8')
|
|
222
|
+
.replace(/^priority:.*$/m, `priority: ${pr}`)
|
|
223
|
+
.replace(/^wsjf:.*$/m, `wsjf: ${score}`)
|
|
224
|
+
.replace(/^sla:.*$/m, `sla: ${due}`));
|
|
225
|
+
sync();
|
|
226
|
+
console.log(`✅ ${task.id} WSJF ${score} → ${pr} (SLA ${due})`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function move() {
|
|
230
|
+
const id = process.argv[3];
|
|
231
|
+
const stage = process.argv[4];
|
|
232
|
+
if (!id || !STAGES[stage]) {
|
|
233
|
+
console.error('Usage: pipeline.mjs move <id> <backlog|working|testing|conclusion>');
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
const task = listTasks().find((t) => t.id === id.padStart(3, '0') || t.id === id);
|
|
237
|
+
if (!task) {
|
|
238
|
+
console.error(`No task with id ${id}.`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
const from = resolve(PIPE, task.stage, task.file);
|
|
242
|
+
const to = resolve(PIPE, stage, task.file);
|
|
243
|
+
let text = readFileSync(from, 'utf-8').replace(/^(status:).*$/m, `status: ${STATUS[stage]}`);
|
|
244
|
+
if (stage === 'conclusion' && !/^concluded:/m.test(text)) {
|
|
245
|
+
text = text.replace(/^---\n([\s\S]*?)\n---/, (full, fm) => `---\n${fm}\nconcluded: ${new Date().toISOString().slice(0, 10)}\n---`);
|
|
246
|
+
}
|
|
247
|
+
writeFileAtomicSync(from, text);
|
|
248
|
+
renameSync(from, to);
|
|
249
|
+
sync();
|
|
250
|
+
// ADR-0015 §C — fire-and-forget state.json mirror (observability, best-effort).
|
|
251
|
+
import('../../runtime/state/state-io.mjs').then((m) => m.readState(PIPE, task.id) && m.writeState(PIPE, task.id, stage === 'conclusion' ? { status: STATUS[stage], endedAt: Date.now() } : { status: STATUS[stage] })).catch(() => {});
|
|
252
|
+
console.log(`✅ Moved ${task.id} → ${stage}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
/** Session-coupled transitions (start/stop) — see `pipeline-session.mjs`. */
|
|
257
|
+
async function sessionCli(verb, marker, dest) {
|
|
258
|
+
const id = process.argv[3];
|
|
259
|
+
if (!id) { console.error(`Usage: pipeline.mjs ${verb} <id>`); process.exit(1); }
|
|
260
|
+
const sess = await import('./pipeline-session.mjs');
|
|
261
|
+
try {
|
|
262
|
+
const result = await (verb === 'start' ? sess.startTask : sess.stopTask)(PIPE, id, sync);
|
|
263
|
+
console.log(`${marker} ${result.id} → ${dest}`);
|
|
264
|
+
} catch (err) { console.error(err.message); process.exit(1); }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function sync() {
|
|
268
|
+
ensureDirs();
|
|
269
|
+
const all = listTasks();
|
|
270
|
+
writeFileAtomicSync(resolve(PIPE, 'devpipeline.md'), renderBoard(all));
|
|
271
|
+
writeFileAtomicSync(resolve(PIPE, 'known-bugs.md'), renderKnownBugs(all));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Print + regenerate the known-bugs map. */
|
|
275
|
+
function bugs() {
|
|
276
|
+
sync();
|
|
277
|
+
const open = listTasks().filter((t) => t.type === 'bug' && t.stage !== 'conclusion');
|
|
278
|
+
console.log(`🐞 Known bugs: ${open.length} open. Map → contextkit/pipeline/known-bugs.md`);
|
|
279
|
+
for (const b of open) console.log(` ${b.severity || '—'} ${b.priority} ${b.id} ${b.bugType || ''} — ${b.title}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const cmd = process.argv[2];
|
|
283
|
+
if (cmd === 'add') add();
|
|
284
|
+
else if (cmd === 'ingest') ingest();
|
|
285
|
+
else if (cmd === 'prioritize') prioritize();
|
|
286
|
+
else if (cmd === 'wsjf') setWsjf();
|
|
287
|
+
else if (cmd === 'bugs') bugs();
|
|
288
|
+
else if (cmd === 'move') move();
|
|
289
|
+
else if (cmd === 'start') await sessionCli('start', '▶', 'working/ (owner: this session)');
|
|
290
|
+
else if (cmd === 'stop') await sessionCli('stop', '⏸', 'backlog/ (released)');
|
|
291
|
+
else if (cmd === 'validate') { const t = listTasks(); const e = runValidate(t); e.length ? (e.forEach((m) => console.error(`✗ ${m}`)), process.exit(1)) : console.log(`✅ ${t.length} tickets validated.`); }
|
|
292
|
+
else if (cmd === 'sync') {
|
|
293
|
+
sync();
|
|
294
|
+
console.log('✅ devpipeline.md regenerated.');
|
|
295
|
+
} else if (cmd === 'list') {
|
|
296
|
+
const all = listTasks();
|
|
297
|
+
if (process.argv.includes('--json')) console.log(JSON.stringify(all, null, 2));
|
|
298
|
+
else for (const t of all) console.log(`[${t.stage}] ${t.id} ${t.priority} ${t.type} — ${t.title}`);
|
|
299
|
+
} else {
|
|
300
|
+
console.error('Usage: pipeline.mjs <add|ingest|prioritize|wsjf|bugs|move|start|stop|validate|sync|list>');
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Playbook registry + runner (roadmap #8 / L6).
|
|
4
|
+
*
|
|
5
|
+
* Playbooks are the reusable procedures in `contextkit/workflows/playbooks/` — the
|
|
6
|
+
* "why / how / anti-patterns" behind a slash command. This turns that folder into
|
|
7
|
+
* a managed layer: list what exists (the registry), show one, and record a run so
|
|
8
|
+
* repeatable procedures become tracked, auditable assets. "Running" a playbook =
|
|
9
|
+
* recording the run and printing the procedure for the agent to follow; the steps
|
|
10
|
+
* themselves are executed by Claude, not this script.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node contextkit/tools/scripts/playbook.mjs list
|
|
14
|
+
* node contextkit/tools/scripts/playbook.mjs show <name>
|
|
15
|
+
* node contextkit/tools/scripts/playbook.mjs run <name> [outcome note...]
|
|
16
|
+
* node contextkit/tools/scripts/playbook.mjs runs
|
|
17
|
+
*
|
|
18
|
+
* Zero third-party deps; defensive (never throws fatally on a missing dir).
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readdirSync, readFileSync, appendFileSync, mkdirSync } from 'node:fs';
|
|
21
|
+
import { join, resolve } from 'node:path';
|
|
22
|
+
import { pathsFor } from '../../runtime/config/paths.mjs';
|
|
23
|
+
|
|
24
|
+
const ROOT = process.cwd();
|
|
25
|
+
const P = pathsFor(ROOT);
|
|
26
|
+
const PLAYBOOKS_DIR = P.playbooks;
|
|
27
|
+
const RUNS_LOG = resolve(P.memory, 'playbook-runs.md');
|
|
28
|
+
|
|
29
|
+
/** All playbook files in the registry, sorted. */
|
|
30
|
+
function listFiles() {
|
|
31
|
+
try {
|
|
32
|
+
return readdirSync(PLAYBOOKS_DIR).filter((f) => f.endsWith('.md')).sort();
|
|
33
|
+
} catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** First `# ` heading of a playbook, as its human title. */
|
|
39
|
+
function titleOf(file) {
|
|
40
|
+
try {
|
|
41
|
+
const heading = readFileSync(join(PLAYBOOKS_DIR, file), 'utf-8').split('\n').find((l) => l.startsWith('# '));
|
|
42
|
+
return heading ? heading.replace(/^#\s+/, '').trim() : file;
|
|
43
|
+
} catch {
|
|
44
|
+
return file;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Resolves a user-given name to an existing playbook file (exact or sans-.md). */
|
|
49
|
+
function resolveName(name) {
|
|
50
|
+
if (!name) return null;
|
|
51
|
+
const files = listFiles();
|
|
52
|
+
const want = name.endsWith('.md') ? name : `${name}.md`;
|
|
53
|
+
return files.find((f) => f === want) || files.find((f) => f.replace(/\.md$/, '') === name) || null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function unknown(name) {
|
|
57
|
+
console.error(`Unknown playbook "${name}". Try: playbook.mjs list`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function list() {
|
|
62
|
+
const files = listFiles();
|
|
63
|
+
if (files.length === 0) {
|
|
64
|
+
console.log('No playbooks found in contextkit/workflows/playbooks/.');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log(`📓 Playbooks (${files.length}) — contextkit/workflows/playbooks/\n`);
|
|
68
|
+
for (const f of files) console.log(` • ${f.replace(/\.md$/, '')} — ${titleOf(f)}`);
|
|
69
|
+
console.log('\nRun one with: node contextkit/tools/scripts/playbook.mjs run <name>');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function show(name) {
|
|
73
|
+
const file = resolveName(name);
|
|
74
|
+
if (!file) unknown(name);
|
|
75
|
+
console.log(`# contextkit/workflows/playbooks/${file}\n`);
|
|
76
|
+
console.log(readFileSync(join(PLAYBOOKS_DIR, file), 'utf-8'));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Records a run (best-effort) and prints the procedure for the agent to follow. */
|
|
80
|
+
function run(name, note) {
|
|
81
|
+
const file = resolveName(name);
|
|
82
|
+
if (!file) unknown(name);
|
|
83
|
+
const when = new Date().toISOString();
|
|
84
|
+
try {
|
|
85
|
+
mkdirSync(P.memory, { recursive: true });
|
|
86
|
+
if (!existsSync(RUNS_LOG)) {
|
|
87
|
+
appendFileSync(RUNS_LOG, '# Playbook runs\n\n| When | Playbook | Note |\n| --- | --- | --- |\n', 'utf-8');
|
|
88
|
+
}
|
|
89
|
+
appendFileSync(RUNS_LOG, `| ${when} | ${file.replace(/\.md$/, '')} | ${note || '—'} |\n`, 'utf-8');
|
|
90
|
+
} catch {
|
|
91
|
+
/* tracking is best-effort — never block the run */
|
|
92
|
+
}
|
|
93
|
+
console.log(`▶️ Running playbook: ${file.replace(/\.md$/, '')} (recorded ${when.slice(0, 10)})`);
|
|
94
|
+
console.log(' Follow the procedure below and apply its judgment:\n');
|
|
95
|
+
console.log(readFileSync(join(PLAYBOOKS_DIR, file), 'utf-8'));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function runs() {
|
|
99
|
+
if (!existsSync(RUNS_LOG)) {
|
|
100
|
+
console.log('No playbook runs recorded yet.');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
console.log(readFileSync(RUNS_LOG, 'utf-8'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function main() {
|
|
107
|
+
const [cmd, name, ...rest] = process.argv.slice(2);
|
|
108
|
+
switch (cmd) {
|
|
109
|
+
case 'list':
|
|
110
|
+
return list();
|
|
111
|
+
case 'show':
|
|
112
|
+
return show(name);
|
|
113
|
+
case 'run':
|
|
114
|
+
return run(name, rest.join(' '));
|
|
115
|
+
case 'runs':
|
|
116
|
+
return runs();
|
|
117
|
+
default:
|
|
118
|
+
console.error('Usage: playbook.mjs <list | show <name> | run <name> [note] | runs>');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
main();
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Closes the predicted-vs-actual loop (ancestor parity).
|
|
4
|
+
*
|
|
5
|
+
* `/simulate-impact` (mark-simulation.mjs) writes a prediction file per run with
|
|
6
|
+
* an empty "Actual" section. This script fills that section from the session
|
|
7
|
+
* ledger: the paths actually changed (`modifications[]`) compared against what
|
|
8
|
+
* the simulation predicted (`simulations[].coveredPaths`). It computes the delta
|
|
9
|
+
* in both directions so a later review can see where the prediction was right or
|
|
10
|
+
* wrong.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node contextkit/tools/scripts/predictions-review.mjs # current session
|
|
14
|
+
* node contextkit/tools/scripts/predictions-review.mjs <sessionId>
|
|
15
|
+
*
|
|
16
|
+
* Defensive: never throws fatally; exits 0 with a message when there is nothing
|
|
17
|
+
* to review. Zero third-party deps (hot-path invariant).
|
|
18
|
+
*/
|
|
19
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
20
|
+
import { resolve } from 'node:path';
|
|
21
|
+
import { readLedger, readMostRecentLedger, toRepoRelative } from '../../runtime/hooks/ledger.mjs';
|
|
22
|
+
|
|
23
|
+
const ROOT = process.cwd();
|
|
24
|
+
const PREDICTIONS_PREFIX = 'contextkit/memory/predictions/';
|
|
25
|
+
|
|
26
|
+
/** True when a covered entry (file, or dir prefix ending in `/`) matches a path. */
|
|
27
|
+
function covers(coveredEntry, actualPath) {
|
|
28
|
+
if (coveredEntry.endsWith('/')) return actualPath.startsWith(coveredEntry);
|
|
29
|
+
return actualPath === coveredEntry;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Unique repo-relative paths this session actually changed (excludes prediction files). */
|
|
33
|
+
function actualChangedPaths(ledger) {
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
for (const mod of ledger.modifications || []) {
|
|
36
|
+
const rel = toRepoRelative(mod?.path);
|
|
37
|
+
if (!rel || rel.startsWith(PREDICTIONS_PREFIX)) continue;
|
|
38
|
+
seen.add(rel);
|
|
39
|
+
}
|
|
40
|
+
return [...seen];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const fmt = (paths) => (paths.length ? paths.map((p) => `\`${p}\``).join(', ') : '— none');
|
|
44
|
+
|
|
45
|
+
/** Builds the filled "Actual" section for one prediction vs the actual changes. */
|
|
46
|
+
function actualSection(covered, actual, date) {
|
|
47
|
+
const predictedHit = covered.filter((c) => actual.some((a) => covers(c, a)));
|
|
48
|
+
const predictedMiss = covered.filter((c) => !actual.some((a) => covers(c, a)));
|
|
49
|
+
const unforeseen = actual.filter((a) => !covered.some((c) => covers(c, a)));
|
|
50
|
+
return [
|
|
51
|
+
`## Actual (reviewed ${date})`,
|
|
52
|
+
'',
|
|
53
|
+
`- **Paths actually changed this session**: ${fmt(actual)}`,
|
|
54
|
+
`- **Predicted ✓ and changed**: ${fmt(predictedHit)}`,
|
|
55
|
+
`- **Predicted ✗ but NOT changed**: ${fmt(predictedMiss)}`,
|
|
56
|
+
`- **Changed but NOT predicted**: ${fmt(unforeseen)}`,
|
|
57
|
+
'- **Risk accuracy**: _was the `/simulate-impact` risk level right? note it here_',
|
|
58
|
+
'',
|
|
59
|
+
].join('\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Replaces the trailing "## Actual" section (stub or prior review) in place. */
|
|
63
|
+
function withActual(content, section) {
|
|
64
|
+
return /^## Actual/m.test(content)
|
|
65
|
+
? content.replace(/^## Actual[\s\S]*$/m, section)
|
|
66
|
+
: `${content.trimEnd()}\n\n${section}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function reviewOne(simulation, actual, date) {
|
|
70
|
+
const rel = simulation?.predictionFile;
|
|
71
|
+
if (typeof rel !== 'string' || !rel) return { skipped: true };
|
|
72
|
+
const abs = resolve(ROOT, rel);
|
|
73
|
+
let content;
|
|
74
|
+
try {
|
|
75
|
+
content = await readFile(abs, 'utf-8');
|
|
76
|
+
} catch {
|
|
77
|
+
return { skipped: true, rel };
|
|
78
|
+
}
|
|
79
|
+
const covered = Array.isArray(simulation.coveredPaths) ? simulation.coveredPaths : [];
|
|
80
|
+
await writeFile(abs, withActual(content, actualSection(covered, actual, date)), 'utf-8');
|
|
81
|
+
return { reviewed: true, rel };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function main() {
|
|
85
|
+
const argSid = process.argv[2];
|
|
86
|
+
const found = argSid ? { sessionId: argSid, ledger: await readLedger(argSid) } : await readMostRecentLedger();
|
|
87
|
+
if (!found?.ledger) {
|
|
88
|
+
console.log('ℹ️ No session ledger found — nothing to review.');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const simulations = (found.ledger.simulations || []).filter((s) => s?.predictionFile);
|
|
92
|
+
if (simulations.length === 0) {
|
|
93
|
+
console.log(`ℹ️ Session ${found.sessionId.slice(0, 8)} has no /simulate-impact predictions to review.`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const actual = actualChangedPaths(found.ledger);
|
|
97
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
98
|
+
let reviewed = 0;
|
|
99
|
+
for (const simulation of simulations) {
|
|
100
|
+
const outcome = await reviewOne(simulation, actual, date);
|
|
101
|
+
if (outcome.reviewed) {
|
|
102
|
+
reviewed++;
|
|
103
|
+
console.log(`✅ Closed predicted-vs-actual loop: ${outcome.rel}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (reviewed === 0) console.log('ℹ️ Prediction file(s) missing — nothing written.');
|
|
107
|
+
else console.log(`\n${reviewed} prediction(s) reviewed against ${actual.length} changed path(s).`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main().catch((err) => {
|
|
111
|
+
console.error('❌ predictions-review failed:', err?.message ?? err);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|