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,248 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SessionStart hook (Level >= 1) — injects project context at session start.
4
+ *
5
+ * Behavior:
6
+ * 1. `git fetch origin` silently to surface ahead/behind divergence.
7
+ * 2. Detects drift from prior sessions (ledgers with unregistered important
8
+ * modifications) and emits a banner. Cleans up resolved ledgers.
9
+ * 3. Includes the latest registered session, the CHANGELOG `[Unreleased]`,
10
+ * and active workspace claims when present.
11
+ *
12
+ * Constraints: concise output, all errors silent (NEVER block a session),
13
+ * zero third-party deps.
14
+ */
15
+ import { rm } from 'node:fs/promises';
16
+ import {
17
+ digestLatestSession,
18
+ exists,
19
+ extractUnreleased,
20
+ readChangelog,
21
+ readSessionsIndex,
22
+ readWorkspaceSummary,
23
+ } from './boot-context-readers.mjs';
24
+ import {
25
+ activeBranches,
26
+ checkGitDivergence,
27
+ getBranch,
28
+ isGreenfield,
29
+ predictionsReviewDue,
30
+ projectName,
31
+ securityModeDue,
32
+ } from './boot-signals.mjs';
33
+ import {
34
+ freshLedger,
35
+ ledgerPathFor,
36
+ listAllLedgers,
37
+ pendingImportantPaths,
38
+ resolveSessionId,
39
+ wasRegisteredDuringSession,
40
+ writeLedger,
41
+ } from './ledger.mjs';
42
+ import { getLevel, loadConfigSync } from '../config/load.mjs';
43
+ import { CONTEXT_SNAPSHOT } from '../config/paths.mjs';
44
+
45
+ const ROOT = process.cwd();
46
+
47
+ async function readStdin() {
48
+ return new Promise((res) => {
49
+ let buf = '';
50
+ process.stdin.setEncoding('utf-8');
51
+ process.stdin.on('data', (c) => (buf += c));
52
+ process.stdin.on('end', () => res(buf));
53
+ setTimeout(() => res(buf), 500).unref?.();
54
+ });
55
+ }
56
+
57
+ /** A ledger touched within this window may belong to a LIVE concurrent session. */
58
+ const ACTIVE_GRACE_MS = 15 * 60 * 1000;
59
+
60
+ /** Most recent activity timestamp for a ledger (its start, or its last edit). */
61
+ function lastActivityAt(ledger) {
62
+ const mods = Array.isArray(ledger.modifications) ? ledger.modifications : [];
63
+ const lastMod = mods.reduce((mx, m) => (typeof m?.at === 'number' && m.at > mx ? m.at : mx), 0);
64
+ return Math.max(typeof ledger.startedAt === 'number' ? ledger.startedAt : 0, lastMod);
65
+ }
66
+
67
+ async function analyzePriorLedgers(currentSessionId) {
68
+ const all = await listAllLedgers();
69
+ const drift = [];
70
+ const now = Date.now();
71
+ for (const { sessionId, ledger } of all) {
72
+ if (sessionId === currentSessionId) continue;
73
+ const mods = Array.isArray(ledger.modifications) ? ledger.modifications : [];
74
+ const registered = ledger.registered || wasRegisteredDuringSession(ledger);
75
+ const pending = pendingImportantPaths(ledger);
76
+ if (registered || pending.length === 0) {
77
+ // Reap a resolved ledger — but NEVER an empty one, nor one touched within the
78
+ // grace window: that may be a LIVE concurrent session that just wrote its
79
+ // fresh (empty) ledger. Deleting it was the race (008): a booting session
80
+ // wiped a live peer. The owning session cleans up its own ledger.
81
+ const maybeLive = mods.length === 0 || now - lastActivityAt(ledger) < ACTIVE_GRACE_MS;
82
+ if (!maybeLive) await rm(ledgerPathFor(sessionId), { force: true }).catch(() => {});
83
+ continue;
84
+ }
85
+ drift.push({ sessionId, paths: pending });
86
+ }
87
+ return drift;
88
+ }
89
+
90
+ async function main() {
91
+ const raw = await readStdin();
92
+ let payload = {};
93
+ try {
94
+ payload = raw ? JSON.parse(raw) : {};
95
+ } catch {
96
+ /* keep empty */
97
+ }
98
+ const sessionId = resolveSessionId(payload);
99
+ const level = getLevel(ROOT);
100
+ const needsSetup = loadConfigSync(ROOT)?.setup?.completed !== true;
101
+
102
+ // Fresh ledger for this session (Level >= 2 uses it; harmless at L1).
103
+ await writeLedger(sessionId, freshLedger(sessionId));
104
+
105
+ const drift = level >= 2 ? await analyzePriorLedgers(sessionId) : [];
106
+ const sessions = await readSessionsIndex(ROOT);
107
+ const changelog = await readChangelog(ROOT);
108
+ const latest = await digestLatestSession(ROOT);
109
+ const workspace = level >= 3 ? await readWorkspaceSummary(ROOT) : null;
110
+ const branches = level >= 3 ? activeBranches(ROOT, getBranch(ROOT)) : null;
111
+ const hasSnapshot = await exists(ROOT, CONTEXT_SNAPSHOT);
112
+ const divergence = checkGitDivergence(ROOT);
113
+ const secDue = securityModeDue(ROOT);
114
+ const predDue = predictionsReviewDue(ROOT);
115
+
116
+ if (!needsSetup && !sessions && !changelog && !latest && drift.length === 0 && !secDue && !predDue) return;
117
+
118
+ const out = [];
119
+ out.push('<project-context-boot>');
120
+ out.push(`# 📚 Boot context — ${await projectName(ROOT)}`);
121
+ out.push('');
122
+ out.push(`Session id: \`${sessionId.slice(0, 16)}\` · Branch: \`${getBranch(ROOT)}\` · ContextDevKit level: \`L${level}\``);
123
+ out.push('');
124
+
125
+ if (needsSetup) {
126
+ const empty = isGreenfield(ROOT);
127
+ out.push('## 🚀 First run — ContextDevKit not configured yet');
128
+ out.push('');
129
+ if (empty) {
130
+ out.push('This folder looks **empty (no code yet)**. Run **`/aidevtool-from0`** — it interviews you');
131
+ out.push('about the product, suggests/refines the stack, drafts a roadmap, adopts the best-practices');
132
+ out.push('constitution, and seeds the DevPipeline. From zero, the kit stays ACTIVE: it keeps');
133
+ out.push('suggesting the next practice/level as the product takes shape.');
134
+ } else {
135
+ out.push('This project already has code. Run **`/setupcontextdevkit`** — it inspects the project, tunes');
136
+ out.push('the config to this stack, fills in `CLAUDE.md`, flags high-risk paths, installs what is');
137
+ out.push('needed, and records a baseline ADR. (Empty project instead? use `/aidevtool-from0`.)');
138
+ }
139
+ out.push('');
140
+ }
141
+
142
+ if (loadConfigSync(ROOT)?.practices?.active === true) {
143
+ out.push('## 🧠 Best-practices skill is ACTIVE');
144
+ out.push('');
145
+ out.push('Honor `contextkit/best-practices.md` (file-size budget, intelligent refactor by responsibility,');
146
+ out.push('SoC, naming, docs). Run `/analyze-code-ia-practices` to audit + get refactor proposals.');
147
+ out.push('');
148
+ }
149
+
150
+ if (loadConfigSync(ROOT)?.behaviors?.active === true) {
151
+ out.push('## 🧭 Behavioral discipline is ACTIVE');
152
+ out.push('');
153
+ out.push('Honor `contextkit/behaviors.md` while coding: **think before coding** (surface assumptions,');
154
+ out.push('ask when ambiguous), **simplicity first**, **surgical changes** (match the surrounding style,');
155
+ out.push('no drive-by refactor), **goal-driven** (reproduce-test first, loop to green).');
156
+ out.push('');
157
+ }
158
+
159
+ if (secDue) {
160
+ out.push('## 🛡️ Security mode — time for a deep sweep');
161
+ out.push('');
162
+ out.push(`**${secDue} sessions** in. Run **\`/deep-analysis\`** — full code + security + deps + bug`);
163
+ out.push('sweep → report → ADRs → backlog. (Active by default; disable via `securityMode.active`.)');
164
+ out.push('');
165
+ }
166
+
167
+ if (predDue) {
168
+ out.push('## 🔮 Predictions — close the loop');
169
+ out.push('');
170
+ out.push(`**${predDue} sessions** in with **unreviewed** \`/simulate-impact\` predictions. Run`);
171
+ out.push('**`/predictions-review`** to fill their *Actual* section (predicted vs actual). It also');
172
+ out.push('auto-runs at `/log-session`; disable the reminder via `predictionsReview.active`.');
173
+ out.push('');
174
+ }
175
+
176
+ if (divergence && (divergence.ahead > 0 || divergence.behind > 0)) {
177
+ out.push('## 🔄 Git status vs upstream');
178
+ out.push('');
179
+ if (divergence.behind > 0) out.push(`- ⚠️ Behind upstream by **${divergence.behind}** commit(s). Consider \`git pull\` before editing.`);
180
+ if (divergence.ahead > 0) out.push(`- ℹ️ Ahead of upstream by **${divergence.ahead}** commit(s) (unpushed).`);
181
+ out.push('');
182
+ }
183
+
184
+ if (drift.length > 0) {
185
+ out.push('## 🚨 Drift from previous session(s)');
186
+ out.push('');
187
+ for (const d of drift) {
188
+ out.push(`Session \`${d.sessionId.slice(0, 8)}\` ended without \`/log-session\` and left ${d.paths.length} important file(s) modified:`);
189
+ for (const p of d.paths.slice(0, 8)) out.push(` - ${p}`);
190
+ if (d.paths.length > 8) out.push(` (… and ${d.paths.length - 8} more)`);
191
+ out.push('');
192
+ }
193
+ out.push('If those changes still matter, **offer to retroactively register them** before new work.');
194
+ out.push('');
195
+ }
196
+
197
+ if (workspace) {
198
+ out.push('## 👥 Active workspace claims');
199
+ out.push('');
200
+ out.push(workspace);
201
+ out.push('');
202
+ }
203
+
204
+ if (branches) {
205
+ out.push('## 🌿 Other active branches (parallel work)');
206
+ out.push('');
207
+ out.push(branches);
208
+ out.push('');
209
+ out.push('If you will touch files another branch changed, coordinate (or `/claim`) — the pre-push');
210
+ out.push('hook will also block a conflicting push.');
211
+ out.push('');
212
+ }
213
+
214
+ if (latest) {
215
+ out.push('## 🗓️ Last registered session');
216
+ out.push('');
217
+ out.push(latest.content);
218
+ if (latest.mode === 'digest') out.push('\n_(digest — open the full log in `contextkit/memory/sessions/` if you need detail) [ADR-0027]_');
219
+ out.push('');
220
+ }
221
+
222
+ if (changelog) {
223
+ const unreleased = extractUnreleased(changelog);
224
+ if (unreleased) {
225
+ out.push('## 📝 Unreleased changes (CHANGELOG `[Unreleased]`)');
226
+ out.push('');
227
+ out.push(unreleased);
228
+ out.push('');
229
+ }
230
+ }
231
+
232
+ out.push('## ⚠️ Process rules');
233
+ out.push('');
234
+ out.push('1. Read SESSIONS index + relevant ADR before non-trivial changes.');
235
+ out.push('2. New architectural decision → `/new-adr <title>` BEFORE implementing.');
236
+ if (level >= 3) out.push('3. Reserve area before parallel work → `/claim <path>`. Free with `/release`.');
237
+ out.push('4. End of productive session → `/log-session`.');
238
+ out.push('5. `/state` for a quick state summary at any time.');
239
+ if (hasSnapshot) out.push('6. `.context-snapshot.md` available for a full-project view.');
240
+ out.push('</project-context-boot>');
241
+
242
+ process.stdout.write(out.join('\n') + '\n');
243
+ }
244
+
245
+ main().catch((err) => {
246
+ process.stderr.write(`[session-start] ${err?.message ?? err}\n`);
247
+ process.exit(0);
248
+ });
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PreToolUse hook (Level 5) — high-risk path gate.
4
+ *
5
+ * Wired with matcher `Edit|Write|MultiEdit`. When the file being edited
6
+ * matches the `l5.highRiskPaths` allowlist from `contextkit/config.json` AND the
7
+ * current session's ledger has no covering `/simulate-impact` record, it emits
8
+ * a `decision: "block"` with instructions.
9
+ *
10
+ * Defensive: any error path exits 0 silently. A broken hook MUST NEVER block
11
+ * real work.
12
+ *
13
+ * Bypass: record a deliberate skip by calling `markSimulation` with
14
+ * `objective: 'BYPASS: <reason>'` and `coveredPaths: [highRiskPath]` — an
15
+ * auditable escape hatch for trivial edits.
16
+ */
17
+ import { getLevel, loadConfig } from '../config/load.mjs';
18
+ import { hasSimulationFor, readLedger, resolveSessionId, toRepoRelative } from './ledger.mjs';
19
+
20
+ const ROOT = process.cwd();
21
+
22
+ async function readStdin() {
23
+ return new Promise((res) => {
24
+ let buf = '';
25
+ process.stdin.setEncoding('utf-8');
26
+ process.stdin.on('data', (c) => (buf += c));
27
+ process.stdin.on('end', () => res(buf));
28
+ setTimeout(() => res(buf), 500).unref?.();
29
+ });
30
+ }
31
+
32
+ function extractFilePath(payload) {
33
+ const tool = payload?.tool_name;
34
+ const input = payload?.tool_input ?? {};
35
+ if ((tool === 'Edit' || tool === 'Write' || tool === 'MultiEdit') && typeof input.file_path === 'string') {
36
+ return input.file_path;
37
+ }
38
+ return null;
39
+ }
40
+
41
+ /** Returns the matching high-risk entry (or null). Dir entries match by prefix. */
42
+ function matchHighRisk(targetPath, highRiskPaths) {
43
+ if (!Array.isArray(highRiskPaths)) return null;
44
+ for (const entry of highRiskPaths) {
45
+ if (typeof entry !== 'string' || entry.length === 0) continue;
46
+ if (entry.endsWith('/')) {
47
+ if (targetPath.startsWith(entry)) return entry;
48
+ } else if (targetPath === entry) {
49
+ return entry;
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+
55
+ function buildBlockReason(targetPath, matchedEntry) {
56
+ return [
57
+ '🛑 L5 gate — high-risk path detected.',
58
+ '',
59
+ 'You are about to modify:',
60
+ ` • ${targetPath}`,
61
+ '',
62
+ `That path matches the configured high-risk entry \`${matchedEntry}\` and the`,
63
+ 'current session has no `/simulate-impact` record covering it.',
64
+ '',
65
+ 'Required next step — pick ONE:',
66
+ ' 1. Run `/simulate-impact "<one-sentence objective>"` first. It produces a',
67
+ ' Blast Radius Report and marks the ledger, unblocking edits inside the',
68
+ ' covered paths.',
69
+ ' 2. If this edit is genuinely trivial (typo / comment), record an explicit',
70
+ ' bypass via markSimulation (objective "BYPASS: <reason>") then retry.',
71
+ '',
72
+ 'Why the gate exists: high-risk paths (schema, shared contracts, auth surface,',
73
+ 'core services) have outsized blast radius. The gate converts "architecture',
74
+ 'before syntax" from a suggestion into executable governance.',
75
+ ].join('\n');
76
+ }
77
+
78
+ async function main() {
79
+ if (getLevel(ROOT) < 5) return; // Inert below Level 5.
80
+
81
+ const raw = await readStdin();
82
+ if (!raw) return;
83
+ let payload;
84
+ try {
85
+ payload = JSON.parse(raw);
86
+ } catch {
87
+ return;
88
+ }
89
+
90
+ const filePath = extractFilePath(payload);
91
+ if (!filePath) return;
92
+ const targetPath = toRepoRelative(filePath);
93
+ if (!targetPath) return;
94
+
95
+ const config = await loadConfig(ROOT);
96
+ const matched = matchHighRisk(targetPath, config?.l5?.highRiskPaths ?? []);
97
+ if (!matched) return;
98
+
99
+ const ledger = await readLedger(resolveSessionId(payload));
100
+ if (hasSimulationFor(ledger, targetPath)) return;
101
+
102
+ process.stdout.write(JSON.stringify({ decision: 'block', reason: buildBlockReason(targetPath, matched) }));
103
+ }
104
+
105
+ main().catch((err) => {
106
+ process.stderr.write(`[simulate-gate] ${err?.message ?? err}\n`);
107
+ process.exit(0);
108
+ });
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PostToolUse hook (Level >= 2) — appends file modifications to the
4
+ * per-session ledger and warns about cross-session claim collisions (L3).
5
+ *
6
+ * Wired with matcher "Edit|Write|MultiEdit". Receives the tool payload via
7
+ * stdin (JSON). Silent on stdout UNLESS a cross-claim is detected.
8
+ *
9
+ * Side effects:
10
+ * 1. Updates `.claude/.sessions/<sid>.json`.
11
+ * 2. Renews the heartbeat in `.claude/.workspace/<sid>.json` (if claimed).
12
+ * 3. Surfaces a cross-claim warning when editing another session's claim.
13
+ *
14
+ * Defensive: any failure exits 0 with a stderr note. Zero third-party deps.
15
+ */
16
+ import { mkdir, readdir, readFile, stat } from 'node:fs/promises';
17
+ import { resolve } from 'node:path';
18
+ import { isTrackable, readLedger, resolveSessionId, sanitizeSid, toRepoRelative, writeLedger } from './ledger.mjs';
19
+ import { writeFileAtomic } from './safe-io.mjs';
20
+ import { WORKSPACE_STATE_DIR } from '../config/paths.mjs';
21
+
22
+ const ROOT = process.cwd();
23
+ const WORKSPACE_DIR = resolve(ROOT, WORKSPACE_STATE_DIR);
24
+
25
+ async function readStdin() {
26
+ return new Promise((res) => {
27
+ let buf = '';
28
+ process.stdin.setEncoding('utf-8');
29
+ process.stdin.on('data', (c) => (buf += c));
30
+ process.stdin.on('end', () => res(buf));
31
+ setTimeout(() => res(buf), 500).unref?.();
32
+ });
33
+ }
34
+
35
+ /** Extracts file paths from an Edit/Write/MultiEdit payload. */
36
+ function extractPaths(payload) {
37
+ const tool = payload?.tool_name;
38
+ const input = payload?.tool_input ?? {};
39
+ if (tool === 'Edit' || tool === 'Write') return input.file_path ? [input.file_path] : [];
40
+ if (tool === 'MultiEdit') {
41
+ if (input.file_path) return [input.file_path];
42
+ if (Array.isArray(input.edits)) return input.edits.map((e) => e?.file_path).filter(Boolean);
43
+ }
44
+ return [];
45
+ }
46
+
47
+ async function renewHeartbeat(sessionId) {
48
+ const path = resolve(WORKSPACE_DIR, `${sanitizeSid(sessionId)}.json`);
49
+ try {
50
+ const claimRecord = JSON.parse(await readFile(path, 'utf-8'));
51
+ claimRecord.lastHeartbeat = Date.now();
52
+ await writeFileAtomic(path, JSON.stringify(claimRecord, null, 2));
53
+ } catch {
54
+ /* no claim file — nothing to renew */
55
+ }
56
+ }
57
+
58
+ async function loadOtherClaims(mySessionId) {
59
+ const claims = new Map();
60
+ let files = [];
61
+ try {
62
+ files = await readdir(WORKSPACE_DIR);
63
+ } catch {
64
+ return claims;
65
+ }
66
+ for (const f of files) {
67
+ if (!f.endsWith('.json')) continue;
68
+ const otherSid = f.slice(0, -5);
69
+ if (otherSid === mySessionId) continue;
70
+ try {
71
+ const claimRecord = JSON.parse(await readFile(resolve(WORKSPACE_DIR, f), 'utf-8'));
72
+ if (Array.isArray(claimRecord?.claims)) {
73
+ for (const cl of claimRecord.claims) if (typeof cl?.path === 'string') claims.set(cl.path, otherSid);
74
+ }
75
+ } catch {
76
+ /* skip malformed */
77
+ }
78
+ }
79
+ return claims;
80
+ }
81
+
82
+ function pathCollides(editedPath, claimedPath) {
83
+ if (editedPath === claimedPath) return true;
84
+ const claimAsDir = claimedPath.endsWith('/') ? claimedPath : `${claimedPath}/`;
85
+ return editedPath.startsWith(claimAsDir);
86
+ }
87
+
88
+ function buildWarning(collisions) {
89
+ const list = collisions.map((c) => ` - \`${c.path}\` (claimed by session \`${c.owner.slice(0, 8)}\`)`).join('\n');
90
+ return [
91
+ '<cross-claim-warning>',
92
+ '⚠️ You just edited a path claimed by ANOTHER active session:',
93
+ list,
94
+ '',
95
+ 'Coordinate before continuing. If the claim is stale, ask the user to /release it.',
96
+ '</cross-claim-warning>',
97
+ ].join('\n');
98
+ }
99
+
100
+ async function main() {
101
+ const raw = await readStdin();
102
+ if (!raw) return;
103
+ let payload;
104
+ try {
105
+ payload = JSON.parse(raw);
106
+ } catch {
107
+ return;
108
+ }
109
+
110
+ // Sanitize once up front: session id is external input and is used to build
111
+ // ledger + workspace paths (defense-in-depth against `../` traversal).
112
+ const sessionId = sanitizeSid(resolveSessionId(payload));
113
+ const paths = extractPaths(payload).map(toRepoRelative).filter(isTrackable);
114
+ if (paths.length === 0) return;
115
+
116
+ // 1. Append to ledger (recording each file's post-edit mtime so the L3
117
+ // concurrency guard can later detect an EXTERNAL change to the same file).
118
+ const ledger = await readLedger(sessionId);
119
+ const tool = payload.tool_name ?? 'unknown';
120
+ const now = Date.now();
121
+ for (const p of paths) {
122
+ let mtime = null;
123
+ try {
124
+ mtime = (await stat(resolve(ROOT, p))).mtimeMs;
125
+ } catch {
126
+ /* file may not exist yet */
127
+ }
128
+ ledger.modifications.push({ path: p, tool, at: now, mtime });
129
+ }
130
+ await writeLedger(sessionId, ledger);
131
+
132
+ // 2. Renew heartbeat (if this session has a claim file).
133
+ await mkdir(WORKSPACE_DIR, { recursive: true }).catch(() => {});
134
+ await renewHeartbeat(sessionId);
135
+
136
+ // 3. Cross-claim detection (L3).
137
+ const otherClaims = await loadOtherClaims(sessionId);
138
+ if (otherClaims.size === 0) return;
139
+ const collisions = [];
140
+ for (const editedPath of paths) {
141
+ for (const [claimedPath, owner] of otherClaims) {
142
+ if (pathCollides(editedPath, claimedPath)) {
143
+ collisions.push({ path: editedPath, owner });
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ if (collisions.length > 0) process.stdout.write(buildWarning(collisions));
149
+ }
150
+
151
+ main().catch((err) => {
152
+ process.stderr.write(`[track-edits] ${err?.message ?? err}\n`);
153
+ process.exit(0);
154
+ });
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Media-provider adapter contract — ADR-0024.
3
+ *
4
+ * Each `*.mjs` file in this directory (except this one) is a concrete
5
+ * adapter for image or video generation. The kit ships two seeds:
6
+ * - nano-banana.mjs (image — Google AI Studio / Imagen)
7
+ * - veo.mjs (video — Google AI Studio / Veo)
8
+ *
9
+ * Adapter shape (all required):
10
+ *
11
+ * export const id = 'nano-banana' | 'veo';
12
+ * export const kind = 'image' | 'video';
13
+ * export const envVar = 'GOOGLE_AI_API_KEY';
14
+ * export const requiredEnv = ['GOOGLE_AI_API_KEY'];
15
+ * export async function generate({ prompt, options, outPath }) { ... }
16
+ *
17
+ * Five contract points (ADR-0024):
18
+ * 1. No SDK dependency. Adapters use `node:fetch` against the REST
19
+ * endpoint.
20
+ * 2. Refuse-on-missing-creds — `generate()` checks `requiredEnv` first
21
+ * and throws MediaProviderError('NO_CREDENTIALS', ...) before any
22
+ * network call.
23
+ * 3. `generate()` returns
24
+ * `{ outPath, durationMs, costEstimateUsd, providerRequestId }`.
25
+ * 4. Refuse-on-content-policy. Google's API rejects some prompts;
26
+ * the adapter throws MediaProviderError('CONTENT_POLICY', ...).
27
+ * 5. Cost-cap guard — process-level tally via CONTEXTDEVKIT_MEDIA_MAX_USD.
28
+ */
29
+
30
+ /** Stable error codes thrown by adapters. Kept narrow on purpose. */
31
+ export const MEDIA_ERROR_CODES = Object.freeze({
32
+ NO_CREDENTIALS: 'NO_CREDENTIALS',
33
+ CONTENT_POLICY: 'CONTENT_POLICY',
34
+ COST_CAP_REACHED: 'COST_CAP_REACHED',
35
+ RATE_LIMIT: 'RATE_LIMIT',
36
+ PROVIDER_ERROR: 'PROVIDER_ERROR',
37
+ BAD_INPUT: 'BAD_INPUT',
38
+ IO: 'IO',
39
+ });
40
+
41
+ export class MediaProviderError extends Error {
42
+ constructor(code, message, { providerRequestId } = {}) {
43
+ super(message);
44
+ this.name = 'MediaProviderError';
45
+ this.code = code;
46
+ if (providerRequestId) this.providerRequestId = providerRequestId;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Validate the shape of a media adapter module.
52
+ *
53
+ * @param {object} mod
54
+ * @returns {{ ok: true } | { ok: false, reasons: string[] }}
55
+ */
56
+ export function validateAdapter(mod) {
57
+ const reasons = [];
58
+ if (typeof mod.id !== 'string' || mod.id.length === 0) reasons.push('missing `id` export');
59
+ if (mod.kind !== 'image' && mod.kind !== 'video') reasons.push('`kind` must be "image" or "video"');
60
+ if (typeof mod.envVar !== 'string' || mod.envVar.length === 0) reasons.push('missing `envVar` export');
61
+ if (!Array.isArray(mod.requiredEnv) || mod.requiredEnv.length === 0) reasons.push('`requiredEnv` must be a non-empty array');
62
+ if (typeof mod.generate !== 'function') reasons.push('missing `generate(input)` export');
63
+ return reasons.length ? { ok: false, reasons } : { ok: true };
64
+ }
65
+
66
+ /* ──────────────────────────────────────────────────────────────────────── */
67
+ /* Cost-cap guard — single shared tally for the lifetime of the process. */
68
+ /* Adapters call `noteCostOrThrow(estimate)` BEFORE making the network call.*/
69
+ /* ──────────────────────────────────────────────────────────────────────── */
70
+
71
+ let _runningCostUsd = 0;
72
+
73
+ /** Reads the per-process USD cap from CONTEXTDEVKIT_MEDIA_MAX_USD; null = no cap. */
74
+ export function readCostCapUsd() {
75
+ const raw = process.env.CONTEXTDEVKIT_MEDIA_MAX_USD;
76
+ if (!raw) return null;
77
+ const n = Number(raw);
78
+ return Number.isFinite(n) && n > 0 ? n : null;
79
+ }
80
+
81
+ /**
82
+ * Throws MediaProviderError('COST_CAP_REACHED') if adding `estimateUsd` to
83
+ * the running tally would exceed the cap. Otherwise adds and returns the
84
+ * new running total.
85
+ *
86
+ * @param {number} estimateUsd
87
+ * @returns {number} new running total
88
+ */
89
+ export function noteCostOrThrow(estimateUsd) {
90
+ const cap = readCostCapUsd();
91
+ const next = _runningCostUsd + Math.max(0, estimateUsd);
92
+ if (cap !== null && next > cap) {
93
+ throw new MediaProviderError(
94
+ MEDIA_ERROR_CODES.COST_CAP_REACHED,
95
+ `cost cap reached: this call (~$${estimateUsd.toFixed(2)}) + running ($${_runningCostUsd.toFixed(2)}) would exceed CONTEXTDEVKIT_MEDIA_MAX_USD ($${cap.toFixed(2)})`,
96
+ );
97
+ }
98
+ _runningCostUsd = next;
99
+ return _runningCostUsd;
100
+ }
101
+
102
+ /** Test hook — resets the cost tally. NOT exposed to adapters. */
103
+ export function _resetCostTallyForTests() { _runningCostUsd = 0; }
104
+
105
+ /**
106
+ * Asserts every required env var is present; throws NO_CREDENTIALS otherwise.
107
+ * Adapters call this first thing in `generate()` (rule 8 — default-refuse).
108
+ *
109
+ * @param {string[]} requiredEnv
110
+ * @param {string} templatePath hint for the error message
111
+ */
112
+ export function assertCredentials(requiredEnv, templatePath = 'contextkit/.env.example') {
113
+ const missing = requiredEnv.filter((name) => !process.env[name]);
114
+ if (missing.length) {
115
+ throw new MediaProviderError(
116
+ MEDIA_ERROR_CODES.NO_CREDENTIALS,
117
+ `missing env var(s): ${missing.join(', ')}. See ${templatePath} for the template and set them (e.g. run with \`node --env-file=contextkit/.env ...\` on Node 20.6+).`,
118
+ );
119
+ }
120
+ }