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.
Files changed (345) hide show
  1. package/CHANGELOG.md +592 -0
  2. package/LICENSE +21 -0
  3. package/README.md +401 -0
  4. package/docs/AGENT-PACKAGE-FORMAT.md +140 -0
  5. package/docs/ARCHITECTURE.md +258 -0
  6. package/docs/CHANGELOG.md +559 -0
  7. package/docs/CUSTOMIZING.md +211 -0
  8. package/docs/LEVELS.md +151 -0
  9. package/docs/ROADMAP.md +385 -0
  10. package/docs/SQUAD-PIPELINE-FORMAT.md +258 -0
  11. package/docs/SQUADS/agent-forge.md +65 -0
  12. package/docs/SQUADS/design-team.md +161 -0
  13. package/docs/token-economy-plan.md +135 -0
  14. package/install.mjs +273 -0
  15. package/instrucoes.md +274 -0
  16. package/package.json +46 -0
  17. package/templates/CLAUDE.md.tpl +133 -0
  18. package/templates/claude/agents/_TEMPLATE.md +52 -0
  19. package/templates/claude/agents/accessibility.md +36 -0
  20. package/templates/claude/agents/agent-architect.md +37 -0
  21. package/templates/claude/agents/architect.md +39 -0
  22. package/templates/claude/agents/code-reviewer.md +43 -0
  23. package/templates/claude/agents/code-security.md +59 -0
  24. package/templates/claude/agents/context-keeper.md +40 -0
  25. package/templates/claude/agents/devops.md +40 -0
  26. package/templates/claude/agents/eval-designer.md +40 -0
  27. package/templates/claude/agents/forge-orchestrator.md +42 -0
  28. package/templates/claude/agents/governance-officer.md +45 -0
  29. package/templates/claude/agents/growth.md +92 -0
  30. package/templates/claude/agents/infra-security.md +53 -0
  31. package/templates/claude/agents/landing-architect.md +154 -0
  32. package/templates/claude/agents/model-router.md +34 -0
  33. package/templates/claude/agents/packager.md +38 -0
  34. package/templates/claude/agents/privacy-lgpd.md +64 -0
  35. package/templates/claude/agents/product-owner.md +51 -0
  36. package/templates/claude/agents/prompt-engineer.md +33 -0
  37. package/templates/claude/agents/qa-e2e.md +52 -0
  38. package/templates/claude/agents/qa-fuzzer.md +24 -0
  39. package/templates/claude/agents/qa-integration.md +21 -0
  40. package/templates/claude/agents/qa-orchestrator.md +40 -0
  41. package/templates/claude/agents/qa-perf.md +40 -0
  42. package/templates/claude/agents/qa-unit.md +39 -0
  43. package/templates/claude/agents/rag-designer.md +54 -0
  44. package/templates/claude/agents/retention.md +85 -0
  45. package/templates/claude/agents/security.md +48 -0
  46. package/templates/claude/agents/seo-specialist.md +106 -0
  47. package/templates/claude/agents/test-engineer.md +48 -0
  48. package/templates/claude/agents/tool-designer.md +32 -0
  49. package/templates/claude/agents/ui-designer.md +37 -0
  50. package/templates/claude/agents/ux-designer.md +38 -0
  51. package/templates/claude/commands/README.md +95 -0
  52. package/templates/claude/commands/advise.md +80 -0
  53. package/templates/claude/commands/audit/analyze-code-ia-practices.md +75 -0
  54. package/templates/claude/commands/audit/audit.md +35 -0
  55. package/templates/claude/commands/audit/contract-check.md +21 -0
  56. package/templates/claude/commands/audit/deep-analysis.md +48 -0
  57. package/templates/claude/commands/audit/deps-audit.md +49 -0
  58. package/templates/claude/commands/audit/security-setup.md +35 -0
  59. package/templates/claude/commands/audit/seo-audit.md +63 -0
  60. package/templates/claude/commands/audit/tech-debt-sweep.md +35 -0
  61. package/templates/claude/commands/bug-hunt.md +42 -0
  62. package/templates/claude/commands/claude-md.md +36 -0
  63. package/templates/claude/commands/close-version.md +25 -0
  64. package/templates/claude/commands/context-refresh.md +19 -0
  65. package/templates/claude/commands/context-stats.md +15 -0
  66. package/templates/claude/commands/dashboard.md +66 -0
  67. package/templates/claude/commands/distill-apply.md +19 -0
  68. package/templates/claude/commands/distill-sessions.md +26 -0
  69. package/templates/claude/commands/fleet.md +47 -0
  70. package/templates/claude/commands/forge/forge-audit.md +16 -0
  71. package/templates/claude/commands/forge/forge-budget.md +16 -0
  72. package/templates/claude/commands/forge/forge-deprecate.md +16 -0
  73. package/templates/claude/commands/forge/forge-doctor.md +17 -0
  74. package/templates/claude/commands/forge/forge-eval.md +16 -0
  75. package/templates/claude/commands/forge/forge-fallback-test.md +17 -0
  76. package/templates/claude/commands/forge/forge-killswitch.md +17 -0
  77. package/templates/claude/commands/forge/forge-list.md +17 -0
  78. package/templates/claude/commands/forge/forge-new.md +41 -0
  79. package/templates/claude/commands/forge/forge-policy.md +16 -0
  80. package/templates/claude/commands/forge/forge-redteam.md +17 -0
  81. package/templates/claude/commands/forge/forge-refresh-matrix.md +20 -0
  82. package/templates/claude/commands/forge/forge-route.md +17 -0
  83. package/templates/claude/commands/forge/forge-show.md +16 -0
  84. package/templates/claude/commands/landing-page.md +71 -0
  85. package/templates/claude/commands/log-session.md +59 -0
  86. package/templates/claude/commands/media-gen.md +93 -0
  87. package/templates/claude/commands/new-adr.md +30 -0
  88. package/templates/claude/commands/pipeline/dev-start.md +64 -0
  89. package/templates/claude/commands/pipeline/pipeline.md +36 -0
  90. package/templates/claude/commands/pipeline/resume.md +70 -0
  91. package/templates/claude/commands/pipeline/retro.md +34 -0
  92. package/templates/claude/commands/pipeline/runs.md +63 -0
  93. package/templates/claude/commands/pipeline/ship.md +54 -0
  94. package/templates/claude/commands/pipeline/workflow.md +85 -0
  95. package/templates/claude/commands/playbook.md +27 -0
  96. package/templates/claude/commands/predictions-review.md +28 -0
  97. package/templates/claude/commands/qa/qa-signoff.md +24 -0
  98. package/templates/claude/commands/qa/scaffold-tests.md +27 -0
  99. package/templates/claude/commands/qa/test-plan.md +26 -0
  100. package/templates/claude/commands/qa/visual-test.md +42 -0
  101. package/templates/claude/commands/roadmap.md +48 -0
  102. package/templates/claude/commands/setup/aidevtool-from0.md +104 -0
  103. package/templates/claude/commands/setup/context-config.md +25 -0
  104. package/templates/claude/commands/setup/context-doctor.md +21 -0
  105. package/templates/claude/commands/setup/context-level.md +17 -0
  106. package/templates/claude/commands/setup/setupcontextdevkit.md +121 -0
  107. package/templates/claude/commands/simulate-impact.md +32 -0
  108. package/templates/claude/commands/squad.md +44 -0
  109. package/templates/claude/commands/state.md +21 -0
  110. package/templates/claude/commands/token-report.md +29 -0
  111. package/templates/claude/commands/tune-agents.md +35 -0
  112. package/templates/claude/commands/vcs/claim.md +18 -0
  113. package/templates/claude/commands/vcs/git.md +83 -0
  114. package/templates/claude/commands/vcs/release.md +15 -0
  115. package/templates/claude/commands/vcs/worktree-new.md +18 -0
  116. package/templates/claude/commands/watch.md +47 -0
  117. package/templates/contextkit/.env.example +36 -0
  118. package/templates/contextkit/CLAUDE.child.md.tpl +38 -0
  119. package/templates/contextkit/README.md +74 -0
  120. package/templates/contextkit/behaviors-examples.md +183 -0
  121. package/templates/contextkit/behaviors.md +116 -0
  122. package/templates/contextkit/best-practices.md +323 -0
  123. package/templates/contextkit/config.json +66 -0
  124. package/templates/contextkit/detectors/README.md +45 -0
  125. package/templates/contextkit/detectors/example-detector.mjs.example +25 -0
  126. package/templates/contextkit/instrucoes.md +114 -0
  127. package/templates/contextkit/memory/GLOSSARY.md +13 -0
  128. package/templates/contextkit/memory/SESSIONS.md +9 -0
  129. package/templates/contextkit/memory/WORKSPACE.md +7 -0
  130. package/templates/contextkit/memory/business-rules/_TEMPLATE.md +33 -0
  131. package/templates/contextkit/memory/decisions/0000-record-architecture-decisions.md +34 -0
  132. package/templates/contextkit/memory/decisions/_TEMPLATE.md +25 -0
  133. package/templates/contextkit/memory/predictions/.gitkeep +0 -0
  134. package/templates/contextkit/memory/roadmap.md +28 -0
  135. package/templates/contextkit/memory/sessions/.gitkeep +0 -0
  136. package/templates/contextkit/memory/workflows/.gitkeep +0 -0
  137. package/templates/contextkit/pipeline/backlog/.gitkeep +0 -0
  138. package/templates/contextkit/pipeline/conclusion/.gitkeep +0 -0
  139. package/templates/contextkit/pipeline/devpipeline.md +9 -0
  140. package/templates/contextkit/pipeline/testing/.gitkeep +0 -0
  141. package/templates/contextkit/pipeline/working/.gitkeep +0 -0
  142. package/templates/contextkit/review-protocol.md +214 -0
  143. package/templates/contextkit/runtime/config/defaults.mjs +215 -0
  144. package/templates/contextkit/runtime/config/levels.mjs +42 -0
  145. package/templates/contextkit/runtime/config/load.mjs +105 -0
  146. package/templates/contextkit/runtime/config/paths.mjs +92 -0
  147. package/templates/contextkit/runtime/config/presets.mjs +47 -0
  148. package/templates/contextkit/runtime/config/schema.mjs +88 -0
  149. package/templates/contextkit/runtime/config/settings-compose.mjs +55 -0
  150. package/templates/contextkit/runtime/git-hooks/commit-msg.mjs +55 -0
  151. package/templates/contextkit/runtime/git-hooks/pre-commit.mjs +47 -0
  152. package/templates/contextkit/runtime/git-hooks/pre-push.mjs +102 -0
  153. package/templates/contextkit/runtime/hooks/boot-context-readers.mjs +111 -0
  154. package/templates/contextkit/runtime/hooks/boot-signals.mjs +135 -0
  155. package/templates/contextkit/runtime/hooks/check-registration.mjs +228 -0
  156. package/templates/contextkit/runtime/hooks/concurrency-guard.mjs +110 -0
  157. package/templates/contextkit/runtime/hooks/ledger.mjs +231 -0
  158. package/templates/contextkit/runtime/hooks/md-extract.mjs +65 -0
  159. package/templates/contextkit/runtime/hooks/path-classification.mjs +62 -0
  160. package/templates/contextkit/runtime/hooks/safe-io.mjs +84 -0
  161. package/templates/contextkit/runtime/hooks/session-digest-core.mjs +85 -0
  162. package/templates/contextkit/runtime/hooks/session-start.mjs +248 -0
  163. package/templates/contextkit/runtime/hooks/simulate-gate.mjs +108 -0
  164. package/templates/contextkit/runtime/hooks/track-edits.mjs +154 -0
  165. package/templates/contextkit/runtime/providers/media/_adapter.mjs +120 -0
  166. package/templates/contextkit/runtime/providers/media/nano-banana.mjs +110 -0
  167. package/templates/contextkit/runtime/providers/media/veo.mjs +162 -0
  168. package/templates/contextkit/runtime/providers/review/_adapter.mjs +71 -0
  169. package/templates/contextkit/runtime/providers/review/detect.mjs +115 -0
  170. package/templates/contextkit/runtime/providers/review/gh.mjs +103 -0
  171. package/templates/contextkit/runtime/state/state-io.mjs +172 -0
  172. package/templates/contextkit/runtime/statusline.mjs +51 -0
  173. package/templates/contextkit/squads/README.md +115 -0
  174. package/templates/contextkit/squads/_BRIEFING.md.tpl +27 -0
  175. package/templates/contextkit/squads/agent-forge/README.md +69 -0
  176. package/templates/contextkit/squads/agent-forge/ROADMAP.md +108 -0
  177. package/templates/contextkit/squads/agent-forge/best-practices.md +89 -0
  178. package/templates/contextkit/squads/agent-forge/cli/forge-admin.mjs +132 -0
  179. package/templates/contextkit/squads/agent-forge/cli/forge-eval-cli.mjs +163 -0
  180. package/templates/contextkit/squads/agent-forge/cli/forge-new.mjs +97 -0
  181. package/templates/contextkit/squads/agent-forge/cli/forge-ops.mjs +177 -0
  182. package/templates/contextkit/squads/agent-forge/lib/architect.mjs +112 -0
  183. package/templates/contextkit/squads/agent-forge/lib/eval-designer.mjs +133 -0
  184. package/templates/contextkit/squads/agent-forge/lib/eval-runner.mjs +167 -0
  185. package/templates/contextkit/squads/agent-forge/lib/governance-officer.mjs +178 -0
  186. package/templates/contextkit/squads/agent-forge/lib/package-ops.mjs +101 -0
  187. package/templates/contextkit/squads/agent-forge/lib/packager.mjs +219 -0
  188. package/templates/contextkit/squads/agent-forge/lib/prompt-gen.mjs +122 -0
  189. package/templates/contextkit/squads/agent-forge/lib/rag-designer.mjs +102 -0
  190. package/templates/contextkit/squads/agent-forge/lib/router.mjs +165 -0
  191. package/templates/contextkit/squads/agent-forge/lib/tool-gen.mjs +113 -0
  192. package/templates/contextkit/squads/agent-forge/lib/yaml.mjs +47 -0
  193. package/templates/contextkit/squads/agent-forge/pipeline.yaml +65 -0
  194. package/templates/contextkit/squads/agent-forge/router/capability-matrix.json +112 -0
  195. package/templates/contextkit/squads/agent-forge/router/decision-rules.json +120 -0
  196. package/templates/contextkit/squads/agent-forge/templates/agent-package/.agentforgerc +12 -0
  197. package/templates/contextkit/squads/agent-forge/templates/agent-package/CHANGELOG.md +13 -0
  198. package/templates/contextkit/squads/agent-forge/templates/agent-package/LICENSE +5 -0
  199. package/templates/contextkit/squads/agent-forge/templates/agent-package/README.md +39 -0
  200. package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/go/README.md +10 -0
  201. package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/go/agent.go +14 -0
  202. package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/go/go.mod +3 -0
  203. package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/node/README.md +11 -0
  204. package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/node/index.js +53 -0
  205. package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/node/package.json +9 -0
  206. package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/python/README.md +10 -0
  207. package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/python/agent.py +16 -0
  208. package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/python/pyproject.toml +10 -0
  209. package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/golden.jsonl +1 -0
  210. package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/red-team.jsonl +3 -0
  211. package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/rubric.yaml +14 -0
  212. package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/run-eval.md +17 -0
  213. package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/thresholds.yaml +18 -0
  214. package/templates/contextkit/squads/agent-forge/templates/agent-package/examples/basic.node.md +17 -0
  215. package/templates/contextkit/squads/agent-forge/templates/agent-package/examples/with-fallback.node.md +24 -0
  216. package/templates/contextkit/squads/agent-forge/templates/agent-package/examples/with-rag.python.md +20 -0
  217. package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/audit.schema.json +23 -0
  218. package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/compliance.policy.yaml +43 -0
  219. package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/cost.policy.yaml +36 -0
  220. package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/fallback-chain.yaml +16 -0
  221. package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/quality.policy.yaml +43 -0
  222. package/templates/contextkit/squads/agent-forge/templates/agent-package/manifest.yaml +91 -0
  223. package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.anthropic.md +19 -0
  224. package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.canonical.md +25 -0
  225. package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.deepseek.md +21 -0
  226. package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.google.md +19 -0
  227. package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.ollama.md +21 -0
  228. package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.openai.md +20 -0
  229. package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/config.yaml +17 -0
  230. package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/index/.gitkeep +3 -0
  231. package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/ingestion/chunker.config.yaml +6 -0
  232. package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/ingestion/sources.yaml +8 -0
  233. package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/retrieval/query-template.md +16 -0
  234. package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/retrieval/rerank.config.yaml +6 -0
  235. package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/anthropic.tools.json +11 -0
  236. package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/deepseek.tools.json +14 -0
  237. package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/google.tools.json +11 -0
  238. package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/ollama.tools.json +14 -0
  239. package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/openai.tools.json +14 -0
  240. package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/schemas.canonical.json +25 -0
  241. package/templates/contextkit/starters/tanstack/README.md +86 -0
  242. package/templates/contextkit/starters/tanstack/index.html +12 -0
  243. package/templates/contextkit/starters/tanstack/package.json +25 -0
  244. package/templates/contextkit/starters/tanstack/src/main.tsx +40 -0
  245. package/templates/contextkit/starters/tanstack/src/router.tsx +12 -0
  246. package/templates/contextkit/starters/tanstack/src/routes/__root.tsx +10 -0
  247. package/templates/contextkit/starters/tanstack/src/routes/index.tsx +17 -0
  248. package/templates/contextkit/starters/tanstack/tsconfig.json +19 -0
  249. package/templates/contextkit/starters/tanstack/vite.config.ts +10 -0
  250. package/templates/contextkit/tools/scripts/adr-digest-core.mjs +42 -0
  251. package/templates/contextkit/tools/scripts/adr-digest.mjs +78 -0
  252. package/templates/contextkit/tools/scripts/agent-tuning.mjs +74 -0
  253. package/templates/contextkit/tools/scripts/aiso-audit.mjs +174 -0
  254. package/templates/contextkit/tools/scripts/audit-shared.mjs +129 -0
  255. package/templates/contextkit/tools/scripts/claim.mjs +133 -0
  256. package/templates/contextkit/tools/scripts/claude-md.mjs +123 -0
  257. package/templates/contextkit/tools/scripts/clean-drive.mjs +78 -0
  258. package/templates/contextkit/tools/scripts/context-config.mjs +111 -0
  259. package/templates/contextkit/tools/scripts/context-level.mjs +98 -0
  260. package/templates/contextkit/tools/scripts/context-pack.mjs +120 -0
  261. package/templates/contextkit/tools/scripts/contract-scan.mjs +186 -0
  262. package/templates/contextkit/tools/scripts/dashboard-data.mjs +198 -0
  263. package/templates/contextkit/tools/scripts/dashboard-html.mjs +215 -0
  264. package/templates/contextkit/tools/scripts/dashboard-server.mjs +129 -0
  265. package/templates/contextkit/tools/scripts/dashboard.mjs +107 -0
  266. package/templates/contextkit/tools/scripts/deep-analysis.mjs +62 -0
  267. package/templates/contextkit/tools/scripts/deps-audit.mjs +201 -0
  268. package/templates/contextkit/tools/scripts/detect-stack.mjs +164 -0
  269. package/templates/contextkit/tools/scripts/distill-detect.mjs +90 -0
  270. package/templates/contextkit/tools/scripts/doctor.mjs +165 -0
  271. package/templates/contextkit/tools/scripts/fleet.mjs +170 -0
  272. package/templates/contextkit/tools/scripts/generate-context.mjs +142 -0
  273. package/templates/contextkit/tools/scripts/gh-alerts.mjs +117 -0
  274. package/templates/contextkit/tools/scripts/git.mjs +97 -0
  275. package/templates/contextkit/tools/scripts/home.mjs +106 -0
  276. package/templates/contextkit/tools/scripts/mark-simulation.mjs +78 -0
  277. package/templates/contextkit/tools/scripts/media-gen.mjs +154 -0
  278. package/templates/contextkit/tools/scripts/pipeline-board.mjs +74 -0
  279. package/templates/contextkit/tools/scripts/pipeline-prioritize.mjs +68 -0
  280. package/templates/contextkit/tools/scripts/pipeline-session.mjs +99 -0
  281. package/templates/contextkit/tools/scripts/pipeline-validate.mjs +136 -0
  282. package/templates/contextkit/tools/scripts/pipeline.mjs +302 -0
  283. package/templates/contextkit/tools/scripts/playbook.mjs +123 -0
  284. package/templates/contextkit/tools/scripts/predictions-review.mjs +113 -0
  285. package/templates/contextkit/tools/scripts/release.mjs +60 -0
  286. package/templates/contextkit/tools/scripts/resume.mjs +114 -0
  287. package/templates/contextkit/tools/scripts/roadmap.mjs +86 -0
  288. package/templates/contextkit/tools/scripts/runs.mjs +116 -0
  289. package/templates/contextkit/tools/scripts/seo-audit.mjs +150 -0
  290. package/templates/contextkit/tools/scripts/session-digest.mjs +89 -0
  291. package/templates/contextkit/tools/scripts/session-reindex.mjs +91 -0
  292. package/templates/contextkit/tools/scripts/setup-complete.mjs +69 -0
  293. package/templates/contextkit/tools/scripts/squad-meta.mjs +23 -0
  294. package/templates/contextkit/tools/scripts/squad-pipeline-condition.mjs +192 -0
  295. package/templates/contextkit/tools/scripts/squad-pipeline.mjs +301 -0
  296. package/templates/contextkit/tools/scripts/squad.mjs +80 -0
  297. package/templates/contextkit/tools/scripts/stats.mjs +138 -0
  298. package/templates/contextkit/tools/scripts/sync-check.mjs +235 -0
  299. package/templates/contextkit/tools/scripts/tech-debt-detectors.mjs +76 -0
  300. package/templates/contextkit/tools/scripts/tech-debt-scan.mjs +164 -0
  301. package/templates/contextkit/tools/scripts/token-report.mjs +153 -0
  302. package/templates/contextkit/tools/scripts/visual-test.mjs +132 -0
  303. package/templates/contextkit/tools/scripts/watch.mjs +106 -0
  304. package/templates/contextkit/tools/scripts/workflow.mjs +136 -0
  305. package/templates/contextkit/tools/scripts/workspace-sync.mjs +220 -0
  306. package/templates/contextkit/tools/scripts/worktree-new.mjs +50 -0
  307. package/templates/contextkit/workflows/L1-static-loading.md +59 -0
  308. package/templates/contextkit/workflows/L2-session-ledger.md +86 -0
  309. package/templates/contextkit/workflows/L3-multi-session.md +80 -0
  310. package/templates/contextkit/workflows/L4-squads.md +68 -0
  311. package/templates/contextkit/workflows/L5-proactive.md +88 -0
  312. package/templates/contextkit/workflows/README.md +47 -0
  313. package/templates/contextkit/workflows/playbooks/distillation-cycle.md +74 -0
  314. package/templates/contextkit/workflows/playbooks/landing-page.md +197 -0
  315. package/templates/contextkit/workflows/playbooks/security-batch.md +68 -0
  316. package/templates/contextkit/workflows/playbooks/seo-aiso.md +288 -0
  317. package/templates/contextkit/workflows/playbooks/simulate-impact.md +83 -0
  318. package/templates/contextkit/workflows/playbooks/tanstack.md +164 -0
  319. package/templates/contextkit/workflows/playbooks/tech-debt-sweep.md +77 -0
  320. package/templates/docs/CHANGELOG.md.tpl +11 -0
  321. package/templates/gitattributes +3 -0
  322. package/templates/github/ISSUE_TEMPLATE/bug_report.md +30 -0
  323. package/templates/github/ISSUE_TEMPLATE/feature_request.md +22 -0
  324. package/templates/github/PULL_REQUEST_TEMPLATE.md +27 -0
  325. package/templates/github/dependabot.yml +27 -0
  326. package/templates/github/workflows/quality.yml +36 -0
  327. package/templates/github/workflows/security.yml +54 -0
  328. package/tools/install/cli.mjs +62 -0
  329. package/tools/install/fs.mjs +56 -0
  330. package/tools/install/git.mjs +114 -0
  331. package/tools/install/project.mjs +51 -0
  332. package/tools/install/uninstall.mjs +54 -0
  333. package/tools/integration-test-compozy.mjs +88 -0
  334. package/tools/integration-test-guards.mjs +269 -0
  335. package/tools/integration-test-tooling-agent-forge.mjs +189 -0
  336. package/tools/integration-test-tooling-pipeline.mjs +164 -0
  337. package/tools/integration-test-tooling.mjs +172 -0
  338. package/tools/integration-test.mjs +228 -0
  339. package/tools/it-helpers.mjs +60 -0
  340. package/tools/selfcheck-agent-forge-ops.mjs +107 -0
  341. package/tools/selfcheck-agent-forge.mjs +304 -0
  342. package/tools/selfcheck-config.mjs +80 -0
  343. package/tools/selfcheck-runtime.mjs +135 -0
  344. package/tools/selfcheck-source.mjs +326 -0
  345. package/tools/selfcheck.mjs +268 -0
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Releases path claims for the current session (Level >= 3).
4
+ *
5
+ * With a path arg: removes just that claim. With no arg: removes ALL claims
6
+ * for this session (deletes the workspace file). Then regenerates WORKSPACE.md.
7
+ *
8
+ * Usage: node contextkit/tools/scripts/release.mjs [path]
9
+ */
10
+ import { execFileSync } from 'node:child_process';
11
+ import { existsSync } from 'node:fs';
12
+ import { readFile, rm } from 'node:fs/promises';
13
+ import { resolve } from 'node:path';
14
+ import { sanitizeSid } from '../../runtime/hooks/ledger.mjs';
15
+ import { writeFileAtomic } from '../../runtime/hooks/safe-io.mjs';
16
+
17
+ const ROOT = process.cwd();
18
+ const WS_DIR = resolve(ROOT, '.claude/.workspace');
19
+ const LAST_TOUCHED = resolve(ROOT, '.claude/.sessions/.last-touched');
20
+
21
+ async function sessionId() {
22
+ try {
23
+ return JSON.parse(await readFile(LAST_TOUCHED, 'utf-8')).sessionId;
24
+ } catch {
25
+ return `local_${process.pid}`;
26
+ }
27
+ }
28
+
29
+ async function main() {
30
+ const target = process.argv[2]?.replaceAll('\\', '/');
31
+ const sid = sanitizeSid(await sessionId());
32
+ const file = resolve(WS_DIR, `${sid}.json`);
33
+
34
+ if (!existsSync(file)) {
35
+ console.log('ā„¹ļø No active claims for this session.');
36
+ return;
37
+ }
38
+
39
+ if (!target) {
40
+ await rm(file, { force: true });
41
+ console.log(`āœ… Released ALL claims for session ${sid.slice(0, 8)}.`);
42
+ } else {
43
+ const claimRecord = JSON.parse(await readFile(file, 'utf-8'));
44
+ const before = (claimRecord.claims || []).length;
45
+ claimRecord.claims = (claimRecord.claims || []).filter((c) => c.path !== target);
46
+ await writeFileAtomic(file, JSON.stringify(claimRecord, null, 2));
47
+ console.log(before === claimRecord.claims.length ? `ā„¹ļø No claim matched "${target}".` : `āœ… Released claim "${target}".`);
48
+ }
49
+
50
+ try {
51
+ execFileSync('node', ['contextkit/tools/scripts/workspace-sync.mjs'], { cwd: ROOT, stdio: 'ignore' });
52
+ } catch {
53
+ /* best effort */
54
+ }
55
+ }
56
+
57
+ main().catch((err) => {
58
+ console.error('āŒ release failed:', err);
59
+ process.exit(1);
60
+ });
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `/resume` — re-bind the current Claude Code session to a previously
4
+ * **unregistered** ledger (ticket 046, Compozy follow-through). When a
5
+ * session ended without `/log-session`, its ledger sits in
6
+ * `.claude/.sessions/<sid>.json` with `registered: false` and the boot
7
+ * context flags it as drift. `/resume <sid>` makes that ledger the
8
+ * active one — subsequent edits continue appending there, `/log-session`
9
+ * registers it properly, claims (if any) are re-asserted under the same id.
10
+ *
11
+ * Usage:
12
+ * resume.mjs list unregistered candidates
13
+ * resume.mjs <session-id> re-bind to that session
14
+ * resume.mjs --json machine-readable listing
15
+ *
16
+ * Refusal modes (rule 8 — refuse, don't assume):
17
+ * - target id not present in `.claude/.sessions/` → exit 1
18
+ * - target id is already registered (no drift to recover) → exit 1
19
+ * - target's path claims overlap an active session's claims → exit 1
20
+ *
21
+ * Read-only on the session ledgers themselves; only rewrites the
22
+ * `.last-touched` pointer atomically.
23
+ */
24
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
25
+ import { resolve } from 'node:path';
26
+ import { LAST_TOUCHED_PATH, listAllLedgers, sanitizeSid } from '../../runtime/hooks/ledger.mjs';
27
+ import { writeFileAtomicSync } from '../../runtime/hooks/safe-io.mjs';
28
+
29
+ const ROOT = process.cwd();
30
+ const WORKSPACE_DIR = resolve(ROOT, '.claude/.workspace');
31
+ const STALE_AFTER_MS = 60 * 60 * 1000;
32
+
33
+ /**
34
+ * Returns the set of paths currently claimed by any *active* (non-stale)
35
+ * session OTHER than `excludeSid`. Used to detect resume-time conflicts.
36
+ *
37
+ * @param {string} excludeSid — the session id we're resuming to (its own
38
+ * claims shouldn't count as a conflict with itself)
39
+ * @returns {Set<string>}
40
+ */
41
+ function activePathClaims(excludeSid) {
42
+ const out = new Set();
43
+ if (!existsSync(WORKSPACE_DIR)) return out;
44
+ for (const name of readdirSync(WORKSPACE_DIR)) {
45
+ if (!name.endsWith('.json')) continue;
46
+ try {
47
+ const rec = JSON.parse(readFileSync(resolve(WORKSPACE_DIR, name), 'utf-8'));
48
+ if (rec.sessionId === excludeSid) continue;
49
+ if (typeof rec.lastHeartbeat !== 'number' || Date.now() - rec.lastHeartbeat > STALE_AFTER_MS) continue;
50
+ for (const c of rec.claims || []) out.add(c.path);
51
+ } catch { /* skip malformed */ }
52
+ }
53
+ return out;
54
+ }
55
+
56
+ /**
57
+ * Reads `claims[]` for the given session id, if any. Workspace records are
58
+ * optional — a session can be in drift without ever calling `/claim`.
59
+ */
60
+ function claimsFor(sid) {
61
+ const file = resolve(WORKSPACE_DIR, `${sid}.json`);
62
+ if (!existsSync(file)) return [];
63
+ try { return JSON.parse(readFileSync(file, 'utf-8')).claims || []; } catch { return []; }
64
+ }
65
+
66
+ async function listUnregistered() {
67
+ const all = await listAllLedgers();
68
+ return all.filter(({ ledger }) => !ledger.registered && Array.isArray(ledger.modifications) && ledger.modifications.length > 0);
69
+ }
70
+
71
+ function fmtAgo(ms) {
72
+ const s = Math.floor((Date.now() - ms) / 1000);
73
+ if (s < 60) return `${s}s ago`;
74
+ if (s < 3600) return `${Math.floor(s / 60)}m ago`;
75
+ if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
76
+ return `${Math.floor(s / 86400)}d ago`;
77
+ }
78
+
79
+ async function listCmd(asJson) {
80
+ const candidates = await listUnregistered();
81
+ if (asJson) { console.log(JSON.stringify(candidates.map(({ sessionId, ledger }) => ({ sessionId, modifications: ledger.modifications.length, startedAt: ledger.startedAt, claims: claimsFor(sessionId).length })), null, 2)); return; }
82
+ if (candidates.length === 0) { console.log(' No unregistered sessions found. The workspace is clean.'); return; }
83
+ console.log(`\n ${candidates.length} unregistered session(s) — candidates for /resume:\n`);
84
+ for (const { sessionId, ledger } of candidates) {
85
+ const claims = claimsFor(sessionId).length;
86
+ const claimNote = claims > 0 ? ` Ā· ${claims} claim(s)` : '';
87
+ console.log(` ${sessionId.slice(0, 12).padEnd(12)} Ā· ${ledger.modifications.length} edit(s) Ā· started ${fmtAgo(ledger.startedAt)}${claimNote}`);
88
+ }
89
+ console.log('\n Resume with: node contextkit/tools/scripts/resume.mjs <session-id>\n');
90
+ }
91
+
92
+ async function resumeCmd(targetSid) {
93
+ const sid = sanitizeSid(targetSid);
94
+ const candidates = await listUnregistered();
95
+ const match = candidates.find((c) => c.sessionId === sid || c.sessionId.startsWith(sid));
96
+ if (!match) { console.error(`āœ— session "${targetSid}" not found among unregistered drift candidates.`); process.exit(1); }
97
+ if (match.ledger.registered) { console.error(`āœ— session ${match.sessionId} is already registered — nothing to resume.`); process.exit(1); }
98
+ const myClaims = new Set(claimsFor(match.sessionId).map((c) => c.path));
99
+ const otherActiveClaims = activePathClaims(match.sessionId);
100
+ const conflicts = [...myClaims].filter((p) => otherActiveClaims.has(p));
101
+ if (conflicts.length > 0) { console.error(`āœ— cannot resume: path(s) claimed by another active session: ${conflicts.join(', ')}`); process.exit(1); }
102
+ writeFileAtomicSync(LAST_TOUCHED_PATH, JSON.stringify({ sessionId: match.sessionId, at: Date.now() }));
103
+ console.log(`ā–¶ Resumed session ${match.sessionId.slice(0, 12)} (${match.ledger.modifications.length} prior edit(s), ${myClaims.size} claim(s)).`);
104
+ console.log(` Run /log-session to register this session properly when you're done.`);
105
+ }
106
+
107
+ async function main() {
108
+ const arg = process.argv[2];
109
+ if (!arg || arg === '--json') { await listCmd(arg === '--json'); return; }
110
+ if (arg === '--help' || arg === '-h') { console.log('Usage: resume.mjs [<session-id> | --json]'); return; }
111
+ await resumeCmd(arg);
112
+ }
113
+
114
+ main().catch((err) => { console.error(`āœ— resume failed: ${err?.message || err}`); process.exit(1); });
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Roadmap helper — find / status / init for the product roadmap.
4
+ *
5
+ * The roadmap is prose managed by the `/roadmap` command; this gives that
6
+ * command deterministic facts:
7
+ * - `find` — scan the repo for an EXISTING roadmap/PRD/spec/vision doc to
8
+ * import (so we don't recreate one the project already has).
9
+ * - `status` — is `contextkit/memory/roadmap.md` defined (vs the placeholder)?
10
+ * - `init` — create the seed roadmap.md if missing.
11
+ *
12
+ * Usage: node contextkit/tools/scripts/roadmap.mjs <find|status|init> [--json]
13
+ */
14
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
15
+ import { join, relative, resolve } from 'node:path';
16
+ import { pathsFor } from '../../runtime/config/paths.mjs';
17
+
18
+ const ROOT = process.cwd();
19
+ const P = pathsFor(ROOT);
20
+ const CANON = P.roadmap;
21
+ const PLACEHOLDER = 'ROADMAP-NOT-DEFINED';
22
+ const SKIP = new Set(['node_modules', '.git', 'dist', 'build', 'out', '.next', '.turbo', 'vendor', 'target', '__pycache__', '.claude']);
23
+ const NAME_RE = /(road[\s_-]?map|^prd\b|product[\s_-]?spec|product[\s_-]?vision|^vision|^spec)\b/i;
24
+
25
+ function walk(dir, depth, acc) {
26
+ if (depth > 3) return acc;
27
+ let entries;
28
+ try {
29
+ entries = readdirSync(dir, { withFileTypes: true });
30
+ } catch {
31
+ return acc;
32
+ }
33
+ for (const e of entries) {
34
+ if (e.isDirectory()) {
35
+ if (!SKIP.has(e.name) && !e.name.startsWith('.tmp')) walk(join(dir, e.name), depth + 1, acc);
36
+ } else if (/\.(md|markdown|txt)$/i.test(e.name)) {
37
+ const rel = relative(ROOT, join(dir, e.name)).replaceAll('\\', '/');
38
+ // Don't report our own canonical/seed file as a "found" external roadmap.
39
+ if (rel === 'contextkit/memory/roadmap.md') continue;
40
+ if (NAME_RE.test(e.name)) acc.push(rel);
41
+ }
42
+ }
43
+ return acc;
44
+ }
45
+
46
+ function isDefined() {
47
+ if (!existsSync(CANON)) return false;
48
+ try {
49
+ const t = readFileSync(CANON, 'utf-8');
50
+ return !t.includes(PLACEHOLDER) && t.trim().length > 0;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ const cmd = process.argv[2];
57
+ const json = process.argv.includes('--json');
58
+
59
+ if (cmd === 'find') {
60
+ const found = walk(ROOT, 0, []);
61
+ if (json) console.log(JSON.stringify({ canonicalDefined: isDefined(), found }, null, 2));
62
+ else {
63
+ console.log(isDefined() ? 'āœ… contextkit/memory/roadmap.md is defined.' : 'ā„¹ļø contextkit/memory/roadmap.md not defined yet.');
64
+ if (found.length) {
65
+ console.log('\nPossible existing roadmap/PRD/spec files to import:');
66
+ for (const f of found) console.log(` - ${f}`);
67
+ } else {
68
+ console.log('\nNo existing roadmap/PRD/spec found — propose one from analysis + user objectives.');
69
+ }
70
+ }
71
+ } else if (cmd === 'status') {
72
+ if (json) console.log(JSON.stringify({ defined: isDefined(), path: 'contextkit/memory/roadmap.md' }));
73
+ else console.log(isDefined() ? 'defined' : 'not-defined');
74
+ process.exit(isDefined() ? 0 : 1);
75
+ } else if (cmd === 'init') {
76
+ if (existsSync(CANON)) {
77
+ console.log('roadmap.md already exists — leaving it untouched.');
78
+ } else {
79
+ mkdirSync(P.memory, { recursive: true });
80
+ writeFileSync(CANON, `# Product Roadmap\n\n<!-- ${PLACEHOLDER} -->\n_No roadmap defined yet._ Run \`/roadmap\`.\n`, 'utf-8');
81
+ console.log('āœ… seeded contextkit/memory/roadmap.md');
82
+ }
83
+ } else {
84
+ console.error('Usage: roadmap.mjs <find|status|init> [--json]');
85
+ process.exit(1);
86
+ }
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `/runs` — lists the last N in-flight items (tasks + pipeline runs) across the
4
+ * project, reading the canonical state.json substrate ([ADR-0015](../../memory/decisions/0015-pipeline-dsl-working-stage-and-multi-session-work-claims.md) Part C).
5
+ *
6
+ * Read-only — never mutates state. Refuses cleanly when no state files exist
7
+ * ("no runs yet"). Token-light by default — prints the 20 most recent; `--all`
8
+ * shows everything, `--json` for machine-readable output.
9
+ *
10
+ * Usage:
11
+ * node contextkit/tools/scripts/runs.mjs # last 20, all kinds
12
+ * node contextkit/tools/scripts/runs.mjs --kind task # tasks only
13
+ * node contextkit/tools/scripts/runs.mjs --kind pipeline-run
14
+ * node contextkit/tools/scripts/runs.mjs --all # no limit
15
+ * node contextkit/tools/scripts/runs.mjs --json # JSON for tooling
16
+ */
17
+ import { listStates } from '../../runtime/state/state-io.mjs';
18
+ import { pathsFor } from '../../runtime/config/paths.mjs';
19
+
20
+ const ROOT = process.cwd();
21
+ const PIPE = pathsFor(ROOT).pipeline;
22
+ const DEFAULT_LIMIT = 20;
23
+ const STATUS_BADGE = { backlog: 'šŸ“‹', working: 'šŸ”µ', testing: '🟔', done: 'āœ…', running: 'šŸ”„', 'blocked-on-checkpoint': 'āø', failed: 'āŒ' };
24
+
25
+ /** Returns the value after `--name`, or undefined when absent. */
26
+ function arg(name) {
27
+ const i = process.argv.indexOf(`--${name}`);
28
+ return i !== -1 && i + 1 < process.argv.length ? process.argv[i + 1] : undefined;
29
+ }
30
+
31
+ function flag(name) {
32
+ return process.argv.includes(`--${name}`);
33
+ }
34
+
35
+ /** Best-effort age formatter mirroring workspace-sync's `relativeTime`. */
36
+ function ago(ms) {
37
+ if (typeof ms !== 'number' || ms <= 0) return '—';
38
+ const s = Math.floor((Date.now() - ms) / 1000);
39
+ if (s < 60) return `${s}s ago`;
40
+ const m = Math.floor(s / 60);
41
+ if (m < 60) return `${m}m ago`;
42
+ const h = Math.floor(m / 60);
43
+ if (h < 24) return `${h}h ago`;
44
+ return `${Math.floor(h / 24)}d ago`;
45
+ }
46
+
47
+ /** Duration when both startedAt + endedAt are present, else "—". */
48
+ function duration(state) {
49
+ if (typeof state.startedAt !== 'number' || typeof state.endedAt !== 'number') return '—';
50
+ const s = Math.max(0, Math.floor((state.endedAt - state.startedAt) / 1000));
51
+ const h = Math.floor(s / 3600);
52
+ const m = Math.floor((s % 3600) / 60);
53
+ const sec = s % 60;
54
+ return h > 0 ? `${h}h ${m}m` : m > 0 ? `${m}m ${sec}s` : `${sec}s`;
55
+ }
56
+
57
+ function renderTasks(tasks) {
58
+ if (tasks.length === 0) return null;
59
+ const out = ['šŸ“‹ tasks', '─'.repeat(60)];
60
+ for (const t of tasks) {
61
+ const badge = STATUS_BADGE[t.status] || 'Ā·';
62
+ const owner = t.ownerUser ? ` Ā· ${t.ownerUser}` : '';
63
+ const branch = t.branch ? ` Ā· ${t.branch}` : '';
64
+ const when = t.endedAt ? `ended ${ago(t.endedAt)} (${duration(t)})` : `started ${ago(t.startedAt)}`;
65
+ out.push(` ${badge} ${t.id.padEnd(5)} [${t.status.padEnd(8)}]${owner}${branch} Ā· ${when}`);
66
+ }
67
+ return out.join('\n');
68
+ }
69
+
70
+ function renderPipelineRuns(runs) {
71
+ if (runs.length === 0) return null;
72
+ const out = ['šŸ¤– pipeline runs', '─'.repeat(60)];
73
+ for (const r of runs) {
74
+ const badge = STATUS_BADGE[r.status] || 'Ā·';
75
+ const step = r.step ? `${r.step.current ?? '?'}/${r.step.total ?? '?'} steps` : '';
76
+ const cycles = r.cycles && Object.keys(r.cycles).length > 0 ? `(${Object.entries(r.cycles).map(([k, v]) => `${k}Ɨ${v}`).join(', ')})` : '';
77
+ const when = r.endedAt ? `ended ${ago(r.endedAt)} (${duration(r)})` : `started ${ago(r.startedAt)}`;
78
+ out.push(` ${badge} ${r.id.padEnd(20)} [${r.status.padEnd(10)}] ${step} ${cycles} Ā· ${when}`);
79
+ }
80
+ return out.join('\n');
81
+ }
82
+
83
+ function main() {
84
+ const kindFilter = arg('kind');
85
+ if (kindFilter && !['task', 'pipeline-run'].includes(kindFilter)) {
86
+ console.error(`Invalid --kind "${kindFilter}". Use "task" or "pipeline-run".`);
87
+ process.exit(1);
88
+ }
89
+ const limit = flag('all') ? Infinity : Number(arg('limit')) || DEFAULT_LIMIT;
90
+ const all = listStates(PIPE, kindFilter ? { kind: kindFilter } : {});
91
+ const truncated = Number.isFinite(limit) ? all.slice(0, limit) : all;
92
+
93
+ if (flag('json')) {
94
+ console.log(JSON.stringify({ total: all.length, shown: truncated.length, states: truncated }, null, 2));
95
+ return;
96
+ }
97
+
98
+ if (all.length === 0) {
99
+ console.log(' No runs yet. Start a task with `/pipeline start <id>` or run a squad pipeline.');
100
+ return;
101
+ }
102
+
103
+ const tasks = truncated.filter((s) => s.kind === 'task');
104
+ const runs = truncated.filter((s) => s.kind === 'pipeline-run');
105
+ const sections = [renderTasks(tasks), renderPipelineRuns(runs)].filter(Boolean);
106
+ if (sections.length === 0) {
107
+ console.log(` No ${kindFilter ?? 'state'} entries match. Total in store: ${all.length}.`);
108
+ return;
109
+ }
110
+ console.log('\n' + sections.join('\n\n') + '\n');
111
+ if (all.length > truncated.length) {
112
+ console.log(` (showing ${truncated.length} of ${all.length} — pass --all for the full list)`);
113
+ }
114
+ }
115
+
116
+ main();
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SEO audit — static analyser for classical SEO smells (ADR-0025).
4
+ *
5
+ * Walks the project for page-shaped files (`.html`, `.astro`, `.jsx`,
6
+ * `.tsx`, `.vue`, `.svelte`, `.mdx`), checks each against the SEO
7
+ * checklist in `contextkit/workflows/playbooks/seo-aiso.md`, and emits
8
+ * findings as either a coloured terminal table (default) or JSON
9
+ * (`--json`).
10
+ *
11
+ * Exit code 1 when any `critical` finding is present (currently only
12
+ * SPA_ENTRYPOINT) — so CI can gate on it. Exit code 0 otherwise.
13
+ *
14
+ * Zero deps. Defensive (rule 2 — never break the dev loop).
15
+ */
16
+ import { readFileSync, existsSync } from 'node:fs';
17
+ import { resolve, relative } from 'node:path';
18
+ import { detectFramework, isSpaFramework, lineOf, renderFindings, exitCodeFor, walkProject } from './audit-shared.mjs';
19
+
20
+ const PAGE_EXTS = ['.html', '.astro', '.jsx', '.tsx', '.vue', '.svelte', '.mdx'];
21
+
22
+ const SEVERITY = {
23
+ SPA_ENTRYPOINT: 'critical',
24
+ MISSING_TITLE: 'high',
25
+ MISSING_DESCRIPTION: 'high',
26
+ MULTIPLE_H1: 'high',
27
+ MISSING_CANONICAL: 'medium',
28
+ MISSING_ALT: 'medium',
29
+ MISSING_SITEMAP: 'high',
30
+ MISSING_ROBOTS: 'medium',
31
+ };
32
+
33
+ const finding = (rel) => (code, line, message) =>
34
+ ({ file: rel, code, line, severity: SEVERITY[code] || 'medium', message });
35
+
36
+ /** Project-level checks: presence of sitemap.xml, robots.txt, SPA entry-point smell. */
37
+ function checkProject(root, framework) {
38
+ const out = [];
39
+ const sitemapPaths = ['sitemap.xml', 'public/sitemap.xml', 'static/sitemap.xml', 'src/pages/sitemap.xml.ts'];
40
+ const robotsPaths = ['robots.txt', 'public/robots.txt', 'static/robots.txt'];
41
+ if (!sitemapPaths.some((p) => existsSync(resolve(root, p)))) {
42
+ out.push({ file: 'public/sitemap.xml', code: 'MISSING_SITEMAP', line: 0, severity: SEVERITY.MISSING_SITEMAP, message: 'no sitemap.xml found at root, public/, or static/' });
43
+ }
44
+ if (!robotsPaths.some((p) => existsSync(resolve(root, p)))) {
45
+ out.push({ file: 'public/robots.txt', code: 'MISSING_ROBOTS', line: 0, severity: SEVERITY.MISSING_ROBOTS, message: 'no robots.txt found at root, public/, or static/' });
46
+ }
47
+ const indexHtml = resolve(root, 'index.html');
48
+ if (existsSync(indexHtml)) {
49
+ let html = '';
50
+ try { html = readFileSync(indexHtml, 'utf8'); } catch { /* defensive */ }
51
+ const emptyRoot = /<div\s+id=["'](root|app|__next|___gatsby)["']\s*>\s*<\/div>/i.test(html);
52
+ if (emptyRoot && isSpaFramework(framework)) {
53
+ out.push({
54
+ file: 'index.html',
55
+ code: 'SPA_ENTRYPOINT',
56
+ line: 1,
57
+ severity: SEVERITY.SPA_ENTRYPOINT,
58
+ message: 'empty <div id="root"></div> with no SSG/SSR framework detected — landing pages must be indexable. Pick Astro (recommended) or Next App Router; see ADR-0025.',
59
+ });
60
+ }
61
+ }
62
+ return out;
63
+ }
64
+
65
+ /** Per-file checks. The `<title>` / `<meta description>` / canonical checks fire only for HTML-shaped files. */
66
+ function checkFile(file, text, rel) {
67
+ const f = finding(rel);
68
+ const out = [];
69
+ const isHtmlLike = /\.(html|astro)$/i.test(file);
70
+
71
+ if (isHtmlLike) {
72
+ if (!/<title>\s*[^<\s][^<]*<\/title>/i.test(text)) {
73
+ out.push(f('MISSING_TITLE', 1, 'no <title> in head, or <title> is empty'));
74
+ }
75
+ if (!/<meta\s+[^>]*name=["']description["'][^>]*content=["'][^"']{20,}["']/i.test(text)) {
76
+ out.push(f('MISSING_DESCRIPTION', 1, 'no <meta name="description"> with at least 20 chars of content'));
77
+ }
78
+ if (!/<link\s+[^>]*rel=["']canonical["']/i.test(text)) {
79
+ out.push(f('MISSING_CANONICAL', 1, 'no <link rel="canonical"> — Google may pick a wrong canonical'));
80
+ }
81
+ }
82
+
83
+ const h1s = [...text.matchAll(/<h1[\s>]/gi)];
84
+ if (h1s.length > 1) {
85
+ out.push(f('MULTIPLE_H1', lineOf(text, h1s[1].index), `${h1s.length} <h1> tags found; only one per page is allowed`));
86
+ }
87
+
88
+ for (const m of text.matchAll(/<img\b([^>]*)>/gi)) {
89
+ const attrs = m[1];
90
+ if (!/\balt\s*=/i.test(attrs)) {
91
+ out.push(f('MISSING_ALT', lineOf(text, m.index), '<img> without alt — refuses screen readers and SEO'));
92
+ }
93
+ }
94
+ return out;
95
+ }
96
+
97
+ /**
98
+ * Run the SEO audit against a project root.
99
+ *
100
+ * @param {string} root absolute path
101
+ * @returns {{ framework: string | null, findings: object[] }}
102
+ */
103
+ export function runSeoAudit(root) {
104
+ const framework = detectFramework(root);
105
+ const findings = [...checkProject(root, framework)];
106
+ for (const file of walkProject(root, PAGE_EXTS)) {
107
+ let text = '';
108
+ try { text = readFileSync(file, 'utf8'); } catch { continue; }
109
+ const rel = relative(root, file).replaceAll('\\', '/');
110
+ findings.push(...checkFile(file, text, rel));
111
+ }
112
+ return { framework, findings };
113
+ }
114
+
115
+ const argv = process.argv.slice(2);
116
+ const wantJson = argv.includes('--json');
117
+ const wantHelp = argv.includes('--help') || argv.includes('-h');
118
+
119
+ const isMain = (() => {
120
+ try {
121
+ const here = new URL(import.meta.url).pathname.toLowerCase();
122
+ const entry = process.argv[1]
123
+ ? new URL('file://' + process.argv[1].replace(/\\/g, '/')).pathname.toLowerCase()
124
+ : '';
125
+ return here === entry;
126
+ } catch { return false; }
127
+ })();
128
+
129
+ if (isMain) {
130
+ if (wantHelp) {
131
+ process.stdout.write(`Usage: seo-audit.mjs [--json]
132
+
133
+ Scans the project for SEO smells per ADR-0025. Exit code 1 on any
134
+ critical finding (currently only SPA_ENTRYPOINT) so CI can gate.
135
+
136
+ --json emit findings as JSON to stdout (machine-readable)
137
+ --help this message
138
+ `);
139
+ process.exit(0);
140
+ }
141
+ const root = process.cwd();
142
+ const result = runSeoAudit(root);
143
+ if (wantJson) {
144
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
145
+ } else {
146
+ process.stdout.write(`\n Framework detected: ${result.framework || 'none (plain HTML or unknown)'}`);
147
+ process.stdout.write(renderFindings(result.findings, { title: 'SEO audit' }));
148
+ }
149
+ process.exit(exitCodeFor(result.findings));
150
+ }
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Session digest — compact, deterministic view of recent session logs. [ADR-0027]
4
+ *
5
+ * Replaces "read the last N raw session files" (75–188 lines each) with a ~6-line
6
+ * structured digest per session, so token-heavy commands (`/distill-sessions`,
7
+ * `/retro`, `/tune-agents`) reason over digests and open a full log only on demand.
8
+ * Read-only, zero third-party deps. Parsing is single-sourced in
9
+ * `runtime/hooks/session-digest-core.mjs` (also used by the boot hook — rule 4).
10
+ *
11
+ * Usage:
12
+ * node contextkit/tools/scripts/session-digest.mjs # last 10 (human)
13
+ * node contextkit/tools/scripts/session-digest.mjs --last 5
14
+ * node contextkit/tools/scripts/session-digest.mjs --id 2026-06-03-36-foo.md
15
+ * node contextkit/tools/scripts/session-digest.mjs --json # machine-readable
16
+ */
17
+ import { readFile, readdir } from 'node:fs/promises';
18
+ import { resolve, basename } from 'node:path';
19
+ import { pathsFor } from '../../runtime/config/paths.mjs';
20
+ import {
21
+ SESSION_FILENAME_RE,
22
+ parseSessionLog,
23
+ renderDigest,
24
+ } from '../../runtime/hooks/session-digest-core.mjs';
25
+
26
+ const ROOT = process.cwd();
27
+ const P = pathsFor(ROOT);
28
+ const args = process.argv.slice(2);
29
+ const flag = (name) => args.includes(name);
30
+ const opt = (name) => {
31
+ const i = args.indexOf(name);
32
+ return i >= 0 ? args[i + 1] : undefined;
33
+ };
34
+
35
+ const readSafe = (abs) => readFile(abs, 'utf-8').catch(() => null);
36
+
37
+ /** Session files under the sessions dir, newest (by number, then date) first. */
38
+ async function listSessions() {
39
+ let files = [];
40
+ try {
41
+ files = await readdir(P.sessions);
42
+ } catch {
43
+ return [];
44
+ }
45
+ return files
46
+ .map((f) => ({ f, m: SESSION_FILENAME_RE.exec(f) }))
47
+ .filter((e) => e.m)
48
+ .map((e) => ({ filename: e.f, number: Number.parseInt(e.m[2], 10), date: e.m[1] }))
49
+ .sort((a, b) => b.number - a.number || b.date.localeCompare(a.date));
50
+ }
51
+
52
+ /** Parse one session file into a record (null when unreadable). */
53
+ async function recordFor(filename) {
54
+ const text = await readSafe(resolve(P.sessions, filename));
55
+ if (text === null) return null;
56
+ return parseSessionLog(text, filename);
57
+ }
58
+
59
+ async function main() {
60
+ const id = opt('--id');
61
+ let records;
62
+ if (id) {
63
+ const rec = await recordFor(basename(id));
64
+ records = rec ? [rec] : [];
65
+ } else {
66
+ const last = Math.max(1, Number.parseInt(opt('--last') || '10', 10) || 10);
67
+ const picked = (await listSessions()).slice(0, last);
68
+ records = (await Promise.all(picked.map((e) => recordFor(e.filename)))).filter(Boolean);
69
+ }
70
+
71
+ if (flag('--json')) {
72
+ process.stdout.write(JSON.stringify({ count: records.length, sessions: records }, null, 2) + '\n');
73
+ return;
74
+ }
75
+
76
+ if (records.length === 0) {
77
+ console.log('No session logs found (contextkit/memory/sessions/). Register one with /log-session.');
78
+ return;
79
+ }
80
+ console.log(`\n🧬 Session digest — ${records.length} session(s), newest first\n`);
81
+ const blocks = records.map((r) => renderDigest(r) || '_(unparseable session — open the full log)_');
82
+ console.log(blocks.join('\n\n'));
83
+ console.log('\nOpen a full log only if a digest flags something to inspect.');
84
+ }
85
+
86
+ main().catch((err) => {
87
+ console.error('āŒ session-digest failed:', err?.message ?? err);
88
+ process.exit(1);
89
+ });