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,55 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * commit-msg git hook (Level >= 3) — validates Conventional Commits.
4
+ *
5
+ * Pattern: <type>(<scope>)?: <subject>
6
+ * - type: feat | fix | chore | docs | refactor | test | ci | build | perf | style | revert
7
+ * - scope: optional, lowercase a-z0-9-
8
+ * - subject: 1..100 chars, no trailing period
9
+ *
10
+ * Bypass: include `[skip-cc]` anywhere in the subject.
11
+ *
12
+ * Invoked by `.git/hooks/commit-msg` (the installer drops a thin wrapper that
13
+ * calls this file). Exit 0 = allowed, 1 = blocked.
14
+ */
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const messageFile = process.argv[2];
18
+ if (!messageFile) {
19
+ console.error('commit-msg hook: missing message file argument');
20
+ process.exit(1);
21
+ }
22
+
23
+ const subject = readFileSync(messageFile, 'utf-8').split('\n')[0].trim();
24
+
25
+ if (/^(Merge|Revert|fixup!|squash!|amend!|wip:)/i.test(subject) || subject.includes('[skip-cc]')) {
26
+ process.exit(0);
27
+ }
28
+
29
+ const PATTERN = /^(feat|fix|chore|docs|refactor|test|ci|build|perf|style|revert)(\([a-z0-9-]+\))?: .{1,100}$/;
30
+
31
+ if (!PATTERN.test(subject)) {
32
+ console.error('');
33
+ console.error('✗ Commit message does not follow Conventional Commits format.');
34
+ console.error('');
35
+ console.error(`Got: ${subject}`);
36
+ console.error('');
37
+ console.error('Expected: <type>(<scope>)?: <subject>');
38
+ console.error(' type: feat | fix | chore | docs | refactor | test | ci | build | perf | style | revert');
39
+ console.error(' subject: 1..100 chars, no trailing period');
40
+ console.error('');
41
+ console.error('Examples:');
42
+ console.error(' feat(api): add schedule endpoint');
43
+ console.error(' fix(ui): correct safe-area inset on notch');
44
+ console.error(' chore: bump dependencies');
45
+ console.error('');
46
+ console.error('Bypass intentionally with `[skip-cc]` in the subject.');
47
+ process.exit(1);
48
+ }
49
+
50
+ if (subject.endsWith('.')) {
51
+ console.error('✗ Commit subject must not end with a period.');
52
+ process.exit(1);
53
+ }
54
+
55
+ process.exit(0);
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pre-commit git hook (Level >= 3).
4
+ *
5
+ * Goal: keep the derived indices (`contextkit/memory/SESSIONS.md` and
6
+ * `WORKSPACE.md`) in sync with their source-of-truth files, FAST (< 1s).
7
+ * Heavy validation (type-check, lint, tests) belongs in CI, not here.
8
+ *
9
+ * Invoked by `.git/hooks/pre-commit` (a thin wrapper the installer drops).
10
+ * Bypass: `git commit --no-verify`.
11
+ */
12
+ import { execSync } from 'node:child_process';
13
+ import { existsSync } from 'node:fs';
14
+ import { resolve } from 'node:path';
15
+ import { pathsFor } from '../config/paths.mjs';
16
+
17
+ const ROOT = process.cwd();
18
+ const P = pathsFor(ROOT);
19
+
20
+ function safeRun(cmd) {
21
+ try {
22
+ execSync(cmd, { cwd: ROOT, stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000 });
23
+ } catch {
24
+ /* never block the commit on a derived-doc regen failure */
25
+ }
26
+ }
27
+
28
+ function main() {
29
+ console.log('› pre-commit: regenerating derived docs...');
30
+
31
+ if (existsSync(P.sessions)) {
32
+ safeRun('node contextkit/tools/scripts/session-reindex.mjs');
33
+ safeRun('git add contextkit/memory/SESSIONS.md');
34
+ }
35
+ if (existsSync(resolve(ROOT, '.claude/.workspace'))) {
36
+ safeRun('node contextkit/tools/scripts/workspace-sync.mjs');
37
+ safeRun('git add contextkit/memory/WORKSPACE.md');
38
+ }
39
+ if (existsSync(P.pipeline)) {
40
+ safeRun('node contextkit/tools/scripts/pipeline.mjs sync');
41
+ safeRun('git add contextkit/pipeline/devpipeline.md contextkit/pipeline/known-bugs.md');
42
+ }
43
+
44
+ console.log('✓ pre-commit done.');
45
+ }
46
+
47
+ main();
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pre-push hook (Level >= 3) — conflict pre-check against the latest remote.
4
+ *
5
+ * Before a push lands, this fetches the target branch (config l3.mainBranch,
6
+ * default "main"), compares what you changed against what changed upstream since
7
+ * your merge-base, and:
8
+ * - BLOCKS (exit 1) if a real textual conflict exists (git merge-tree) — so a
9
+ * parallel dev/agent who edited the same lines isn't silently overwritten.
10
+ * - WARNS (exit 0) if you both touched the same files but they auto-merge.
11
+ * - stays silent when there is no overlap.
12
+ *
13
+ * This is the cross-machine guarantee: the per-session ledger can't see other
14
+ * machines, but git can. Bypass (audited): CONTEXT_ALLOW_CONFLICT_PUSH=1 git push.
15
+ *
16
+ * Defensive: no remote / offline / old git → allow (never block on tooling).
17
+ */
18
+ import { execFileSync } from 'node:child_process';
19
+ import { loadConfigSync } from '../config/load.mjs';
20
+
21
+ const ROOT = process.cwd();
22
+ const MAIN = loadConfigSync(ROOT).l3?.mainBranch || 'main';
23
+
24
+ /**
25
+ * Cap every git subprocess (ms) so the network `fetch` below can't hang a push
26
+ * against an unreachable/slow remote. On timeout `execFileSync` throws → `git()`
27
+ * returns `{ ok:false }`, which the conflict check already treats as "couldn't
28
+ * refresh, allow the push" (defensive: never block on tooling). Env-overridable.
29
+ */
30
+ const GIT_TIMEOUT_MS = Number.parseInt(process.env.CONTEXT_GIT_TIMEOUT_MS || '', 10) || 15000;
31
+
32
+ if (process.env.CONTEXT_ALLOW_CONFLICT_PUSH === '1') {
33
+ console.error('⚠️ pre-push: conflict check bypassed (CONTEXT_ALLOW_CONFLICT_PUSH=1).');
34
+ process.exit(0);
35
+ }
36
+
37
+ function git(args, allowFail = true) {
38
+ try {
39
+ return { ok: true, out: execFileSync('git', args, { cwd: ROOT, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], timeout: GIT_TIMEOUT_MS }).trim() };
40
+ } catch (err) {
41
+ if (!allowFail) throw err;
42
+ return { ok: false, out: '', status: err?.status };
43
+ }
44
+ }
45
+
46
+ function fileList(range) {
47
+ const r = git(['diff', '--name-only', range]);
48
+ return r.ok ? r.out.split('\n').filter(Boolean) : [];
49
+ }
50
+
51
+ function main() {
52
+ // Refresh the remote ref (best effort, short timeout via git's own).
53
+ git(['fetch', 'origin', MAIN, '--quiet']);
54
+
55
+ const remote = `origin/${MAIN}`;
56
+ if (!git(['rev-parse', '--verify', '--quiet', remote]).ok) process.exit(0); // no upstream yet
57
+
58
+ const base = git(['merge-base', 'HEAD', remote]);
59
+ if (!base.ok || !base.out) process.exit(0);
60
+
61
+ const ours = new Set(fileList(`${base.out}..HEAD`));
62
+ const theirs = fileList(`${base.out}..${remote}`);
63
+ const overlap = theirs.filter((f) => ours.has(f));
64
+ if (overlap.length === 0) process.exit(0); // disjoint — safe
65
+
66
+ // Real textual conflict? `git merge-tree --write-tree` exits 1 on conflict.
67
+ const mt = git(['merge-tree', '--write-tree', 'HEAD', remote]);
68
+ const hasConflict = mt.status === 1; // status>1 / unsupported → unknown
69
+
70
+ if (hasConflict) {
71
+ console.error('');
72
+ console.error(`✗ pre-push BLOCKED — your branch conflicts with ${remote} (someone pushed there).`);
73
+ console.error('');
74
+ console.error(' Files changed on BOTH sides (likely conflicts):');
75
+ for (const f of overlap) console.error(` - ${f}`);
76
+ console.error('');
77
+ console.error(' Two sessions/devs changed the same file. Reconcile before pushing so');
78
+ console.error(' neither change is lost:');
79
+ console.error(` git pull --rebase origin ${MAIN} # replay your work on top, resolve conflicts`);
80
+ console.error(' # review each file: keep BOTH functions/changes, then continue the rebase');
81
+ console.error('');
82
+ console.error(' Emergency bypass (audited): CONTEXT_ALLOW_CONFLICT_PUSH=1 git push ...');
83
+ console.error('');
84
+ process.exit(1);
85
+ }
86
+
87
+ // Overlap but auto-mergeable — warn, don't block.
88
+ console.error('');
89
+ console.error(`⚠️ pre-push: you and ${remote} both changed these files (git can auto-merge,`);
90
+ console.error(' but double-check nothing is logically clobbered):');
91
+ for (const f of overlap) console.error(` - ${f}`);
92
+ console.error(` Tip: git pull --rebase origin ${MAIN} before pushing keeps history clean.`);
93
+ console.error('');
94
+ process.exit(0);
95
+ }
96
+
97
+ try {
98
+ main();
99
+ } catch (err) {
100
+ process.stderr.write(`[pre-push] ${err?.message ?? err}\n`);
101
+ process.exit(0);
102
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Boot-context content readers — pure I/O for `session-start.mjs`.
3
+ *
4
+ * Each function reads ONE source artifact and returns a Markdown snippet (or
5
+ * null when missing/empty). All helpers are defensive — they never throw.
6
+ * Zero third-party deps.
7
+ */
8
+ import { readdir, readFile, stat } from 'node:fs/promises';
9
+ import { resolve } from 'node:path';
10
+ import { CHANGELOG, SESSIONS_DIR, SESSIONS_INDEX, WORKSPACE_INDEX } from '../config/paths.mjs';
11
+ import { parseSessionLog, renderDigest } from './session-digest-core.mjs';
12
+
13
+ const SECTION_LIMIT = 60;
14
+
15
+ /** Matches `<YYYY-MM-DD>-<NN>-<slug>.md`. Slug allows `a-z0-9._-`. */
16
+ const ENTRY_PATTERN = /^(\d{4}-\d{2}-\d{2})-(\d{2,})-([a-z0-9._-]+)\.md$/;
17
+
18
+ async function readSafe(root, relPath) {
19
+ try {
20
+ return await readFile(resolve(root, relPath), 'utf-8');
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ export async function exists(root, relPath) {
27
+ try {
28
+ await stat(resolve(root, relPath));
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ /** Newest session file (canonical number; later DATE breaks a numbering tie) + its content. */
36
+ async function latestSessionEntry(root) {
37
+ let files = [];
38
+ try {
39
+ files = await readdir(resolve(root, SESSIONS_DIR));
40
+ } catch {
41
+ return null;
42
+ }
43
+ const entries = files
44
+ .map((f) => ENTRY_PATTERN.exec(f))
45
+ .filter(Boolean)
46
+ .map((m) => ({ filename: m[0], date: m[1], num: Number.parseInt(m[2], 10) }))
47
+ // Session number is canonical; on a number collision the later DATE wins so
48
+ // the boot banner never shows a stale entry just because of a numbering clash.
49
+ .sort((a, b) => b.num - a.num || b.date.localeCompare(a.date));
50
+ if (entries.length === 0) return null;
51
+ const content = await readSafe(root, `${SESSIONS_DIR}/${entries[0].filename}`);
52
+ if (!content) return null;
53
+ return { filename: entries[0].filename, content, path: resolve(root, SESSIONS_DIR, entries[0].filename) };
54
+ }
55
+
56
+ /** Most recent registered session as a raw-truncated Markdown snippet + its path. */
57
+ export async function extractLatestSession(root) {
58
+ const entry = await latestSessionEntry(root);
59
+ if (!entry) return null;
60
+ const lines = entry.content.split('\n').slice(0, SECTION_LIMIT);
61
+ return { path: entry.path, content: lines.join('\n').trim() };
62
+ }
63
+
64
+ /**
65
+ * Compact ~6-line digest of the latest session for the boot banner [ADR-0027].
66
+ * Falls back to the raw-truncated snippet when the log can't be parsed — a digest
67
+ * miss degrades visibly, it never empties the banner (rules 2 & 8).
68
+ * @returns {Promise<?{path:string, content:string, mode:'digest'|'raw'}>}
69
+ */
70
+ export async function digestLatestSession(root) {
71
+ const entry = await latestSessionEntry(root);
72
+ if (!entry) return null;
73
+ const digest = renderDigest(parseSessionLog(entry.content, entry.filename));
74
+ if (digest) return { path: entry.path, content: digest, mode: 'digest' };
75
+ const lines = entry.content.split('\n').slice(0, SECTION_LIMIT);
76
+ return { path: entry.path, content: lines.join('\n').trim(), mode: 'raw' };
77
+ }
78
+
79
+ /** Extracts the `[Unreleased]` block from a CHANGELOG-shaped string. */
80
+ export function extractUnreleased(text) {
81
+ if (!text) return null;
82
+ const lines = text.split('\n');
83
+ const startIdx = lines.findIndex((l) => /^##\s+\[Unreleased\]/i.test(l));
84
+ if (startIdx === -1) return null;
85
+ const slice = lines.slice(startIdx + 1);
86
+ const endRel = slice.findIndex((l) => l.trim() === '---' || /^##\s+\[/.test(l));
87
+ const end = endRel === -1 ? slice.length : endRel;
88
+ const truncated = end > SECTION_LIMIT;
89
+ const content = slice.slice(0, Math.min(end, SECTION_LIMIT)).join('\n').trim();
90
+ if (!content || /add your changes|empty|vazio/i.test(content)) return null;
91
+ // Tell the reader the boot banner is showing a clipped view, not the whole list.
92
+ return truncated ? `${content}\n… (truncated — full [Unreleased] in docs/CHANGELOG.md)` : content;
93
+ }
94
+
95
+ /** Active-session table from WORKSPACE.md (first ~12 lines after the header). */
96
+ export async function readWorkspaceSummary(root) {
97
+ const md = await readSafe(root, WORKSPACE_INDEX);
98
+ if (!md) return null;
99
+ const lines = md.split('\n');
100
+ const startIdx = lines.findIndex((l) => /^##\s+🟢\s+Active/.test(l));
101
+ if (startIdx === -1) return null;
102
+ return lines.slice(startIdx).slice(0, 12).join('\n').trim();
103
+ }
104
+
105
+ export async function readChangelog(root) {
106
+ return readSafe(root, CHANGELOG);
107
+ }
108
+
109
+ export async function readSessionsIndex(root) {
110
+ return readSafe(root, SESSIONS_INDEX);
111
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Boot signals — environment detectors for the SessionStart banner.
3
+ *
4
+ * Pure-ish read-only helpers split out of `session-start.mjs` to keep that hook
5
+ * under the line budget: git divergence/branch, other active branches, project
6
+ * name, greenfield detection, and the config-driven cadence triggers
7
+ * (security-mode, predictions-review). All best-effort and silent on error —
8
+ * a signal never blocks a session. Zero third-party deps.
9
+ */
10
+ import { execSync } from 'node:child_process';
11
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
12
+ import { readFile } from 'node:fs/promises';
13
+ import { basename, resolve } from 'node:path';
14
+ import { loadConfigSync } from '../config/load.mjs';
15
+ import { pathsFor } from '../config/paths.mjs';
16
+
17
+ export function checkGitDivergence(root) {
18
+ try {
19
+ execSync('git fetch origin --quiet', { cwd: root, stdio: 'ignore', timeout: 5000 });
20
+ } catch {
21
+ return null;
22
+ }
23
+ try {
24
+ const counts = execSync('git rev-list --left-right --count HEAD...@{u}', { cwd: root, encoding: 'utf-8', timeout: 3000 }).trim();
25
+ const [a, b] = counts.split(/\s+/);
26
+ return { ahead: Number.parseInt(a ?? '0', 10), behind: Number.parseInt(b ?? '0', 10) };
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ export function getBranch(root) {
33
+ try {
34
+ return execSync('git symbolic-ref --short HEAD', { cwd: root, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
35
+ } catch {
36
+ return 'detached';
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Cross-machine + same-machine awareness (L3): recent OTHER branches — local
42
+ * worktrees and remote feature branches — with author + age, so parallel work
43
+ * (other devs/agents) is visible at boot. Read-only, best effort.
44
+ */
45
+ export function activeBranches(root, currentBranch) {
46
+ const lines = [];
47
+ try {
48
+ const worktrees = execSync('git worktree list --porcelain', { cwd: root, encoding: 'utf-8', timeout: 3000 })
49
+ .split('\n')
50
+ .filter((l) => l.startsWith('branch '))
51
+ .map((l) => l.replace('branch refs/heads/', '').trim())
52
+ .filter((b) => b && b !== currentBranch);
53
+ for (const b of [...new Set(worktrees)].slice(0, 5)) lines.push(`- 🌳 local worktree on \`${b}\``);
54
+ } catch {
55
+ /* not a worktree setup */
56
+ }
57
+ try {
58
+ const remote = execSync(
59
+ `git for-each-ref --sort=-committerdate --count=20 --format="%(refname:short)|%(committerdate:relative)|%(authorname)" refs/remotes`,
60
+ { cwd: root, encoding: 'utf-8', timeout: 3000 },
61
+ )
62
+ .split('\n')
63
+ .map((l) => l.trim())
64
+ .filter(Boolean)
65
+ .map((l) => l.split('|'))
66
+ .filter(([ref]) => ref && !/\/(main|master|HEAD)$/.test(ref) && !ref.endsWith(`/${currentBranch}`))
67
+ .filter(([, rel]) => !/(month|year)/.test(rel || ''))
68
+ .slice(0, 5);
69
+ for (const [ref, rel, author] of remote) lines.push(`- ☁️ \`${ref}\` — ${rel} by ${author}`);
70
+ } catch {
71
+ /* no remote */
72
+ }
73
+ return lines.length ? lines.join('\n') : null;
74
+ }
75
+
76
+ /** True when the project has no source code yet (routes first-run to /aidevtool-from0). */
77
+ export function isGreenfield(root) {
78
+ return !['src', 'app', 'apps', 'packages', 'lib', 'components', 'pages', 'server', 'cmd', 'internal'].some((d) => existsSync(resolve(root, d)));
79
+ }
80
+
81
+ export async function projectName(root) {
82
+ try {
83
+ const pkg = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf-8'));
84
+ if (typeof pkg?.name === 'string' && pkg.name) return pkg.name;
85
+ } catch {
86
+ /* no package.json */
87
+ }
88
+ return basename(root);
89
+ }
90
+
91
+ /** Counts registered session files. Shared by the cadence triggers. */
92
+ function sessionCount(root) {
93
+ try {
94
+ return readdirSync(pathsFor(root).sessions).filter((f) => f.endsWith('.md')).length;
95
+ } catch {
96
+ return 0;
97
+ }
98
+ }
99
+
100
+ /** Security mode (config): returns the cadence N when a /deep-analysis is due, else 0. */
101
+ export function securityModeDue(root) {
102
+ const cfg = loadConfigSync(root)?.securityMode;
103
+ if (!cfg || cfg.active !== true) return 0;
104
+ const everyN = Number(cfg.everyNSessions) > 0 ? Number(cfg.everyNSessions) : 10;
105
+ const n = sessionCount(root);
106
+ return n > 0 && n % everyN === 0 ? everyN : 0;
107
+ }
108
+
109
+ /**
110
+ * Predictions-review cadence (config): returns N when a review is due — an
111
+ * every-N-sessions tick AND at least one `/simulate-impact` prediction is still
112
+ * unreviewed (`fill on review` stub). Zero noise when there's nothing to review.
113
+ */
114
+ export function predictionsReviewDue(root) {
115
+ const cfg = loadConfigSync(root)?.predictionsReview;
116
+ if (!cfg || cfg.active !== true) return 0;
117
+ const everyN = Number(cfg.everyNSessions) > 0 ? Number(cfg.everyNSessions) : 10;
118
+ const n = sessionCount(root);
119
+ if (n === 0 || n % everyN !== 0) return 0;
120
+ try {
121
+ const dir = pathsFor(root).predictions;
122
+ const unreviewed = readdirSync(dir)
123
+ .filter((f) => f.endsWith('.md'))
124
+ .some((f) => {
125
+ try {
126
+ return readFileSync(resolve(dir, f), 'utf-8').includes('fill on review');
127
+ } catch {
128
+ return false;
129
+ }
130
+ });
131
+ return unreviewed ? everyN : 0;
132
+ } catch {
133
+ return 0;
134
+ }
135
+ }
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Stop hook (Level >= 2) — nudges Claude to register the session on drift.
4
+ *
5
+ * Session-aware via the per-session ledger keyed by `session_id`. Anti-loop
6
+ * guard via the `stop_hook_active` flag plus a `stopWarnedAt` timestamp.
7
+ *
8
+ * Decision rules:
9
+ * - `stop_hook_active === true` → silent (anti-loop).
10
+ * - Important paths touched < 2 → silent.
11
+ * - Session already registered → silent.
12
+ * - Already nudged once → silent.
13
+ * - Otherwise: emit `decision: "block"` with the path list + instructions.
14
+ *
15
+ * Side jobs (informational, NEVER block): Level 5 archives old registered
16
+ * ledgers and proposes a distillation cycle when the session count crosses
17
+ * the configured threshold.
18
+ *
19
+ * Zero third-party deps.
20
+ */
21
+ import { mkdir, readdir, readFile, rename, stat, writeFile } from 'node:fs/promises';
22
+ import { resolve } from 'node:path';
23
+ import {
24
+ ledgerPathFor,
25
+ listAllLedgers,
26
+ pendingImportantPaths,
27
+ readLedger,
28
+ resolveSessionId,
29
+ SESSIONS_DIR,
30
+ wasRegisteredDuringSession,
31
+ writeLedger,
32
+ } from './ledger.mjs';
33
+ import { getLevel, loadConfig } from '../config/load.mjs';
34
+ import { SESSIONS_DIR as SESSIONS_MD_DIR, SESSIONS_INDEX } from '../config/paths.mjs';
35
+
36
+ const ROOT = process.cwd();
37
+ const ARCHIVE_DIR = resolve(SESSIONS_DIR, '.archive');
38
+ const DISTILL_NUDGE_PATH = resolve(SESSIONS_DIR, '.distill-nudge');
39
+ const DISTILL_NUDGE_DEBOUNCE_MS = 24 * 60 * 60 * 1000;
40
+ const ADVISOR_NUDGE_PATH = resolve(SESSIONS_DIR, '.advisor-nudge');
41
+ const ADVISOR_NUDGE_DEBOUNCE_MS = 24 * 60 * 60 * 1000;
42
+
43
+ async function readStdin() {
44
+ return new Promise((res) => {
45
+ let buf = '';
46
+ process.stdin.setEncoding('utf-8');
47
+ process.stdin.on('data', (c) => (buf += c));
48
+ process.stdin.on('end', () => res(buf));
49
+ setTimeout(() => res(buf), 500).unref?.();
50
+ });
51
+ }
52
+
53
+ function buildReason(paths, sessionId) {
54
+ const list = paths.slice(0, 12).map((p) => ` - ${p}`).join('\n');
55
+ const overflow = paths.length > 12 ? `\n (… and ${paths.length - 12} more)` : '';
56
+ return [
57
+ `⚠️ Session drift detected (session id: ${sessionId.slice(0, 8)}…).`,
58
+ `${paths.length} important file(s) were modified, but ${SESSIONS_INDEX} was not updated.`,
59
+ '',
60
+ 'Modified paths:',
61
+ list + overflow,
62
+ '',
63
+ 'Before finalizing, do ONE of the following and then stop again:',
64
+ ' 1. Run /log-session to register this session and update the CHANGELOG.',
65
+ ' 2. If this session is intentionally discardable (experiment that will be',
66
+ ' reverted), tell the user and confirm before stopping.',
67
+ '',
68
+ 'You will not be nudged again for this session — this notice fires once.',
69
+ ].join('\n');
70
+ }
71
+
72
+ /** L5 — archives ledgers that are registered AND older than the cutoff. */
73
+ async function archiveOldRegisteredLedgers() {
74
+ let config;
75
+ try {
76
+ config = await loadConfig(ROOT);
77
+ } catch {
78
+ return;
79
+ }
80
+ const ageDays = Number(config?.l5?.distill?.archiveLedgersOlderThanDays);
81
+ if (!Number.isFinite(ageDays) || ageDays <= 0) return;
82
+ const cutoff = Date.now() - ageDays * 24 * 60 * 60 * 1000;
83
+ let entries;
84
+ try {
85
+ entries = await listAllLedgers();
86
+ } catch {
87
+ return;
88
+ }
89
+ for (const { sessionId, ledger } of entries) {
90
+ if (!(ledger.registered || wasRegisteredDuringSession(ledger))) continue;
91
+ const path = ledgerPathFor(sessionId);
92
+ try {
93
+ const st = await stat(path);
94
+ if (st.mtimeMs > cutoff) continue;
95
+ await mkdir(ARCHIVE_DIR, { recursive: true });
96
+ await rename(path, resolve(ARCHIVE_DIR, `${sessionId}.json`));
97
+ } catch {
98
+ /* best effort */
99
+ }
100
+ }
101
+ }
102
+
103
+ /** L5 — proposes `/distill-sessions` once the session count crosses threshold. */
104
+ async function maybeProposeDistillation() {
105
+ let config;
106
+ try {
107
+ config = await loadConfig(ROOT);
108
+ } catch {
109
+ return null;
110
+ }
111
+ const threshold = Number(config?.l5?.distill?.proposeAfterSessions);
112
+ if (!Number.isFinite(threshold) || threshold <= 0) return null;
113
+ let count = 0;
114
+ try {
115
+ const entries = await readdir(resolve(ROOT, SESSIONS_MD_DIR));
116
+ count = entries.filter((f) => /^\d{4}-\d{2}-\d{2}-\d{2,}-.+\.md$/.test(f)).length;
117
+ } catch {
118
+ return null;
119
+ }
120
+ if (count < threshold) return null;
121
+ let lastNudgeAt = 0;
122
+ try {
123
+ lastNudgeAt = Number.parseInt((await readFile(DISTILL_NUDGE_PATH, 'utf-8')).trim(), 10) || 0;
124
+ } catch {
125
+ /* no prior nudge */
126
+ }
127
+ if (Date.now() - lastNudgeAt < DISTILL_NUDGE_DEBOUNCE_MS) return null;
128
+ try {
129
+ await mkdir(SESSIONS_DIR, { recursive: true });
130
+ await writeFile(DISTILL_NUDGE_PATH, String(Date.now()), 'utf-8');
131
+ } catch {
132
+ return null;
133
+ }
134
+ return [
135
+ `💡 Distillation cycle ready (L5): ${count} sessions registered (threshold ${threshold}).`,
136
+ ' Consider `/distill-sessions` to propose CLAUDE.md refinements, then `/distill-apply`.',
137
+ " Skipping is fine — you'll be reminded again in 24h.",
138
+ ].join('\n');
139
+ }
140
+
141
+ /**
142
+ * ADR-0028 — proactively suggests `/advise` after a PRODUCTIVE session.
143
+ * Mirrors `maybeProposeDistillation`: config-gated (`advisor.active` &&
144
+ * `advisor.nudgeOnStop`), fires only when ≥ 2 important paths were touched this
145
+ * session (real implementation), and debounced 24h. Nudge-only — never blocks,
146
+ * never runs the network or the AI (that lives in the `/advise` command).
147
+ *
148
+ * @param {object} ledger - this session's ledger (for the "real work" signal).
149
+ * @returns {Promise<string|null>} the nudge text, or null when it should stay silent.
150
+ */
151
+ async function maybeProposeAdvisor(ledger) {
152
+ let config;
153
+ try {
154
+ config = await loadConfig(ROOT);
155
+ } catch {
156
+ return null;
157
+ }
158
+ const advisor = config?.advisor;
159
+ if (advisor?.active !== true || advisor?.nudgeOnStop !== true) return null;
160
+ const touched = pendingImportantPaths(ledger).length;
161
+ if (touched < 2) return null;
162
+ let lastNudgeAt = 0;
163
+ try {
164
+ lastNudgeAt = Number.parseInt((await readFile(ADVISOR_NUDGE_PATH, 'utf-8')).trim(), 10) || 0;
165
+ } catch {
166
+ /* no prior nudge */
167
+ }
168
+ if (Date.now() - lastNudgeAt < ADVISOR_NUDGE_DEBOUNCE_MS) return null;
169
+ try {
170
+ await mkdir(SESSIONS_DIR, { recursive: true });
171
+ await writeFile(ADVISOR_NUDGE_PATH, String(Date.now()), 'utf-8');
172
+ } catch {
173
+ return null;
174
+ }
175
+ return [
176
+ `💡 Proactive Advisor (L6): ${touched} important file(s) touched this session.`,
177
+ ' Run `/advise` for a six-lane improvement scan (architecture · features · deepen ·',
178
+ ' security · UX · growth) before you wrap up. Skipping is fine — reminded again in 24h.',
179
+ ].join('\n');
180
+ }
181
+
182
+ async function main() {
183
+ const raw = await readStdin();
184
+ let payload = {};
185
+ try {
186
+ payload = raw ? JSON.parse(raw) : {};
187
+ } catch {
188
+ payload = {};
189
+ }
190
+ if (payload.stop_hook_active === true) return;
191
+
192
+ const sessionId = resolveSessionId(payload);
193
+ const ledger = await readLedger(sessionId);
194
+ const level = getLevel(ROOT);
195
+
196
+ const sideSuggestions = [];
197
+ if (level >= 5) {
198
+ await archiveOldRegisteredLedgers();
199
+ const distill = await maybeProposeDistillation();
200
+ if (distill) sideSuggestions.push(distill);
201
+ const advise = await maybeProposeAdvisor(ledger);
202
+ if (advise) sideSuggestions.push(advise);
203
+ }
204
+ const flushSide = () => {
205
+ if (sideSuggestions.length > 0) process.stdout.write(sideSuggestions.join('\n\n') + '\n');
206
+ };
207
+
208
+ if (ledger.registered || wasRegisteredDuringSession(ledger)) return flushSide();
209
+
210
+ const paths = pendingImportantPaths(ledger);
211
+ if (paths.length < 2) return flushSide();
212
+ if (typeof ledger.stopWarnedAt === 'number') return flushSide();
213
+
214
+ // Mark BEFORE emitting to avoid races.
215
+ ledger.stopWarnedAt = Date.now();
216
+ await writeLedger(sessionId, ledger);
217
+
218
+ const reason =
219
+ sideSuggestions.length > 0
220
+ ? `${buildReason(paths, sessionId)}\n\n${sideSuggestions.join('\n\n')}`
221
+ : buildReason(paths, sessionId);
222
+ process.stdout.write(JSON.stringify({ decision: 'block', reason }));
223
+ }
224
+
225
+ main().catch((err) => {
226
+ process.stderr.write(`[check-registration] ${err?.message ?? err}\n`);
227
+ process.exit(0);
228
+ });