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,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Agent-tuning signals — the deterministic half of /tune-agents.
|
|
4
|
+
*
|
|
5
|
+
* Aggregates the signals available for refining agent briefings: the roster +
|
|
6
|
+
* tier-2 briefing coverage, and how often each agent is referenced across the
|
|
7
|
+
* session history (a usage proxy). `/tune-agents` reads this, adds judgment, and
|
|
8
|
+
* PROPOSES briefing edits — it never auto-applies (mirrors /distill-sessions).
|
|
9
|
+
*
|
|
10
|
+
* agent-tuning.mjs # human summary
|
|
11
|
+
* agent-tuning.mjs --json # { agents: [...], sessionsAnalyzed, withoutBriefing }
|
|
12
|
+
*
|
|
13
|
+
* Zero-dependency, defensive: degrades to empty signals, never throws.
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
16
|
+
import { resolve } from 'node:path';
|
|
17
|
+
import { squadOf } from './squad-meta.mjs';
|
|
18
|
+
import { pathsFor } from '../../runtime/config/paths.mjs';
|
|
19
|
+
|
|
20
|
+
const ROOT = process.cwd();
|
|
21
|
+
const P = pathsFor(ROOT);
|
|
22
|
+
const AGENTS = resolve(ROOT, '.claude/agents');
|
|
23
|
+
const SQUADS = P.squads;
|
|
24
|
+
const SESSIONS = P.sessions;
|
|
25
|
+
|
|
26
|
+
function read(p) {
|
|
27
|
+
try {
|
|
28
|
+
return readFileSync(p, 'utf-8');
|
|
29
|
+
} catch {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function listMd(dir) {
|
|
35
|
+
try {
|
|
36
|
+
return readdirSync(dir).filter((f) => f.endsWith('.md'));
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function collect() {
|
|
43
|
+
const agentFiles = listMd(AGENTS).filter((f) => f !== '_TEMPLATE.md');
|
|
44
|
+
const sessions = listMd(SESSIONS).map((f) => read(resolve(SESSIONS, f)));
|
|
45
|
+
const agents = agentFiles.map((f) => {
|
|
46
|
+
const name = f.slice(0, -3);
|
|
47
|
+
const squad = squadOf(AGENTS, name);
|
|
48
|
+
return {
|
|
49
|
+
name,
|
|
50
|
+
squad,
|
|
51
|
+
hasBriefing: existsSync(resolve(SQUADS, squad, `${name}.md`)),
|
|
52
|
+
mentions: sessions.filter((s) => s.includes(name)).length,
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
agents.sort((a, b) => b.mentions - a.mentions);
|
|
56
|
+
return { agents, sessionsAnalyzed: sessions.length, withoutBriefing: agents.filter((a) => !a.hasBriefing).map((a) => a.name) };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function main() {
|
|
60
|
+
const s = collect();
|
|
61
|
+
if (process.argv.includes('--json')) {
|
|
62
|
+
process.stdout.write(JSON.stringify(s, null, 2) + '\n');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!s.agents.length) {
|
|
66
|
+
console.log('🎯 agent-tuning: no agents found (Level < 4?).');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
console.log(`🎯 agent-tuning signals — ${s.agents.length} agents, ${s.sessionsAnalyzed} sessions analyzed\n`);
|
|
70
|
+
for (const a of s.agents) console.log(` ${a.hasBriefing ? '📄' : ' '} ${a.name} (${a.squad}) ${a.mentions} mention(s)`);
|
|
71
|
+
console.log(`\n ${s.withoutBriefing.length} agent(s) without a tier-2 briefing. Run /tune-agents to propose refinements.`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
main();
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AISO audit — static analyser for AI Search Optimization smells (ADR-0025).
|
|
4
|
+
*
|
|
5
|
+
* AISO is the discoverability surface for LLM answer engines
|
|
6
|
+
* (ChatGPT search, Perplexity, Claude search, Gemini). A site can rank
|
|
7
|
+
* well on Google and never appear in LLM answers — this audit catches
|
|
8
|
+
* the missing patterns: `llms.txt`, FAQ schema, semantic HTML5,
|
|
9
|
+
* author + date stamps, and a `robots.txt` that does not block AI
|
|
10
|
+
* crawlers by accident.
|
|
11
|
+
*
|
|
12
|
+
* Same shape as seo-audit.mjs: walks page files, emits findings,
|
|
13
|
+
* supports `--json`. Zero deps. Defensive.
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
16
|
+
import { resolve, relative } from 'node:path';
|
|
17
|
+
import { lineOf, renderFindings, exitCodeFor, walkProject } from './audit-shared.mjs';
|
|
18
|
+
|
|
19
|
+
const PAGE_EXTS = ['.html', '.astro', '.jsx', '.tsx', '.vue', '.svelte', '.mdx'];
|
|
20
|
+
|
|
21
|
+
const SEVERITY = {
|
|
22
|
+
MISSING_LLMS_TXT: 'high',
|
|
23
|
+
MISSING_FAQ_SCHEMA: 'high',
|
|
24
|
+
MISSING_ORG_SCHEMA: 'medium',
|
|
25
|
+
DIV_SOUP: 'medium',
|
|
26
|
+
JS_RENDERED_CONTENT: 'high',
|
|
27
|
+
MISSING_AUTHOR_SCHEMA: 'low',
|
|
28
|
+
MISSING_DATE_STAMP: 'low',
|
|
29
|
+
BLOCKS_AI_CRAWLERS: 'high',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const finding = (rel) => (code, line, message) =>
|
|
33
|
+
({ file: rel, code, line, severity: SEVERITY[code] || 'medium', message });
|
|
34
|
+
|
|
35
|
+
const AI_CRAWLERS = ['GPTBot', 'ClaudeBot', 'PerplexityBot', 'Google-Extended', 'OAI-SearchBot'];
|
|
36
|
+
|
|
37
|
+
/** robots.txt smells: missing file (handled by seo-audit) and AI-crawler disallow without explicit override. */
|
|
38
|
+
function checkRobots(root) {
|
|
39
|
+
const out = [];
|
|
40
|
+
const robotsPaths = ['robots.txt', 'public/robots.txt', 'static/robots.txt'];
|
|
41
|
+
const robotsPath = robotsPaths.map((p) => resolve(root, p)).find((p) => existsSync(p));
|
|
42
|
+
if (!robotsPath) return out;
|
|
43
|
+
let txt = '';
|
|
44
|
+
try { txt = readFileSync(robotsPath, 'utf8'); } catch { return out; }
|
|
45
|
+
for (const ua of AI_CRAWLERS) {
|
|
46
|
+
const block = new RegExp(`User-agent:\\s*${ua}[\\s\\S]*?Disallow:\\s*/`, 'i');
|
|
47
|
+
if (block.test(txt)) {
|
|
48
|
+
out.push({
|
|
49
|
+
file: relative(root, robotsPath).replaceAll('\\', '/'),
|
|
50
|
+
code: 'BLOCKS_AI_CRAWLERS',
|
|
51
|
+
line: 0,
|
|
52
|
+
severity: SEVERITY.BLOCKS_AI_CRAWLERS,
|
|
53
|
+
message: `robots.txt blocks ${ua}; the site will not appear in that LLM's answers. Add a project ADR if this is intentional.`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** llms.txt presence — the 2024-vintage AISO convention. */
|
|
61
|
+
function checkLlmsTxt(root) {
|
|
62
|
+
const paths = ['llms.txt', 'public/llms.txt', 'static/llms.txt'];
|
|
63
|
+
if (paths.some((p) => existsSync(resolve(root, p)))) return [];
|
|
64
|
+
return [{
|
|
65
|
+
file: 'public/llms.txt',
|
|
66
|
+
code: 'MISSING_LLMS_TXT',
|
|
67
|
+
line: 0,
|
|
68
|
+
severity: SEVERITY.MISSING_LLMS_TXT,
|
|
69
|
+
message: 'no llms.txt at root — LLM answer engines have no curated routing map. See llmstxt.org for the format.',
|
|
70
|
+
}];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Per-file AISO checks: FAQ + Org schema, div-soup, JS-rendered content, author + date stamps. */
|
|
74
|
+
function checkFile(file, text, rel) {
|
|
75
|
+
const f = finding(rel);
|
|
76
|
+
const out = [];
|
|
77
|
+
const isHtmlLike = /\.(html|astro)$/i.test(file);
|
|
78
|
+
|
|
79
|
+
if (!isHtmlLike) return out;
|
|
80
|
+
|
|
81
|
+
// FAQ schema — the load-bearing AISO move
|
|
82
|
+
if (!/"@type":\s*"FAQPage"/.test(text)) {
|
|
83
|
+
out.push(f('MISSING_FAQ_SCHEMA', 1, 'no FAQPage JSON-LD — LLMs cite FAQ entries near-verbatim; without it the page does not appear in answers'));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Organization schema
|
|
87
|
+
if (!/"@type":\s*"Organization"/.test(text)) {
|
|
88
|
+
out.push(f('MISSING_ORG_SCHEMA', 1, 'no Organization JSON-LD — brand entity unknown to LLM rankers'));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Author / person schema (low — only flags if completely absent)
|
|
92
|
+
if (!/"@type":\s*"(Person|Article|NewsArticle)"/.test(text) && /<article/i.test(text)) {
|
|
93
|
+
out.push(f('MISSING_AUTHOR_SCHEMA', 1, 'page has <article> but no Person/Article schema — LLMs weight authored content over anonymous'));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Date stamps
|
|
97
|
+
if (!/(datePublished|dateModified|article:modified_time)/i.test(text)) {
|
|
98
|
+
out.push(f('MISSING_DATE_STAMP', 1, 'no published/modified timestamp — LLMs care about recency'));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Div soup: ratio of <div> to semantic HTML5 tags
|
|
102
|
+
const divs = (text.match(/<div[\s>]/gi) || []).length;
|
|
103
|
+
const semantic = (text.match(/<(article|section|nav|main|aside|header|footer)[\s>]/gi) || []).length;
|
|
104
|
+
if (divs > 5 && semantic > 0 && divs / Math.max(semantic, 1) > 5) {
|
|
105
|
+
out.push(f('DIV_SOUP', 1, `${divs} <div> to ${semantic} semantic tags (ratio ${(divs / semantic).toFixed(1)}:1) — LLM extractors weight semantic HTML5`));
|
|
106
|
+
} else if (divs > 10 && semantic === 0) {
|
|
107
|
+
out.push(f('DIV_SOUP', 1, `${divs} <div> with no semantic HTML5 tags at all — use <article>, <section>, <nav>, <main>`));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// JS-rendered content heuristic: large <script> with template-like content but tiny <body>
|
|
111
|
+
const bodyMatch = /<body[^>]*>([\s\S]*?)<\/body>/i.exec(text);
|
|
112
|
+
if (bodyMatch) {
|
|
113
|
+
const bodyText = bodyMatch[1].replace(/<[^>]+>/g, '').trim();
|
|
114
|
+
const scripts = [...text.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/gi)];
|
|
115
|
+
const heavyScript = scripts.some((m) => m[1].length > 2000);
|
|
116
|
+
if (bodyText.length < 200 && heavyScript) {
|
|
117
|
+
out.push(f('JS_RENDERED_CONTENT', lineOf(text, bodyMatch.index), 'body text < 200 chars but script body > 2 kB — content appears JS-rendered; LLM crawlers may miss it'));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Run the AISO audit against a project root.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} root
|
|
127
|
+
* @returns {{ findings: object[] }}
|
|
128
|
+
*/
|
|
129
|
+
export function runAisoAudit(root) {
|
|
130
|
+
const findings = [...checkLlmsTxt(root), ...checkRobots(root)];
|
|
131
|
+
for (const file of walkProject(root, PAGE_EXTS)) {
|
|
132
|
+
let text = '';
|
|
133
|
+
try { text = readFileSync(file, 'utf8'); } catch { continue; }
|
|
134
|
+
findings.push(...checkFile(file, text, relative(root, file).replaceAll('\\', '/')));
|
|
135
|
+
}
|
|
136
|
+
return { findings };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const argv = process.argv.slice(2);
|
|
140
|
+
const wantJson = argv.includes('--json');
|
|
141
|
+
const wantHelp = argv.includes('--help') || argv.includes('-h');
|
|
142
|
+
|
|
143
|
+
const isMain = (() => {
|
|
144
|
+
try {
|
|
145
|
+
const here = new URL(import.meta.url).pathname.toLowerCase();
|
|
146
|
+
const entry = process.argv[1]
|
|
147
|
+
? new URL('file://' + process.argv[1].replace(/\\/g, '/')).pathname.toLowerCase()
|
|
148
|
+
: '';
|
|
149
|
+
return here === entry;
|
|
150
|
+
} catch { return false; }
|
|
151
|
+
})();
|
|
152
|
+
|
|
153
|
+
if (isMain) {
|
|
154
|
+
if (wantHelp) {
|
|
155
|
+
process.stdout.write(`Usage: aiso-audit.mjs [--json]
|
|
156
|
+
|
|
157
|
+
Scans the project for AI Search Optimization smells per ADR-0025.
|
|
158
|
+
AISO is about LLM answer-engine discoverability (ChatGPT, Perplexity,
|
|
159
|
+
Claude, Gemini) — orthogonal to classical SEO.
|
|
160
|
+
|
|
161
|
+
--json emit findings as JSON to stdout
|
|
162
|
+
--help this message
|
|
163
|
+
`);
|
|
164
|
+
process.exit(0);
|
|
165
|
+
}
|
|
166
|
+
const root = process.cwd();
|
|
167
|
+
const result = runAisoAudit(root);
|
|
168
|
+
if (wantJson) {
|
|
169
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
170
|
+
} else {
|
|
171
|
+
process.stdout.write(renderFindings(result.findings, { title: 'AISO audit' }));
|
|
172
|
+
}
|
|
173
|
+
process.exit(exitCodeFor(result.findings));
|
|
174
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the SEO + AISO audit scripts (ADR-0025).
|
|
3
|
+
*
|
|
4
|
+
* - `walkProject(root, exts)` — recursive file walk that skips the usual
|
|
5
|
+
* noise (`node_modules`, `.git`, `dist`, `build`, framework caches).
|
|
6
|
+
* - `detectFramework(root)` — reads package.json and reports the
|
|
7
|
+
* rendering posture (`astro`, `next`, `nuxt`, `remix`, `sveltekit`,
|
|
8
|
+
* `vite-react`, or `null`).
|
|
9
|
+
* - `lineOf(text, idx)` — 1-based line number for a regex match index.
|
|
10
|
+
* - `renderFindings(findings)` — pretty terminal output, grouped by
|
|
11
|
+
* severity. JSON output handled by the calling script.
|
|
12
|
+
*
|
|
13
|
+
* Zero deps. Defensive: any failure returns a safe default (empty
|
|
14
|
+
* array, `null` framework) — rule 2.
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
17
|
+
import { join, extname } from 'node:path';
|
|
18
|
+
|
|
19
|
+
const SKIP_DIRS = /(^|[\\/])(node_modules|\.git|dist|build|out|\.next|\.astro|\.nuxt|\.svelte-kit|\.cache|coverage|\.vercel|\.netlify|\.turbo)([\\/]|$)/;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Walk a directory recursively yielding files with one of the given
|
|
23
|
+
* extensions. Skips noise dirs. Defensive — unreadable subdirs are
|
|
24
|
+
* silently skipped.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} root
|
|
27
|
+
* @param {string[]} exts e.g. ['.html', '.astro', '.jsx']
|
|
28
|
+
*/
|
|
29
|
+
export function* walkProject(root, exts) {
|
|
30
|
+
const stack = [root];
|
|
31
|
+
while (stack.length > 0) {
|
|
32
|
+
const dir = stack.pop();
|
|
33
|
+
let entries;
|
|
34
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); }
|
|
35
|
+
catch { continue; }
|
|
36
|
+
for (const e of entries) {
|
|
37
|
+
const full = join(dir, e.name);
|
|
38
|
+
if (SKIP_DIRS.test(full)) continue;
|
|
39
|
+
if (e.isDirectory()) stack.push(full);
|
|
40
|
+
else if (exts.includes(extname(e.name))) yield full;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Best-effort framework detection from package.json. The result drives
|
|
47
|
+
* the SPA_ENTRYPOINT refusal in seo-audit.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} root
|
|
50
|
+
* @returns {'astro' | 'next' | 'nuxt' | 'remix' | 'sveltekit' | 'vite-react' | 'gatsby' | 'eleventy' | null}
|
|
51
|
+
*/
|
|
52
|
+
export function detectFramework(root) {
|
|
53
|
+
let pkg;
|
|
54
|
+
try {
|
|
55
|
+
pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8').replace(/^/, ''));
|
|
56
|
+
} catch { return null; }
|
|
57
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
58
|
+
if (deps.astro) return 'astro';
|
|
59
|
+
if (deps.next) return 'next';
|
|
60
|
+
if (deps.nuxt) return 'nuxt';
|
|
61
|
+
if (deps['@remix-run/react'] || deps['@remix-run/node']) return 'remix';
|
|
62
|
+
if (deps['@sveltejs/kit']) return 'sveltekit';
|
|
63
|
+
if (deps.gatsby) return 'gatsby';
|
|
64
|
+
if (deps['@11ty/eleventy']) return 'eleventy';
|
|
65
|
+
if (deps.vite && (deps.react || deps['react-dom'])) return 'vite-react';
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** True when the detected framework leaves the user responsible for SSR/SSG wiring. */
|
|
70
|
+
export const isSpaFramework = (fw) => fw === 'vite-react' || fw === null;
|
|
71
|
+
|
|
72
|
+
/** True when the detected framework ships SSG / SSR by default. */
|
|
73
|
+
export const isIndexableFramework = (fw) =>
|
|
74
|
+
fw === 'astro' || fw === 'next' || fw === 'nuxt' || fw === 'remix' ||
|
|
75
|
+
fw === 'sveltekit' || fw === 'gatsby' || fw === 'eleventy';
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 1-based line number for the character at `idx` in `text`.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} text
|
|
81
|
+
* @param {number} idx
|
|
82
|
+
* @returns {number}
|
|
83
|
+
*/
|
|
84
|
+
export function lineOf(text, idx) {
|
|
85
|
+
if (idx <= 0) return 1;
|
|
86
|
+
let n = 1;
|
|
87
|
+
for (let i = 0; i < idx; i++) if (text.charCodeAt(i) === 10) n++;
|
|
88
|
+
return n;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const SEV_ORDER = ['critical', 'high', 'medium', 'low'];
|
|
92
|
+
const SEV_LABEL = {
|
|
93
|
+
critical: '\x1b[31mCRITICAL\x1b[0m',
|
|
94
|
+
high: '\x1b[33mHIGH \x1b[0m',
|
|
95
|
+
medium: '\x1b[36mMEDIUM \x1b[0m',
|
|
96
|
+
low: '\x1b[90mLOW \x1b[0m',
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Render findings as a coloured terminal table grouped by severity.
|
|
101
|
+
*
|
|
102
|
+
* @param {Array<{ code:string, file:string, line:number, severity:string, message:string }>} findings
|
|
103
|
+
* @param {object} opts
|
|
104
|
+
* @param {string} opts.title
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
export function renderFindings(findings, { title }) {
|
|
108
|
+
if (!findings.length) {
|
|
109
|
+
return `\n🔍 ${title}\n ✅ no findings — all checks passed.\n`;
|
|
110
|
+
}
|
|
111
|
+
const bySev = Object.fromEntries(SEV_ORDER.map((s) => [s, []]));
|
|
112
|
+
for (const f of findings) (bySev[f.severity] || bySev.medium).push(f);
|
|
113
|
+
const lines = [`\n🔍 ${title}`, ` ${findings.length} finding${findings.length === 1 ? '' : 's'}\n`];
|
|
114
|
+
for (const sev of SEV_ORDER) {
|
|
115
|
+
if (!bySev[sev].length) continue;
|
|
116
|
+
lines.push(` ${SEV_LABEL[sev]} (${bySev[sev].length})`);
|
|
117
|
+
for (const f of bySev[sev]) {
|
|
118
|
+
const loc = f.line ? `:${f.line}` : '';
|
|
119
|
+
lines.push(` ${f.code.padEnd(22, ' ')} ${f.file}${loc}`);
|
|
120
|
+
if (f.message) lines.push(` ↳ ${f.message}`);
|
|
121
|
+
}
|
|
122
|
+
lines.push('');
|
|
123
|
+
}
|
|
124
|
+
return lines.join('\n') + '\n';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Convenience: exit code from findings (1 if any critical, 0 otherwise). */
|
|
128
|
+
export const exitCodeFor = (findings) =>
|
|
129
|
+
findings.some((f) => f.severity === 'critical') ? 1 : 0;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Reserves resources for the current session (Level >= 3) — paths and, since
|
|
4
|
+
* [ADR-0015 §B](../../memory/decisions/0015-pipeline-dsl-working-stage-and-multi-session-work-claims.md),
|
|
5
|
+
* DevPipeline task ids. Writes/updates `.claude/.workspace/<sid>.json`, then
|
|
6
|
+
* regenerates `contextkit/memory/WORKSPACE.md`. The current session id is read
|
|
7
|
+
* from the `.last-touched` pointer the hooks maintain.
|
|
8
|
+
*
|
|
9
|
+
* Usage: node contextkit/tools/scripts/claim.mjs <path> [path2 ...]
|
|
10
|
+
* API: attachTask(taskId) / detachTask(taskId) — used by pipeline.mjs
|
|
11
|
+
* start|stop so task ownership flows through the same single source
|
|
12
|
+
* of truth as path ownership.
|
|
13
|
+
*/
|
|
14
|
+
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import { existsSync } from 'node:fs';
|
|
16
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
17
|
+
import { resolve } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { sanitizeSid } from '../../runtime/hooks/ledger.mjs';
|
|
20
|
+
import { writeFileAtomic } from '../../runtime/hooks/safe-io.mjs';
|
|
21
|
+
|
|
22
|
+
const ROOT = process.cwd();
|
|
23
|
+
const WS_DIR = resolve(ROOT, '.claude/.workspace');
|
|
24
|
+
const LAST_TOUCHED = resolve(ROOT, '.claude/.sessions/.last-touched');
|
|
25
|
+
|
|
26
|
+
// execFileSync (argv array, no shell) — consistent with the other git callers.
|
|
27
|
+
function gitOut(args, fallback) {
|
|
28
|
+
try {
|
|
29
|
+
return execFileSync('git', args, { cwd: ROOT, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || fallback;
|
|
30
|
+
} catch {
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function sessionId() {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(await readFile(LAST_TOUCHED, 'utf-8')).sessionId;
|
|
38
|
+
} catch {
|
|
39
|
+
return `local_${process.pid}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Loads (or creates) the current session's workspace record. Backward-compat:
|
|
45
|
+
* records without `tasks[]` get an empty default so old sessions parse fine.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} sid — already sanitized
|
|
48
|
+
* @returns {Promise<{ sessionId, branch, user, startedAt, lastHeartbeat, claims, tasks, file }>}
|
|
49
|
+
*/
|
|
50
|
+
async function loadRecord(sid) {
|
|
51
|
+
await mkdir(WS_DIR, { recursive: true });
|
|
52
|
+
const file = resolve(WS_DIR, `${sid}.json`);
|
|
53
|
+
const fresh = { sessionId: sid, branch: gitOut(['symbolic-ref', '--short', 'HEAD'], 'detached'), user: gitOut(['config', 'user.name'], 'unknown'), startedAt: Date.now(), lastHeartbeat: Date.now(), claims: [], tasks: [] };
|
|
54
|
+
if (!existsSync(file)) return { ...fresh, file };
|
|
55
|
+
try {
|
|
56
|
+
const existing = JSON.parse(await readFile(file, 'utf-8'));
|
|
57
|
+
return { ...fresh, ...existing, tasks: Array.isArray(existing.tasks) ? existing.tasks : [], file };
|
|
58
|
+
} catch {
|
|
59
|
+
return { ...fresh, file };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function persistRecord(record) {
|
|
64
|
+
record.lastHeartbeat = Date.now();
|
|
65
|
+
const { file, ...body } = record;
|
|
66
|
+
await writeFileAtomic(file, JSON.stringify(body, null, 2));
|
|
67
|
+
try {
|
|
68
|
+
execFileSync('node', ['contextkit/tools/scripts/workspace-sync.mjs'], { cwd: ROOT, stdio: 'ignore' });
|
|
69
|
+
} catch {
|
|
70
|
+
/* best effort */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Attaches a DevPipeline task id to the current session's record (ADR-0015 §B).
|
|
76
|
+
* Called by `pipeline.mjs start <id>`. Idempotent: re-attaching a task id is a
|
|
77
|
+
* no-op (heartbeat refresh only).
|
|
78
|
+
*
|
|
79
|
+
* @param {string} taskId
|
|
80
|
+
*/
|
|
81
|
+
export async function attachTask(taskId) {
|
|
82
|
+
const sid = sanitizeSid(await sessionId());
|
|
83
|
+
const record = await loadRecord(sid);
|
|
84
|
+
const id = String(taskId).padStart(3, '0');
|
|
85
|
+
const existing = record.tasks.find((t) => t.id === id);
|
|
86
|
+
if (existing) {
|
|
87
|
+
existing.lastHeartbeat = Date.now();
|
|
88
|
+
} else {
|
|
89
|
+
record.tasks.push({ id, startedAt: Date.now(), lastHeartbeat: Date.now() });
|
|
90
|
+
}
|
|
91
|
+
await persistRecord(record);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Detaches a DevPipeline task id from the current session's record. Called by
|
|
96
|
+
* `pipeline.mjs stop <id>` and by the stale-eviction sweep in workspace-sync.
|
|
97
|
+
* No-op if the task wasn't on this session's list.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} taskId
|
|
100
|
+
*/
|
|
101
|
+
export async function detachTask(taskId) {
|
|
102
|
+
const sid = sanitizeSid(await sessionId());
|
|
103
|
+
const record = await loadRecord(sid);
|
|
104
|
+
const id = String(taskId).padStart(3, '0');
|
|
105
|
+
const before = record.tasks.length;
|
|
106
|
+
record.tasks = record.tasks.filter((t) => t.id !== id);
|
|
107
|
+
if (record.tasks.length === before) return;
|
|
108
|
+
await persistRecord(record);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function main() {
|
|
112
|
+
const paths = process.argv.slice(2).map((p) => p.replaceAll('\\', '/'));
|
|
113
|
+
if (paths.length === 0) {
|
|
114
|
+
console.error('Usage: claim.mjs <path> [path2 ...]');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
const sid = sanitizeSid(await sessionId());
|
|
118
|
+
const record = await loadRecord(sid);
|
|
119
|
+
const existing = new Set((record.claims || []).map((c) => c.path));
|
|
120
|
+
for (const p of paths) {
|
|
121
|
+
if (!existing.has(p)) record.claims.push({ path: p, claimedAt: Date.now() });
|
|
122
|
+
}
|
|
123
|
+
await persistRecord(record);
|
|
124
|
+
console.log(`✅ Claimed ${paths.length} path(s) for session ${sid.slice(0, 8)}: ${paths.join(', ')}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Only run the CLI when invoked directly; library imports stay side-effect-free.
|
|
128
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
|
|
129
|
+
main().catch((err) => {
|
|
130
|
+
console.error('❌ claim failed:', err);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Modular CLAUDE.md scaffolder.
|
|
4
|
+
*
|
|
5
|
+
* Like the source platform (root CLAUDE.md + apps/api/CLAUDE.md +
|
|
6
|
+
* apps/mobile/CLAUDE.md), each app / independent module should carry its OWN
|
|
7
|
+
* scoped CLAUDE.md so Claude Code loads the closest, most relevant rules. This
|
|
8
|
+
* detects module roots and ensures each has one.
|
|
9
|
+
*
|
|
10
|
+
* A "module root" is a directory that looks independently buildable: under a
|
|
11
|
+
* monorepo group (apps/ packages/ modules/ services/ libs/ apps-*), OR a
|
|
12
|
+
* conventional split dir (backend/ frontend/ client/ server/ api/ web/ mobile/
|
|
13
|
+
* desktop/ functions/ worker/), AND it contains a manifest or a src/.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* node contextkit/tools/scripts/claude-md.mjs find [--json]
|
|
17
|
+
* node contextkit/tools/scripts/claude-md.mjs scaffold # create missing (stub) CLAUDE.md
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
20
|
+
import { dirname, join, resolve } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const ROOT = process.cwd();
|
|
24
|
+
// Installed at <project>/contextkit/tools/scripts/ → the seeded template is at <project>/contextkit/.
|
|
25
|
+
const TPL = resolve(dirname(fileURLToPath(import.meta.url)), '../../CLAUDE.child.md.tpl');
|
|
26
|
+
|
|
27
|
+
const GROUPS = ['apps', 'packages', 'modules', 'services', 'libs', 'plugins'];
|
|
28
|
+
const SPLITS = ['backend', 'frontend', 'client', 'server', 'api', 'web', 'mobile', 'desktop', 'functions', 'worker', 'workers', 'app'];
|
|
29
|
+
const MANIFESTS = ['package.json', 'pyproject.toml', 'go.mod', 'Cargo.toml', 'tsconfig.json', 'composer.json', 'Gemfile', 'pom.xml'];
|
|
30
|
+
|
|
31
|
+
function looksBuildable(absDir) {
|
|
32
|
+
if (MANIFESTS.some((m) => existsSync(join(absDir, m)))) return true;
|
|
33
|
+
return existsSync(join(absDir, 'src'));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function dirsIn(absDir) {
|
|
37
|
+
try {
|
|
38
|
+
return readdirSync(absDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith('.')).map((e) => e.name);
|
|
39
|
+
} catch {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Detect module roots (depth 1 splits + depth 2 monorepo group children). */
|
|
45
|
+
function detectModuleRoots() {
|
|
46
|
+
const roots = new Set();
|
|
47
|
+
for (const name of SPLITS) {
|
|
48
|
+
const abs = resolve(ROOT, name);
|
|
49
|
+
if (existsSync(abs) && looksBuildable(abs)) roots.add(name);
|
|
50
|
+
}
|
|
51
|
+
for (const group of GROUPS) {
|
|
52
|
+
const groupAbs = resolve(ROOT, group);
|
|
53
|
+
if (!existsSync(groupAbs)) continue;
|
|
54
|
+
for (const child of dirsIn(groupAbs)) {
|
|
55
|
+
const abs = join(groupAbs, child);
|
|
56
|
+
if (looksBuildable(abs)) roots.add(`${group}/${child}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return [...roots].sort();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function moduleStack(absDir) {
|
|
63
|
+
if (existsSync(join(absDir, 'package.json'))) {
|
|
64
|
+
try {
|
|
65
|
+
const pkg = JSON.parse(readFileSync(join(absDir, 'package.json'), 'utf-8').replace(/^/, ''));
|
|
66
|
+
const deps = Object.keys({ ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) });
|
|
67
|
+
const known = ['react', 'next', 'expo', 'react-native', 'vue', 'svelte', 'hono', 'express', 'fastify', '@nestjs/core', 'drizzle-orm', 'prisma', 'vite'];
|
|
68
|
+
const found = known.filter((k) => deps.includes(k));
|
|
69
|
+
return `Node/TypeScript. ${found.length ? 'Detected: ' + found.join(', ') + '.' : '_TODO: fill in._'}`;
|
|
70
|
+
} catch {
|
|
71
|
+
/* ignore */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const [f, label] of [['pyproject.toml', 'Python'], ['go.mod', 'Go'], ['Cargo.toml', 'Rust']]) {
|
|
75
|
+
if (existsSync(join(absDir, f))) return `${label} (${f}).`;
|
|
76
|
+
}
|
|
77
|
+
return '_TODO: fill in this module\'s stack._';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function render(tpl, vars) {
|
|
81
|
+
return tpl.replace(/\{\{(\w+)\}\}/g, (_, k) => (k in vars ? vars[k] : `{{${k}}}`));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const cmd = process.argv[2];
|
|
85
|
+
const roots = detectModuleRoots();
|
|
86
|
+
const status = roots.map((r) => ({ module: r, hasClaudeMd: existsSync(resolve(ROOT, r, 'CLAUDE.md')) }));
|
|
87
|
+
|
|
88
|
+
if (cmd === 'find') {
|
|
89
|
+
if (process.argv.includes('--json')) {
|
|
90
|
+
console.log(JSON.stringify({ moduleRoots: status }, null, 2));
|
|
91
|
+
} else if (status.length === 0) {
|
|
92
|
+
console.log('No app/module roots detected (single-package project — root CLAUDE.md is enough).');
|
|
93
|
+
} else {
|
|
94
|
+
console.log('Module roots:');
|
|
95
|
+
for (const s of status) console.log(` ${s.hasClaudeMd ? '✓' : '✗'} ${s.module}/CLAUDE.md`);
|
|
96
|
+
const missing = status.filter((s) => !s.hasClaudeMd).length;
|
|
97
|
+
if (missing) console.log(`\n${missing} module(s) missing CLAUDE.md — run \`claude-md.mjs scaffold\` (or /claude-md).`);
|
|
98
|
+
}
|
|
99
|
+
} else if (cmd === 'scaffold') {
|
|
100
|
+
if (!existsSync(TPL)) {
|
|
101
|
+
console.error(`Child template not found at ${TPL}.`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
const tpl = readFileSync(TPL, 'utf-8');
|
|
105
|
+
let created = 0;
|
|
106
|
+
for (const s of status) {
|
|
107
|
+
if (s.hasClaudeMd) continue;
|
|
108
|
+
const abs = resolve(ROOT, s.module);
|
|
109
|
+
const content = render(tpl, {
|
|
110
|
+
MODULE_NAME: s.module,
|
|
111
|
+
MODULE_PATH: s.module,
|
|
112
|
+
DATE: new Date().toISOString().slice(0, 10),
|
|
113
|
+
MODULE_STACK: moduleStack(abs),
|
|
114
|
+
});
|
|
115
|
+
writeFileSync(resolve(abs, 'CLAUDE.md'), content, 'utf-8');
|
|
116
|
+
created++;
|
|
117
|
+
console.log(`✓ created ${s.module}/CLAUDE.md`);
|
|
118
|
+
}
|
|
119
|
+
console.log(created === 0 ? 'All module roots already have a CLAUDE.md.' : `\n✅ Scaffolded ${created} scoped CLAUDE.md. Fill in the TODOs (or let /claude-md do it).`);
|
|
120
|
+
} else {
|
|
121
|
+
console.error('Usage: claude-md.mjs <find|scaffold> [--json]');
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|