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,164 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Deterministic tech-debt scanner — the engine behind `/tech-debt-sweep`.
4
+ *
5
+ * Walks the project's source files (skipping config.ledger.irrelevant paths),
6
+ * runs the regex detectors, and reports findings. Fast, zero-dependency.
7
+ *
8
+ * Usage:
9
+ * node contextkit/tools/scripts/tech-debt-scan.mjs # console summary
10
+ * node contextkit/tools/scripts/tech-debt-scan.mjs --json # machine-readable
11
+ * node contextkit/tools/scripts/tech-debt-scan.mjs --write # → contextkit/memory/tech-debt-board.md
12
+ * node contextkit/tools/scripts/tech-debt-scan.mjs --quick # red zone only (severity >= 4)
13
+ */
14
+ import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
15
+ import { join, relative, resolve } from 'node:path';
16
+ import { pathToFileURL } from 'node:url';
17
+ import { loadConfigSync } from '../../runtime/config/load.mjs';
18
+ import { PLATFORM_DIR, pathsFor } from '../../runtime/config/paths.mjs';
19
+ import { ALL_DETECTORS, CODE_RE } from './tech-debt-detectors.mjs';
20
+
21
+ const ROOT = process.cwd();
22
+ const P = pathsFor(ROOT);
23
+ const cfg = loadConfigSync(ROOT);
24
+ const IRRELEVANT = (cfg.ledger?.irrelevant || []).concat(['.git/', `${PLATFORM_DIR}/tools/`, `${PLATFORM_DIR}/runtime/`, `${PLATFORM_DIR}/detectors/`]);
25
+ const LINE_BUDGET = cfg.l5?.lineBudget || { yellow: 240, red: 308 };
26
+
27
+ function isIgnored(rel) {
28
+ const norm = rel.replaceAll('\\', '/');
29
+ return IRRELEVANT.some((p) => norm === p || norm.startsWith(p));
30
+ }
31
+
32
+ function walk(dir, acc) {
33
+ let entries;
34
+ try {
35
+ entries = readdirSync(dir, { withFileTypes: true });
36
+ } catch {
37
+ return acc;
38
+ }
39
+ for (const e of entries) {
40
+ const abs = join(dir, e.name);
41
+ const rel = relative(ROOT, abs).replaceAll('\\', '/');
42
+ if (isIgnored(rel) || e.name.startsWith('.tmp')) continue;
43
+ if (e.isDirectory()) walk(abs, acc);
44
+ else if (CODE_RE.test(e.name)) acc.push(rel);
45
+ }
46
+ return acc;
47
+ }
48
+
49
+ /** Functions exported by a custom detector module: default (fn or array) + `detectors`. */
50
+ function collectFns(mod) {
51
+ const out = [];
52
+ if (typeof mod.default === 'function') out.push(mod.default);
53
+ else if (Array.isArray(mod.default)) out.push(...mod.default.filter((x) => typeof x === 'function'));
54
+ if (Array.isArray(mod.detectors)) out.push(...mod.detectors.filter((x) => typeof x === 'function'));
55
+ return out;
56
+ }
57
+
58
+ /** Drop-in detectors from `contextkit/detectors/*.mjs`. Defensive — a broken one is skipped. */
59
+ async function loadCustomDetectors() {
60
+ let files;
61
+ try {
62
+ files = readdirSync(P.detectors).filter((f) => f.endsWith('.mjs'));
63
+ } catch {
64
+ return [];
65
+ }
66
+ const fns = [];
67
+ for (const f of files) {
68
+ try {
69
+ fns.push(...collectFns(await import(pathToFileURL(resolve(P.detectors, f)).href)));
70
+ } catch {
71
+ /* a broken custom detector must never block the scan */
72
+ }
73
+ }
74
+ return fns;
75
+ }
76
+
77
+ function scan(quick, detectors) {
78
+ const files = walk(ROOT, []);
79
+ const findings = [];
80
+ for (const rel of files) {
81
+ let content;
82
+ try {
83
+ content = readFileSync(resolve(ROOT, rel), 'utf-8');
84
+ } catch {
85
+ continue;
86
+ }
87
+ for (const detector of detectors) {
88
+ try {
89
+ const opts = detector.name === 'detectLineBudget' ? LINE_BUDGET : undefined;
90
+ for (const f of detector(rel, content, opts) || []) findings.push(f);
91
+ } catch {
92
+ /* a custom detector must not break the scan */
93
+ }
94
+ }
95
+ }
96
+ const filtered = quick ? findings.filter((f) => f.severity >= 4) : findings;
97
+ filtered.sort((a, b) => b.severity - a.severity || a.path.localeCompare(b.path));
98
+ return { fileCount: files.length, findings: filtered };
99
+ }
100
+
101
+ function renderBoard({ fileCount, findings }) {
102
+ const out = [];
103
+ out.push('# Tech Debt Board');
104
+ out.push('');
105
+ out.push(`> Auto-generated by \`tech-debt-scan\` on ${new Date().toISOString().slice(0, 10)} — ${fileCount} files scanned, ${findings.length} finding(s).`);
106
+ out.push('> Human-reviewed, advisory. Regenerate any time.');
107
+ out.push('');
108
+ const byKind = {};
109
+ for (const f of findings) (byKind[f.kind] = byKind[f.kind] || []).push(f);
110
+ if (findings.length === 0) {
111
+ out.push('✅ No findings. Clean.');
112
+ return out.join('\n') + '\n';
113
+ }
114
+ for (const [kind, items] of Object.entries(byKind)) {
115
+ out.push(`## ${kind} (${items.length})`);
116
+ out.push('');
117
+ for (const f of items) {
118
+ const sev = '●'.repeat(f.severity);
119
+ out.push(`- ${sev} \`${f.path}${f.line ? `:${f.line}` : ''}\` — ${f.message}`);
120
+ }
121
+ out.push('');
122
+ }
123
+ return out.join('\n') + '\n';
124
+ }
125
+
126
+ async function main() {
127
+ const args = process.argv.slice(2);
128
+ const detectors = [...ALL_DETECTORS, ...(await loadCustomDetectors())];
129
+ const report = scan(args.includes('--quick'), detectors);
130
+ if (args.includes('--ci')) {
131
+ // CI gate: fail the build on any RED-zone (severity 5) finding so the kit —
132
+ // or any project — can't regress past its own hard line-budget limit.
133
+ const red = report.findings.filter((f) => f.severity >= 5);
134
+ if (red.length > 0) {
135
+ console.error(`✗ tech-debt CI gate: ${red.length} RED-zone finding(s) (must be 0):`);
136
+ for (const f of red) console.error(` ${'●'.repeat(f.severity)} ${f.path}${f.line ? ':' + f.line : ''} — ${f.message}`);
137
+ process.exit(1);
138
+ }
139
+ console.log(`✓ tech-debt CI gate: no RED-zone findings across ${report.fileCount} files.`);
140
+ return;
141
+ }
142
+ if (args.includes('--json')) {
143
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
144
+ return;
145
+ }
146
+ if (args.includes('--write')) {
147
+ writeFileSync(resolve(P.memory, 'tech-debt-board.md'), renderBoard(report), 'utf-8');
148
+ // Also dump the raw findings so the DevPipeline can ingest them deterministically.
149
+ writeFileSync(resolve(P.memory, 'tech-debt-findings.json'), JSON.stringify(report, null, 2), 'utf-8');
150
+ console.log(`✅ tech-debt-board.md + tech-debt-findings.json written — ${report.findings.length} finding(s) across ${report.fileCount} files.`);
151
+ console.log(' → feed the backlog: node contextkit/tools/scripts/pipeline.mjs ingest contextkit/memory/tech-debt-findings.json --type chore');
152
+ return;
153
+ }
154
+ const counts = report.findings.reduce((m, f) => ((m[f.kind] = (m[f.kind] || 0) + 1), m), {});
155
+ console.log(`🧹 Tech debt: ${report.findings.length} finding(s) across ${report.fileCount} files.`);
156
+ for (const [k, n] of Object.entries(counts)) console.log(` ${k}: ${n}`);
157
+ for (const f of report.findings.slice(0, 15)) console.log(` ${'●'.repeat(f.severity)} ${f.path}${f.line ? ':' + f.line : ''} — ${f.message}`);
158
+ if (report.findings.length > 15) console.log(` … and ${report.findings.length - 15} more (use --write for the full board).`);
159
+ }
160
+
161
+ main().catch((err) => {
162
+ console.error('tech-debt-scan failed:', err?.message ?? err);
163
+ process.exit(1);
164
+ });
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Token economy & usage insight (roadmap #7 / L6).
4
+ *
5
+ * Reports Claude Code token usage for THIS project by reading the local session
6
+ * transcripts (`~/.claude/projects/<project>/<sessionId>.jsonl`). Aggregates
7
+ * input / output / cache tokens per session and per ISO week, and warns when a
8
+ * session crosses the configured budget (`tokens.budgetPerSession`). Read-only,
9
+ * local, zero third-party deps; reports **aggregated counts only**, never content.
10
+ * Degrades to a clear "no data" message when transcripts aren't found.
11
+ *
12
+ * Usage:
13
+ * node contextkit/tools/scripts/token-report.mjs # this project (table)
14
+ * node contextkit/tools/scripts/token-report.mjs --json
15
+ * node contextkit/tools/scripts/token-report.mjs --all # every project, not just cwd
16
+ * node contextkit/tools/scripts/token-report.mjs --from <dir> # read transcripts from <dir>
17
+ */
18
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
19
+ import { homedir } from 'node:os';
20
+ import { join } from 'node:path';
21
+ import { loadConfigSync } from '../../runtime/config/load.mjs';
22
+
23
+ const ROOT = process.cwd();
24
+ const norm = (p) => String(p || '').replace(/\\/g, '/').toLowerCase().replace(/\/+$/, '');
25
+ const ROOT_N = norm(ROOT);
26
+ const args = process.argv.slice(2);
27
+ const flag = (name) => args.includes(name);
28
+ const opt = (name) => {
29
+ const i = args.indexOf(name);
30
+ return i >= 0 ? args[i + 1] : undefined;
31
+ };
32
+
33
+ /** All `.jsonl` transcript files under the source dir (recursive, shallow). */
34
+ function findTranscripts(fromDir) {
35
+ const base = fromDir || join(homedir(), '.claude', 'projects');
36
+ const out = [];
37
+ const walk = (dir, depth) => {
38
+ let entries;
39
+ try {
40
+ entries = readdirSync(dir, { withFileTypes: true });
41
+ } catch {
42
+ return;
43
+ }
44
+ for (const e of entries) {
45
+ const p = join(dir, e.name);
46
+ if (e.isDirectory() && depth < 3) walk(p, depth + 1);
47
+ else if (e.isFile() && e.name.endsWith('.jsonl')) out.push(p);
48
+ }
49
+ };
50
+ if (existsSync(base)) walk(base, 0);
51
+ return out;
52
+ }
53
+
54
+ function isoWeek(ts) {
55
+ const d = new Date(ts);
56
+ if (Number.isNaN(d.getTime())) return 'unknown';
57
+ const t = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
58
+ t.setUTCDate(t.getUTCDate() + 4 - (t.getUTCDay() || 7));
59
+ const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
60
+ const week = Math.ceil(((t - yearStart) / 86400000 + 1) / 7);
61
+ return `${t.getUTCFullYear()}-W${String(week).padStart(2, '0')}`;
62
+ }
63
+
64
+ /** sessionId → token totals, built from `message.usage` entries (filtered by cwd unless --all). */
65
+ function aggregate(files, all) {
66
+ const sessions = new Map();
67
+ for (const file of files) {
68
+ let text;
69
+ try {
70
+ text = readFileSync(file, 'utf-8');
71
+ } catch {
72
+ continue;
73
+ }
74
+ for (const line of text.split('\n')) {
75
+ if (!line.trim()) continue;
76
+ let entry;
77
+ try {
78
+ entry = JSON.parse(line);
79
+ } catch {
80
+ continue;
81
+ }
82
+ const usage = entry?.message?.usage;
83
+ if (!usage) continue;
84
+ if (!all && norm(entry.cwd) !== ROOT_N) continue;
85
+ const sid = entry.sessionId || file;
86
+ const s = sessions.get(sid) || { input: 0, output: 0, cacheRead: 0, cacheCreate: 0, turns: 0, at: '', week: 'unknown' };
87
+ s.input += usage.input_tokens || 0;
88
+ s.output += usage.output_tokens || 0;
89
+ s.cacheRead += usage.cache_read_input_tokens || 0;
90
+ s.cacheCreate += usage.cache_creation_input_tokens || 0;
91
+ s.turns += 1;
92
+ if (entry.timestamp && entry.timestamp > s.at) {
93
+ s.at = entry.timestamp;
94
+ s.week = isoWeek(entry.timestamp);
95
+ }
96
+ sessions.set(sid, s);
97
+ }
98
+ }
99
+ return sessions;
100
+ }
101
+
102
+ const sessionTotal = (s) => s.input + s.output + s.cacheRead + s.cacheCreate;
103
+ const n = (x) => x.toLocaleString('en-US');
104
+
105
+ function summarize(sessions) {
106
+ const rows = [...sessions.entries()].map(([sid, s]) => ({ sid, ...s, total: sessionTotal(s) }));
107
+ rows.sort((a, b) => b.total - a.total);
108
+ const totals = rows.reduce(
109
+ (t, r) => ({ input: t.input + r.input, output: t.output + r.output, cacheRead: t.cacheRead + r.cacheRead, cacheCreate: t.cacheCreate + r.cacheCreate, total: t.total + r.total }),
110
+ { input: 0, output: 0, cacheRead: 0, cacheCreate: 0, total: 0 },
111
+ );
112
+ const weeks = {};
113
+ for (const r of rows) weeks[r.week] = (weeks[r.week] || 0) + r.total;
114
+ return { rows, totals, weeks, sessions: rows.length };
115
+ }
116
+
117
+ function main() {
118
+ const budget = loadConfigSync(ROOT).tokens || { budgetPerSession: 0, warnAtPct: 80 };
119
+ const files = findTranscripts(opt('--from'));
120
+ const { rows, totals, weeks, sessions } = summarize(aggregate(files, flag('--all')));
121
+
122
+ if (flag('--json')) {
123
+ process.stdout.write(JSON.stringify({ sessions, totals, weeks, budget, perSession: rows }, null, 2) + '\n');
124
+ return;
125
+ }
126
+
127
+ console.log('\n🪙 ContextDevKit token report' + (flag('--all') ? ' (all projects)' : ` — ${ROOT}`) + '\n');
128
+ if (sessions === 0) {
129
+ console.log('No token usage found in Claude Code transcripts (~/.claude/projects/).');
130
+ console.log('Open this project in Claude Code and run a session, then try again.');
131
+ return;
132
+ }
133
+ console.log(`Sessions: ${sessions} Total tokens: ${n(totals.total)}`);
134
+ console.log(` input ${n(totals.input)} · output ${n(totals.output)} · cache-read ${n(totals.cacheRead)} · cache-write ${n(totals.cacheCreate)}\n`);
135
+
136
+ const over = budget.budgetPerSession > 0 ? rows.filter((r) => r.total >= (budget.budgetPerSession * (budget.warnAtPct || 100)) / 100) : [];
137
+ console.log('Top sessions by tokens:');
138
+ for (const r of rows.slice(0, 10)) {
139
+ const pct = budget.budgetPerSession > 0 ? ` (${Math.round((r.total / budget.budgetPerSession) * 100)}% of budget)` : '';
140
+ const hot = budget.budgetPerSession > 0 && r.total >= (budget.budgetPerSession * (budget.warnAtPct || 100)) / 100 ? ' ⚠️' : '';
141
+ console.log(` ${String(r.sid).slice(0, 8)} ${n(r.total).padStart(12)} (${r.turns} turns)${pct}${hot}`);
142
+ }
143
+ console.log('\nPer ISO week:');
144
+ for (const [week, total] of Object.entries(weeks).sort()) console.log(` ${week} ${n(total)}`);
145
+
146
+ if (budget.budgetPerSession > 0) {
147
+ console.log(`\nBudget: ${n(budget.budgetPerSession)}/session (warn at ${budget.warnAtPct}%).` + (over.length ? ` ⚠️ ${over.length} session(s) over the warn line.` : ' ✅ all within budget.'));
148
+ } else {
149
+ console.log('\nNo per-session budget set. Configure with: /context-config set tokens.budgetPerSession <n>');
150
+ }
151
+ }
152
+
153
+ main();
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Visual / browser-driven testing harness — scaffolder (roadmap #6).
4
+ *
5
+ * The kit does NOT bundle or run the browser runner (Playwright/Selenium are heavy
6
+ * PROJECT dependencies that download real browsers) — it SCAFFOLDS a starter and the
7
+ * project owns the runner. Stack-aware: JS (@playwright/test) + Python
8
+ * (pytest-playwright). Zero-dependency, defensive, write-if-missing.
9
+ *
10
+ * visual-test.mjs status [--json] # is a visual harness set up here?
11
+ * visual-test.mjs scaffold [--js | --python] # write a starter (won't clobber)
12
+ */
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
14
+ import { dirname, resolve } from 'node:path';
15
+
16
+ const ROOT = process.cwd();
17
+ const has = (p) => existsSync(resolve(ROOT, p));
18
+ const read = (p) => {
19
+ try {
20
+ return readFileSync(resolve(ROOT, p), 'utf-8');
21
+ } catch {
22
+ return '';
23
+ }
24
+ };
25
+ const readJson = (p) => {
26
+ try {
27
+ return JSON.parse(read(p).replace(/^/, ''));
28
+ } catch {
29
+ return null;
30
+ }
31
+ };
32
+
33
+ const JS_CONFIGS = ['playwright.config.js', 'playwright.config.ts', 'playwright.config.mjs', 'cypress.config.js'];
34
+
35
+ function detectStacks() {
36
+ const stacks = [];
37
+ // A stack counts if its manifest exists OR a visual harness is already scaffolded
38
+ // (so `status` recognizes an existing harness even in a bare project).
39
+ if (has('package.json') || JS_CONFIGS.some(has)) stacks.push('js');
40
+ if (has('pyproject.toml') || has('requirements.txt') || has('tests/visual/conftest.py')) stacks.push('python');
41
+ return stacks;
42
+ }
43
+
44
+ function jsStatus() {
45
+ const pkg = readJson('package.json') || {};
46
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
47
+ return {
48
+ runner: deps.cypress ? 'cypress' : 'playwright-js',
49
+ dep: !!(deps['@playwright/test'] || deps.playwright || deps.cypress),
50
+ config: JS_CONFIGS.some(has),
51
+ };
52
+ }
53
+
54
+ function pyStatus() {
55
+ const text = read('pyproject.toml') + read('requirements.txt');
56
+ return { runner: 'playwright-python', dep: /pytest-playwright|playwright|selenium/.test(text), config: has('tests/visual') };
57
+ }
58
+
59
+ function buildStatus() {
60
+ const stacks = detectStacks();
61
+ const report = {
62
+ stacks,
63
+ js: stacks.includes('js') ? jsStatus() : null,
64
+ python: stacks.includes('python') ? pyStatus() : null,
65
+ };
66
+ report.set = !!((report.js && (report.js.dep || report.js.config)) || (report.python && (report.python.dep || report.python.config)));
67
+ return report;
68
+ }
69
+
70
+ function writeIfMissing(rel, content) {
71
+ const abs = resolve(ROOT, rel);
72
+ if (existsSync(abs)) return [];
73
+ mkdirSync(dirname(abs), { recursive: true });
74
+ writeFileSync(abs, content, 'utf-8');
75
+ return [rel];
76
+ }
77
+
78
+ function scaffoldJs() {
79
+ const esm = (readJson('package.json') || {}).type === 'module';
80
+ const config = esm
81
+ ? `import { defineConfig, devices } from '@playwright/test';\n\nexport default defineConfig({\n testDir: './tests/visual',\n use: { baseURL: process.env.BASE_URL || 'http://localhost:3000' },\n projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],\n});\n`
82
+ : `const { defineConfig, devices } = require('@playwright/test');\n\nmodule.exports = defineConfig({\n testDir: './tests/visual',\n use: { baseURL: process.env.BASE_URL || 'http://localhost:3000' },\n projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],\n});\n`;
83
+ const spec = `import { test, expect } from '@playwright/test';\n\n// Visual baseline. The first run creates the snapshot; later runs diff against it.\n// Update intentionally with: npx playwright test --update-snapshots\ntest('home page — visual baseline', async ({ page }) => {\n await page.goto('/');\n await expect(page).toHaveScreenshot('home.png', { maxDiffPixelRatio: 0.01 });\n});\n`;
84
+ return [...writeIfMissing('playwright.config.js', config), ...writeIfMissing('tests/visual/home.spec.js', spec)];
85
+ }
86
+
87
+ function scaffoldPython() {
88
+ const conftest = `import os\nimport pytest\n\n\n@pytest.fixture(scope="session")\ndef base_url():\n return os.environ.get("BASE_URL", "http://localhost:3000")\n`;
89
+ const test = `# Visual baseline with pytest-playwright. Install:\n# pip install pytest-playwright && playwright install chromium\n# Run: pytest tests/visual\nimport pathlib\n\nSNAP = pathlib.Path(__file__).parent / "__screenshots__"\n\n\ndef test_home_visual_baseline(page, base_url):\n SNAP.mkdir(exist_ok=True)\n page.goto(base_url)\n # First run writes the baseline; later runs compare. Swap in your team's\n # visual-diff assertion (e.g. pytest-playwright-visual) when you adopt one.\n page.screenshot(path=str(SNAP / "home.png"))\n`;
90
+ return [...writeIfMissing('tests/visual/conftest.py', conftest), ...writeIfMissing('tests/visual/test_home_visual.py', test)];
91
+ }
92
+
93
+ function runScaffold(only) {
94
+ const stacks = only ? [only] : detectStacks().length ? detectStacks() : ['js'];
95
+ const written = [];
96
+ for (const s of stacks) written.push(...(s === 'python' ? scaffoldPython() : scaffoldJs()));
97
+ if (written.length === 0) {
98
+ console.log('🖼️ visual-test: starter already present (nothing written).');
99
+ return;
100
+ }
101
+ console.log(`🖼️ visual-test: scaffolded ${written.length} file(s):`);
102
+ for (const w of written) console.log(` + ${w}`);
103
+ console.log('\nNext — install the runner (a PROJECT dependency, never the kit):');
104
+ if (stacks.includes('js')) console.log(' npm i -D @playwright/test && npx playwright install chromium');
105
+ if (stacks.includes('python')) console.log(' pip install pytest-playwright && playwright install chromium');
106
+ console.log(' Point baseURL / BASE_URL at your running app, then run the visual suite.');
107
+ }
108
+
109
+ function main() {
110
+ const args = process.argv.slice(2);
111
+ const cmd = args[0];
112
+ if (cmd === 'status') {
113
+ const report = buildStatus();
114
+ if (args.includes('--json')) {
115
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
116
+ return;
117
+ }
118
+ console.log(`🖼️ visual-test status — stacks: ${report.stacks.join(', ') || 'none'}`);
119
+ if (report.js) console.log(` JS: runner=${report.js.runner} dep=${report.js.dep} config=${report.js.config}`);
120
+ if (report.python) console.log(` Python: dep=${report.python.dep} layout=${report.python.config}`);
121
+ console.log(report.set ? ' ✓ a visual harness looks set up.' : ' ✗ no visual harness yet — run: visual-test.mjs scaffold');
122
+ return;
123
+ }
124
+ if (cmd === 'scaffold') {
125
+ runScaffold(args.includes('--python') ? 'python' : args.includes('--js') ? 'js' : null);
126
+ return;
127
+ }
128
+ console.error('Usage: visual-test.mjs <status [--json] | scaffold [--js|--python]>');
129
+ process.exit(1);
130
+ }
131
+
132
+ main();
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `/watch` — tail the active session ledger.
4
+ *
5
+ * Default: print every modification recorded in the current session's ledger,
6
+ * formatted one-per-line, then exit.
7
+ *
8
+ * `--follow` (`-f`): re-read the ledger every 500ms and print only entries
9
+ * that appeared since the last read. Exits cleanly on SIGINT (Ctrl-C).
10
+ *
11
+ * Zero deps. Reuses `readMostRecentLedger` from the runtime so the active
12
+ * session resolution stays single-sourced (rule 4).
13
+ *
14
+ * Refused-silently-to-false-negative posture: if the ledger cannot be read,
15
+ * print the error and exit non-zero. Never pretend "0 entries."
16
+ */
17
+ import { readMostRecentLedger } from '../../runtime/hooks/ledger.mjs';
18
+
19
+ const FOLLOW_INTERVAL_MS = 500;
20
+
21
+ const args = process.argv.slice(2);
22
+ const follow = args.includes('-f') || args.includes('--follow');
23
+
24
+ /**
25
+ * Format one ledger modification for stdout.
26
+ *
27
+ * @param {{ path: string, tool: string, at: number }} mod
28
+ * @returns {string}
29
+ */
30
+ export function parseLedgerEntry(mod) {
31
+ if (!mod || typeof mod !== 'object') return '';
32
+ const at = typeof mod.at === 'number' ? new Date(mod.at) : new Date();
33
+ const hh = String(at.getHours()).padStart(2, '0');
34
+ const mm = String(at.getMinutes()).padStart(2, '0');
35
+ const ss = String(at.getSeconds()).padStart(2, '0');
36
+ const tool = (mod.tool || '?').toUpperCase().padEnd(5, ' ');
37
+ const path = mod.path || '<no path>';
38
+ return `[${hh}:${mm}:${ss}] ${tool} ${path}`;
39
+ }
40
+
41
+ async function loadEntries() {
42
+ const result = await readMostRecentLedger();
43
+ if (!result) return { sessionId: null, entries: [] };
44
+ const entries = Array.isArray(result.ledger?.modifications)
45
+ ? result.ledger.modifications
46
+ : [];
47
+ return { sessionId: result.sessionId, entries };
48
+ }
49
+
50
+ async function printAllOnce() {
51
+ const { sessionId, entries } = await loadEntries();
52
+ if (!sessionId) {
53
+ process.stderr.write('no active session — `/log-session` to register or start a new one\n');
54
+ process.exit(1);
55
+ }
56
+ process.stdout.write(`# session ${sessionId} — ${entries.length} entr${entries.length === 1 ? 'y' : 'ies'}\n`);
57
+ for (const mod of entries) process.stdout.write(parseLedgerEntry(mod) + '\n');
58
+ }
59
+
60
+ async function follower() {
61
+ let printedCount = 0;
62
+ let lastSessionId = null;
63
+ const tick = async () => {
64
+ try {
65
+ const { sessionId, entries } = await loadEntries();
66
+ if (!sessionId) return;
67
+ if (sessionId !== lastSessionId) {
68
+ process.stdout.write(`# session ${sessionId}\n`);
69
+ lastSessionId = sessionId;
70
+ printedCount = 0;
71
+ }
72
+ for (let i = printedCount; i < entries.length; i++) {
73
+ process.stdout.write(parseLedgerEntry(entries[i]) + '\n');
74
+ }
75
+ printedCount = entries.length;
76
+ } catch (err) {
77
+ process.stderr.write(`watch: ${err.message}\n`);
78
+ }
79
+ };
80
+ await tick();
81
+ const handle = setInterval(tick, FOLLOW_INTERVAL_MS);
82
+ const stop = () => {
83
+ clearInterval(handle);
84
+ process.stdout.write('\n# watch stopped\n');
85
+ process.exit(0);
86
+ };
87
+ process.on('SIGINT', stop);
88
+ process.on('SIGTERM', stop);
89
+ }
90
+
91
+ const isMain = (() => {
92
+ try {
93
+ const url = new URL(import.meta.url);
94
+ const arg = process.argv[1] ? new URL('file://' + process.argv[1].replace(/\\/g, '/')) : null;
95
+ return arg && url.pathname.toLowerCase() === arg.pathname.toLowerCase();
96
+ } catch {
97
+ return false;
98
+ }
99
+ })();
100
+
101
+ if (isMain) {
102
+ (follow ? follower() : printAllOnce()).catch((err) => {
103
+ process.stderr.write(`watch: ${err.message}\n`);
104
+ process.exit(1);
105
+ });
106
+ }