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,198 @@
1
+ /**
2
+ * Dashboard data reader — single owner of "read the project's current
3
+ * state into a structured object" (ticket 051).
4
+ *
5
+ * Every consumer (the snapshot generator and the live `--watch` server)
6
+ * goes through `buildDashboardData(root)`. Files are re-read on each
7
+ * call; there is no caching — the data object is the snapshot.
8
+ *
9
+ * Zero deps. YAML frontmatter is parsed with a small inline parser
10
+ * (key: value pairs only — the kit's tickets/ADRs do not use nested
11
+ * YAML).
12
+ *
13
+ * Single-sourced paths via `paths.mjs` per rule 4.
14
+ */
15
+ import { execFileSync } from 'node:child_process';
16
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
17
+ import { basename, resolve } from 'node:path';
18
+ import {
19
+ PLATFORM_DIR,
20
+ MEMORY_DIR,
21
+ DECISIONS_DIR,
22
+ SESSIONS_DIR,
23
+ CHANGELOG,
24
+ CONFIG_FILE,
25
+ } from '../../runtime/config/paths.mjs';
26
+
27
+ const PIPELINE_DIR = `${PLATFORM_DIR}/pipeline`;
28
+ const ROADMAP_FILE = `${MEMORY_DIR}/roadmap.md`;
29
+ const LANES = ['backlog', 'working', 'testing', 'conclusion'];
30
+
31
+ /** Strip a leading UTF-8 BOM if present (rule 4). */
32
+ const stripBom = (s) => s.replace(/^/, '');
33
+
34
+ /** Read a file as utf-8; returns '' on any failure (defensive — rule 2). */
35
+ function readSafe(path) {
36
+ try { return stripBom(readFileSync(path, 'utf-8')); } catch { return ''; }
37
+ }
38
+
39
+ /**
40
+ * Parse YAML-frontmatter from a markdown file's text.
41
+ *
42
+ * @param {string} text
43
+ * @returns {{ data: Record<string, string>, body: string }}
44
+ */
45
+ export function parseFrontmatter(text) {
46
+ if (!text.startsWith('---')) return { data: {}, body: text };
47
+ const end = text.indexOf('\n---', 4);
48
+ if (end === -1) return { data: {}, body: text };
49
+ const raw = text.slice(4, end).trim();
50
+ const body = text.slice(end + 4).replace(/^\n/, '');
51
+ const data = {};
52
+ for (const line of raw.split('\n')) {
53
+ const m = /^([\w-]+):\s*(.*)$/.exec(line);
54
+ if (m) data[m[1]] = m[2].trim();
55
+ }
56
+ return { data, body };
57
+ }
58
+
59
+ function readTicket(lane, file, dir) {
60
+ const text = readSafe(resolve(dir, file));
61
+ const { data, body } = parseFrontmatter(text);
62
+ const lines = body.split('\n').filter((l) => l.trim().length > 0);
63
+ const firstHeading = lines.find((l) => l.startsWith('## ')) || '';
64
+ const bodyExcerpt = (firstHeading ? body.split(firstHeading)[1] || '' : body).slice(0, 280).trim();
65
+ return {
66
+ id: data.id || file.slice(0, 3),
67
+ title: data.title || firstHeading.replace(/^##\s+/, '') || file.replace(/\.md$/, ''),
68
+ type: data.type || '',
69
+ priority: data.priority || '',
70
+ sla: data.sla || '',
71
+ status: data.status || lane,
72
+ source: data.source || '',
73
+ bodyExcerpt,
74
+ file: `${PIPELINE_DIR}/${lane}/${file}`,
75
+ lane,
76
+ };
77
+ }
78
+
79
+ function readLane(lane, root) {
80
+ const dir = resolve(root, PIPELINE_DIR, lane);
81
+ if (!existsSync(dir)) return [];
82
+ return readdirSync(dir)
83
+ .filter((f) => f.endsWith('.md') && !f.startsWith('.'))
84
+ .map((file) => readTicket(lane, file, dir))
85
+ .sort((a, b) => a.id.localeCompare(b.id));
86
+ }
87
+
88
+ function readPipeline(root) {
89
+ const out = {};
90
+ for (const lane of LANES) out[lane] = readLane(lane, root);
91
+ return out;
92
+ }
93
+
94
+ function readAdrs(root) {
95
+ const dir = resolve(root, DECISIONS_DIR);
96
+ if (!existsSync(dir)) return [];
97
+ return readdirSync(dir)
98
+ .filter((f) => /^\d{4}-.+\.md$/.test(f))
99
+ .map((file) => {
100
+ const text = readSafe(resolve(dir, file));
101
+ const number = file.slice(0, 4);
102
+ const titleMatch = /^#\s+ADR-\d+:\s*(.+)$/m.exec(text);
103
+ const statusMatch = /^-\s*\*\*Status\*\*:\s*(.+)$/m.exec(text);
104
+ const dateMatch = /^-\s*\*\*Date\*\*:\s*(.+)$/m.exec(text);
105
+ return {
106
+ number,
107
+ title: titleMatch ? titleMatch[1].trim() : file.replace(/^\d{4}-/, '').replace(/\.md$/, ''),
108
+ status: statusMatch ? statusMatch[1].trim() : 'Unknown',
109
+ date: dateMatch ? dateMatch[1].trim() : '',
110
+ file: `${DECISIONS_DIR}/${file}`,
111
+ };
112
+ })
113
+ .sort((a, b) => b.number.localeCompare(a.number));
114
+ }
115
+
116
+ function readSessions(root, limit = 10) {
117
+ const dir = resolve(root, SESSIONS_DIR);
118
+ if (!existsSync(dir)) return [];
119
+ return readdirSync(dir)
120
+ .filter((f) => /^\d{4}-\d{2}-\d{2}-\d{2,}-.+\.md$/.test(f))
121
+ .map((file) => {
122
+ const text = readSafe(resolve(dir, file));
123
+ const dateMatch = /^-\s*\*\*Date\*\*:\s*(.+)$/m.exec(text);
124
+ const numMatch = /^-\s*\*\*Session number\*\*:\s*(.+)$/m.exec(text);
125
+ const branchMatch = /^-\s*\*\*Branch\*\*:\s*`?([^`\n]+)`?$/m.exec(text);
126
+ const titleMatch = /^#\s+(.+)$/m.exec(text);
127
+ return {
128
+ number: numMatch ? numMatch[1].trim() : file.split('-')[3] || '',
129
+ title: titleMatch ? titleMatch[1].trim() : file.replace(/\.md$/, ''),
130
+ date: dateMatch ? dateMatch[1].trim() : file.slice(0, 10),
131
+ branch: branchMatch ? branchMatch[1].trim() : '',
132
+ file: `${SESSIONS_DIR}/${file}`,
133
+ };
134
+ })
135
+ .sort((a, b) => b.number.localeCompare(a.number, undefined, { numeric: true }))
136
+ .slice(0, limit);
137
+ }
138
+
139
+ function readRoadmap(root) {
140
+ const text = readSafe(resolve(root, ROADMAP_FILE));
141
+ return { exists: text.length > 0, markdown: text };
142
+ }
143
+
144
+ function readChangelogUnreleased(root) {
145
+ const text = readSafe(resolve(root, CHANGELOG));
146
+ const idx = text.indexOf('## [Unreleased]');
147
+ if (idx === -1) return '';
148
+ const after = text.slice(idx + '## [Unreleased]'.length);
149
+ const next = after.search(/\n## \[/);
150
+ return (next === -1 ? after : after.slice(0, next)).trim();
151
+ }
152
+
153
+ function readConfig(root) {
154
+ try {
155
+ return JSON.parse(stripBom(readFileSync(resolve(root, CONFIG_FILE), 'utf-8')));
156
+ } catch { return null; }
157
+ }
158
+
159
+ function readBranch(root) {
160
+ try {
161
+ return execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
162
+ cwd: root, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 2000,
163
+ }).trim();
164
+ } catch { return ''; }
165
+ }
166
+
167
+ /**
168
+ * Build the full dashboard data object.
169
+ *
170
+ * @param {string} root project root (absolute)
171
+ * @returns {object} plain JS data object — no Dates, no functions
172
+ */
173
+ export function buildDashboardData(root) {
174
+ const config = readConfig(root);
175
+ const pipeline = readPipeline(root);
176
+ return {
177
+ meta: {
178
+ project: basename(root),
179
+ branch: readBranch(root),
180
+ level: config?.level ?? null,
181
+ platformDir: PLATFORM_DIR,
182
+ generatedAt: Date.now(),
183
+ },
184
+ pipeline,
185
+ counts: {
186
+ backlog: pipeline.backlog.length,
187
+ working: pipeline.working.length,
188
+ testing: pipeline.testing.length,
189
+ conclusion: pipeline.conclusion.length,
190
+ },
191
+ adrs: readAdrs(root),
192
+ sessions: readSessions(root),
193
+ roadmap: readRoadmap(root),
194
+ changelogUnreleased: readChangelogUnreleased(root),
195
+ };
196
+ }
197
+
198
+ export const DASHBOARD_LANES = LANES;
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Dashboard HTML renderer — turns a data object from `dashboard-data.mjs`
3
+ * into a single self-contained HTML string (ticket 051).
4
+ *
5
+ * Single string output, no streaming. Inline CSS + inline JS, no
6
+ * external assets. Works opened directly from the file system AND when
7
+ * served by `dashboard-server.mjs` (the live `--watch` mode appends a
8
+ * tiny SSE-reconnecting client at the bottom).
9
+ *
10
+ * The renderer is **purely functional** — same input gives the same
11
+ * HTML. Reads no files; mutates no globals. Easy to unit-test.
12
+ */
13
+
14
+ const TYPE_COLORS = {
15
+ bug: '#e3413c', chore: '#7a8497', increment: '#3b6ef0',
16
+ spike: '#8b5cf6', docs: '#10b981',
17
+ };
18
+ const PRI_COLORS = {
19
+ P1: '#e3413c', P2: '#f59e0b', P3: '#facc15', P4: '#9ca3af',
20
+ };
21
+ const ADR_STATUS_COLORS = {
22
+ Accepted: '#10b981', Proposed: '#f59e0b', Superseded: '#9ca3af',
23
+ };
24
+
25
+ const escapeHtml = (s) => String(s ?? '')
26
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
27
+ .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
28
+
29
+ const fmtTime = (ms) => {
30
+ const d = new Date(ms);
31
+ const p = (n) => String(n).padStart(2, '0');
32
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
33
+ };
34
+
35
+ const renderBadge = (text, color) =>
36
+ `<span class="badge" style="background:${color}">${escapeHtml(text)}</span>`;
37
+
38
+ const renderCard = (t) => {
39
+ const typeBadge = t.type ? renderBadge(t.type, TYPE_COLORS[t.type] || '#7a8497') : '';
40
+ const priBadge = t.priority ? renderBadge(t.priority, PRI_COLORS[t.priority] || '#9ca3af') : '';
41
+ const sla = t.sla ? `<span class="sla" title="SLA">⏰ ${escapeHtml(t.sla)}</span>` : '';
42
+ const src = t.source ? `<span class="src" title="source">↳ ${escapeHtml(t.source)}</span>` : '';
43
+ return `<article class="card" data-id="${escapeHtml(t.id)}">
44
+ <header><span class="tid">#${escapeHtml(t.id)}</span>${typeBadge}${priBadge}</header>
45
+ <h3>${escapeHtml(t.title)}</h3>
46
+ <footer>${sla}${src}</footer>
47
+ </article>`;
48
+ };
49
+
50
+ const LANE_META = {
51
+ backlog: { title: 'Backlog', icon: '📋', tone: 'lane-backlog' },
52
+ working: { title: 'Working', icon: '🔵', tone: 'lane-working' },
53
+ testing: { title: 'Testing', icon: '🟡', tone: 'lane-testing' },
54
+ conclusion: { title: 'Concluded', icon: '✅', tone: 'lane-conclusion' },
55
+ };
56
+
57
+ const renderLane = (lane, tickets) => {
58
+ const meta = LANE_META[lane];
59
+ const items = tickets.length
60
+ ? tickets.map(renderCard).join('')
61
+ : `<div class="empty">— empty —</div>`;
62
+ return `<section class="lane ${meta.tone}">
63
+ <header><span class="lane-icon">${meta.icon}</span><h2>${meta.title}</h2><span class="lane-count">${tickets.length}</span></header>
64
+ <div class="lane-body">${items}</div>
65
+ </section>`;
66
+ };
67
+
68
+ const renderAdrs = (adrs) => {
69
+ if (!adrs.length) return '<p class="empty">No ADRs yet.</p>';
70
+ return adrs.slice(0, 12).map((a) => {
71
+ const statusColor = ADR_STATUS_COLORS[a.status?.split(/[\s(]/)[0]] || '#9ca3af';
72
+ return `<article class="adr"><header>
73
+ <span class="adr-num">ADR-${escapeHtml(a.number)}</span>
74
+ ${renderBadge(a.status?.split('(')[0].trim() || '?', statusColor)}
75
+ <span class="adr-date">${escapeHtml(a.date)}</span>
76
+ </header><h4>${escapeHtml(a.title)}</h4></article>`;
77
+ }).join('');
78
+ };
79
+
80
+ const renderSessions = (sessions) => {
81
+ if (!sessions.length) return '<p class="empty">No sessions logged yet.</p>';
82
+ return sessions.map((s) => `<article class="session">
83
+ <header><span class="sess-num">Session ${escapeHtml(s.number)}</span><span class="sess-date">${escapeHtml(s.date)}</span></header>
84
+ <h4>${escapeHtml(s.title)}</h4>
85
+ <footer>${s.branch ? `<code>${escapeHtml(s.branch)}</code>` : ''}</footer>
86
+ </article>`).join('');
87
+ };
88
+
89
+ const renderMarkdownLight = (md) => {
90
+ if (!md) return '';
91
+ return escapeHtml(md)
92
+ .replace(/^### (.+)$/gm, '<h4>$1</h4>')
93
+ .replace(/^## (.+)$/gm, '<h3>$1</h3>')
94
+ .replace(/^# (.+)$/gm, '<h2>$1</h2>')
95
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
96
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
97
+ .replace(/^- (.+)$/gm, '<li>$1</li>')
98
+ .replace(/(<li>.*?<\/li>\n?)+/gs, (m) => `<ul>${m}</ul>`)
99
+ .replace(/\n\n/g, '</p><p>')
100
+ .replace(/^(?!<)/gm, '');
101
+ };
102
+
103
+ /**
104
+ * Render the full HTML document for a dashboard data object.
105
+ *
106
+ * @param {object} data from buildDashboardData()
107
+ * @param {object} [opts]
108
+ * @param {boolean} [opts.live] when true, append the SSE client script
109
+ * @returns {string} self-contained HTML
110
+ */
111
+ export function renderDashboardHTML(data, opts = {}) {
112
+ const live = !!opts.live;
113
+ const indicator = live
114
+ ? `<span class="live-pill" id="live-pill"><span class="dot"></span>live</span>`
115
+ : `<span class="snap-pill">snapshot · ${fmtTime(data.meta.generatedAt)}</span>`;
116
+ const liveScript = live ? `<script>${CLIENT_JS}</script>` : '';
117
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8">
118
+ <meta name="viewport" content="width=device-width,initial-scale=1">
119
+ <title>${escapeHtml(data.meta.project)} — ContextDevKit dashboard</title>
120
+ <style>${CSS}</style>
121
+ </head><body>
122
+ <header class="topbar">
123
+ <div class="brand"><strong>${escapeHtml(data.meta.project)}</strong><span class="muted">·</span><code>${escapeHtml(data.meta.branch || '?')}</code><span class="muted">·</span>L${data.meta.level ?? '?'}</div>
124
+ ${indicator}
125
+ </header>
126
+ <main>
127
+ <section class="counts">
128
+ ${Object.entries(data.counts).map(([k, v]) => `<div class="count ${LANE_META[k].tone}"><span class="n">${v}</span><span class="l">${LANE_META[k].title}</span></div>`).join('')}
129
+ </section>
130
+ <section class="pipeline" id="pipeline">
131
+ ${['backlog', 'working', 'testing', 'conclusion'].map((l) => renderLane(l, data.pipeline[l])).join('')}
132
+ </section>
133
+ <section class="two-col">
134
+ <div><h2>Recent ADRs</h2><div class="adrs" id="adrs">${renderAdrs(data.adrs)}</div></div>
135
+ <div><h2>Recent sessions</h2><div class="sessions" id="sessions">${renderSessions(data.sessions)}</div></div>
136
+ </section>
137
+ <details class="block"><summary><h2>CHANGELOG · [Unreleased]</h2></summary><div class="md" id="changelog">${renderMarkdownLight(data.changelogUnreleased)}</div></details>
138
+ ${data.roadmap.exists ? `<details class="block"><summary><h2>Roadmap</h2></summary><div class="md" id="roadmap">${renderMarkdownLight(data.roadmap.markdown)}</div></details>` : ''}
139
+ </main>
140
+ <footer class="footnote">ContextDevKit · ${escapeHtml(data.meta.platformDir)}/ · generated ${fmtTime(data.meta.generatedAt)}</footer>
141
+ ${liveScript}
142
+ </body></html>`;
143
+ }
144
+
145
+ const CSS = `
146
+ :root { color-scheme: light dark; --bg:#fff; --fg:#1f2328; --muted:#7a8497; --card:#f6f7f9; --border:#e1e4e8; --accent:#5046e5; }
147
+ @media (prefers-color-scheme: dark) { :root { --bg:#0c0c10; --fg:#e4e6eb; --muted:#9ca3af; --card:#16161c; --border:#2a2a33; } }
148
+ * { box-sizing: border-box; } body { margin:0; font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif; background:var(--bg); color:var(--fg); }
149
+ .topbar { display:flex; align-items:center; justify-content:space-between; padding:14px 24px; border-bottom:1px solid var(--border); position:sticky; top:0; background:var(--bg); z-index:10; }
150
+ .brand { display:flex; gap:8px; align-items:center; } .brand code { font:12px/1 ui-monospace,Menlo,monospace; padding:3px 6px; border:1px solid var(--border); border-radius:4px; }
151
+ .muted { color:var(--muted); } .live-pill { display:inline-flex; align-items:center; gap:6px; padding:4px 10px; border-radius:12px; background:#10b981; color:#fff; font-weight:600; font-size:12px; }
152
+ .live-pill .dot { width:8px; height:8px; background:#fff; border-radius:50%; animation: pulse 2s infinite; }
153
+ .live-pill.stale { background:#e3413c; } .snap-pill { font-size:12px; color:var(--muted); }
154
+ @keyframes pulse { 0%,100% { opacity:1 } 50% { opacity:.4 } }
155
+ main { max-width: 1600px; margin: 0 auto; padding: 24px; }
156
+ .counts { display:grid; grid-template-columns: repeat(4, 1fr); gap:12px; margin-bottom:24px; }
157
+ .count { padding:14px; border-radius:8px; background:var(--card); border:1px solid var(--border); display:flex; justify-content:space-between; align-items:baseline; }
158
+ .count .n { font-size:28px; font-weight:700; } .count .l { color:var(--muted); }
159
+ .pipeline { display:grid; grid-template-columns: repeat(4, minmax(220px, 1fr)); gap:14px; margin-bottom:32px; }
160
+ @media (max-width:1100px) { .pipeline { grid-template-columns: repeat(2, 1fr); } } @media (max-width:680px) { .pipeline { grid-template-columns: 1fr; } }
161
+ .lane { background:var(--card); border:1px solid var(--border); border-radius:8px; display:flex; flex-direction:column; min-height:200px; }
162
+ .lane > header { display:flex; align-items:center; gap:8px; padding:12px 14px; border-bottom:1px solid var(--border); }
163
+ .lane > header h2 { font-size:13px; font-weight:600; text-transform:uppercase; letter-spacing:.5px; margin:0; flex:1; }
164
+ .lane-count { background:var(--bg); border:1px solid var(--border); padding:1px 8px; border-radius:10px; font-size:11px; font-weight:600; }
165
+ .lane-body { padding:8px; display:flex; flex-direction:column; gap:8px; flex:1; }
166
+ .lane-backlog > header { color:#7a8497; } .lane-working > header { color:#3b6ef0; }
167
+ .lane-testing > header { color:#f59e0b; } .lane-conclusion > header { color:#10b981; }
168
+ .card { background:var(--bg); border:1px solid var(--border); border-radius:6px; padding:10px; transition: transform .1s ease, box-shadow .1s ease; }
169
+ .card:hover { transform: translateY(-1px); box-shadow: 0 2px 8px rgba(0,0,0,.08); border-color:var(--accent); }
170
+ .card header { display:flex; gap:6px; align-items:center; margin-bottom:6px; }
171
+ .card .tid { font:11px/1 ui-monospace,Menlo,monospace; color:var(--muted); margin-right:auto; }
172
+ .card h3 { font-size:13px; font-weight:600; margin:0; line-height:1.35; }
173
+ .card footer { display:flex; flex-direction:column; gap:2px; margin-top:6px; font-size:11px; color:var(--muted); }
174
+ .badge { display:inline-block; padding:1px 7px; border-radius:8px; color:#fff; font-size:10px; font-weight:600; letter-spacing:.3px; text-transform:uppercase; }
175
+ .empty { color:var(--muted); font-style:italic; padding:12px; text-align:center; font-size:12px; }
176
+ .two-col { display:grid; grid-template-columns: 1fr 1fr; gap:24px; margin-bottom:24px; } @media (max-width:900px) { .two-col { grid-template-columns: 1fr; } }
177
+ .two-col h2 { font-size:14px; font-weight:600; text-transform:uppercase; letter-spacing:.5px; color:var(--muted); margin:0 0 10px; }
178
+ .adrs, .sessions { display:flex; flex-direction:column; gap:8px; }
179
+ .adr, .session { background:var(--card); border:1px solid var(--border); border-radius:6px; padding:10px 12px; }
180
+ .adr header, .session header { display:flex; gap:8px; align-items:center; margin-bottom:4px; }
181
+ .adr-num, .sess-num { font:11px/1 ui-monospace,Menlo,monospace; font-weight:600; }
182
+ .adr-date, .sess-date { font-size:11px; color:var(--muted); margin-left:auto; }
183
+ .adr h4, .session h4 { font-size:13px; margin:0; font-weight:500; line-height:1.35; }
184
+ .session footer { margin-top:4px; font-size:11px; }
185
+ .block { background:var(--card); border:1px solid var(--border); border-radius:8px; padding:0 16px; margin-bottom:16px; }
186
+ .block summary { cursor:pointer; padding:14px 0; list-style:none; }
187
+ .block summary h2 { display:inline; font-size:14px; font-weight:600; text-transform:uppercase; letter-spacing:.5px; color:var(--muted); margin:0; }
188
+ .block summary::before { content:'▸ '; color:var(--muted); } .block[open] summary::before { content:'▾ '; }
189
+ .md { padding-bottom:16px; } .md h3 { font-size:13px; text-transform:uppercase; letter-spacing:.5px; color:var(--muted); margin:14px 0 4px; }
190
+ .md ul { padding-left:20px; margin:6px 0; } .md code { background:var(--bg); border:1px solid var(--border); padding:1px 4px; border-radius:3px; font:12px ui-monospace,Menlo,monospace; }
191
+ .footnote { text-align:center; padding:24px; font-size:11px; color:var(--muted); }
192
+ `;
193
+
194
+ const CLIENT_JS = `
195
+ const pill = document.getElementById('live-pill');
196
+ const renderLane = (l, items) => items.length ? items.map(t => \`<article class="card"><header><span class="tid">#\${t.id}</span></header><h3>\${t.title}</h3></article>\`).join('') : '<div class="empty">— empty —</div>';
197
+ function applyData(d) {
198
+ for (const lane of ['backlog','working','testing','conclusion']) {
199
+ const sel = document.querySelector('.lane-' + lane + ' .lane-body');
200
+ const cnt = document.querySelector('.lane-' + lane + ' .lane-count');
201
+ if (sel && d.pipeline[lane]) sel.innerHTML = renderLane(lane, d.pipeline[lane]);
202
+ if (cnt) cnt.textContent = d.pipeline[lane].length;
203
+ }
204
+ for (const [k, v] of Object.entries(d.counts || {})) {
205
+ const el = document.querySelector('.count.lane-' + k + ' .n'); if (el) el.textContent = v;
206
+ }
207
+ }
208
+ function connect() {
209
+ const ev = new EventSource('/events');
210
+ ev.onopen = () => { if (pill) { pill.classList.remove('stale'); } };
211
+ ev.onerror = () => { if (pill) { pill.classList.add('stale'); } ev.close(); setTimeout(connect, 1500); };
212
+ ev.onmessage = (e) => { try { applyData(JSON.parse(e.data)); } catch {} };
213
+ }
214
+ connect();
215
+ `;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Dashboard live server (`--watch` mode) — ticket 051.
3
+ *
4
+ * Spawns a tiny `node:http` server bound to 127.0.0.1 only. Two routes:
5
+ * GET / → serves the dashboard HTML with the live-mode client
6
+ * GET /events → text/event-stream that pushes the rebuilt data
7
+ * object whenever a file in the platform dir changes
8
+ *
9
+ * Change detection: `fs.watch` on `contextkit/` recursive=true with a
10
+ * 200 ms debouncer collapsing bursts (e.g. `pipeline.mjs sync`
11
+ * rewriting indices). A 15 s heartbeat keeps the SSE connection alive
12
+ * through reverse proxies (not that we proxy — we bind localhost — but
13
+ * defensive).
14
+ *
15
+ * Zero deps. Defensive: any failure logs and continues (rule 2 — never
16
+ * break the dev loop). Clean shutdown on SIGINT.
17
+ */
18
+ import { createServer } from 'node:http';
19
+ import { watch as fsWatch } from 'node:fs';
20
+ import { resolve } from 'node:path';
21
+ import { PLATFORM_DIR } from '../../runtime/config/paths.mjs';
22
+ import { buildDashboardData } from './dashboard-data.mjs';
23
+ import { renderDashboardHTML } from './dashboard-html.mjs';
24
+
25
+ const HEARTBEAT_MS = 15_000;
26
+ const DEBOUNCE_MS = 200;
27
+ const DEFAULT_PORT = 4242;
28
+
29
+ /**
30
+ * Resolve the port to bind to (CLI override > env > default).
31
+ *
32
+ * @param {string[]} argv process.argv slice
33
+ * @returns {number}
34
+ */
35
+ export function resolvePort(argv = []) {
36
+ const flag = argv.find((a) => a.startsWith('--port='));
37
+ if (flag) {
38
+ const n = Number(flag.slice('--port='.length));
39
+ if (Number.isInteger(n) && n > 0 && n < 65536) return n;
40
+ }
41
+ const env = process.env.CONTEXTDEVKIT_DASHBOARD_PORT;
42
+ if (env) {
43
+ const n = Number(env);
44
+ if (Number.isInteger(n) && n > 0 && n < 65536) return n;
45
+ }
46
+ return DEFAULT_PORT;
47
+ }
48
+
49
+ /**
50
+ * Start the live dashboard server. Resolves once it is listening.
51
+ *
52
+ * @param {object} opts
53
+ * @param {string} opts.root project root (absolute)
54
+ * @param {number} opts.port port to bind on 127.0.0.1
55
+ * @returns {Promise<{ close: () => void, port: number }>}
56
+ */
57
+ export async function startDashboardServer({ root, port }) {
58
+ const clients = new Set();
59
+ let cachedData = buildDashboardData(root);
60
+ let cachedHtml = renderDashboardHTML(cachedData, { live: true });
61
+ let debounceHandle = null;
62
+ let watcher = null;
63
+
64
+ const broadcast = (payload) => {
65
+ const line = `data: ${JSON.stringify(payload)}\n\n`;
66
+ for (const res of clients) {
67
+ try { res.write(line); } catch { /* socket dead — onClose will clean up */ }
68
+ }
69
+ };
70
+
71
+ const rebuild = () => {
72
+ try {
73
+ cachedData = buildDashboardData(root);
74
+ cachedHtml = renderDashboardHTML(cachedData, { live: true });
75
+ broadcast(cachedData);
76
+ } catch (err) {
77
+ process.stderr.write(`dashboard: rebuild failed: ${err.message}\n`);
78
+ }
79
+ };
80
+
81
+ const scheduleRebuild = () => {
82
+ if (debounceHandle) clearTimeout(debounceHandle);
83
+ debounceHandle = setTimeout(rebuild, DEBOUNCE_MS);
84
+ };
85
+
86
+ const server = createServer((req, res) => {
87
+ if (req.url === '/events') {
88
+ res.writeHead(200, {
89
+ 'Content-Type': 'text/event-stream',
90
+ 'Cache-Control': 'no-cache, no-transform',
91
+ 'Connection': 'keep-alive',
92
+ 'X-Accel-Buffering': 'no',
93
+ });
94
+ res.write(`data: ${JSON.stringify(cachedData)}\n\n`);
95
+ clients.add(res);
96
+ const heartbeat = setInterval(() => {
97
+ try { res.write(': hb\n\n'); } catch { /* dead */ }
98
+ }, HEARTBEAT_MS);
99
+ req.on('close', () => {
100
+ clearInterval(heartbeat);
101
+ clients.delete(res);
102
+ });
103
+ return;
104
+ }
105
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
106
+ res.end(cachedHtml);
107
+ });
108
+
109
+ try {
110
+ watcher = fsWatch(resolve(root, PLATFORM_DIR), { recursive: true }, () => scheduleRebuild());
111
+ } catch (err) {
112
+ process.stderr.write(`dashboard: fs.watch failed (recursive may not be supported here): ${err.message}\n`);
113
+ }
114
+
115
+ await new Promise((res, rej) => {
116
+ server.once('error', rej);
117
+ server.listen(port, '127.0.0.1', res);
118
+ });
119
+
120
+ return {
121
+ port: server.address().port,
122
+ close: () => {
123
+ try { watcher?.close(); } catch { /* defensive */ }
124
+ for (const r of clients) { try { r.end(); } catch { /* defensive */ } }
125
+ clients.clear();
126
+ server.close();
127
+ },
128
+ };
129
+ }
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `/dashboard` entry — ticket 051.
4
+ *
5
+ * Two modes, one binary:
6
+ * node contextkit/tools/scripts/dashboard.mjs # snapshot → dashboard.html
7
+ * node contextkit/tools/scripts/dashboard.mjs --watch # live server on :4242
8
+ * node contextkit/tools/scripts/dashboard.mjs --watch --port=N # override port
9
+ * node contextkit/tools/scripts/dashboard.mjs --out=path.html # custom snapshot output
10
+ *
11
+ * Snapshot mode is offline-friendly: the resulting file is self-contained
12
+ * (inline CSS + JS, no external assets) and can be opened by double-click.
13
+ * Live mode binds 127.0.0.1 only — never accessible from the network.
14
+ *
15
+ * Zero deps. Defensive (rule 2 — never break the dev loop).
16
+ */
17
+ import { writeFileSync } from 'node:fs';
18
+ import { resolve } from 'node:path';
19
+ import { buildDashboardData } from './dashboard-data.mjs';
20
+ import { renderDashboardHTML } from './dashboard-html.mjs';
21
+
22
+ const argv = process.argv.slice(2);
23
+ const flag = (name) => argv.includes(`--${name}`) || argv.includes(`-${name[0]}`);
24
+ const valueFlag = (name) => {
25
+ const hit = argv.find((a) => a.startsWith(`--${name}=`));
26
+ return hit ? hit.slice(name.length + 3) : null;
27
+ };
28
+
29
+ const WANT_HELP = flag('help') || argv.includes('-h');
30
+ const WANT_WATCH = flag('watch');
31
+ const OUT = valueFlag('out') || 'dashboard.html';
32
+
33
+ const help = () => process.stdout.write(`Usage: dashboard.mjs [--watch] [--port=N] [--out=PATH]
34
+
35
+ Snapshot mode (default):
36
+ Writes a self-contained HTML file (default: ./dashboard.html) and exits.
37
+ Open it in a browser. The file shows the state at the moment of generation.
38
+
39
+ Live mode (--watch):
40
+ Spawns a tiny HTTP server on 127.0.0.1:4242 (override with --port=N or
41
+ $CONTEXTDEVKIT_DASHBOARD_PORT). The page reconnects automatically via SSE
42
+ and re-renders when files in the platform dir change.
43
+
44
+ Examples:
45
+ node contextkit/tools/scripts/dashboard.mjs
46
+ node contextkit/tools/scripts/dashboard.mjs --watch
47
+ node contextkit/tools/scripts/dashboard.mjs --watch --port=8080
48
+ node contextkit/tools/scripts/dashboard.mjs --out=tmp/state.html
49
+ `);
50
+
51
+ async function runSnapshot(root) {
52
+ const data = buildDashboardData(root);
53
+ const html = renderDashboardHTML(data, { live: false });
54
+ const outPath = resolve(root, OUT);
55
+ writeFileSync(outPath, html, 'utf-8');
56
+ process.stdout.write(`📊 dashboard snapshot → ${outPath}\n`);
57
+ process.stdout.write(` Open with your file:// viewer or just double-click.\n`);
58
+ }
59
+
60
+ async function runLive(root) {
61
+ const { startDashboardServer, resolvePort } = await import('./dashboard-server.mjs');
62
+ const port = resolvePort(argv);
63
+ let server;
64
+ try {
65
+ server = await startDashboardServer({ root, port });
66
+ } catch (err) {
67
+ if (err.code === 'EADDRINUSE') {
68
+ process.stderr.write(`dashboard: port ${port} is in use — try --port=N or stop the conflicting process.\n`);
69
+ } else {
70
+ process.stderr.write(`dashboard: failed to start on port ${port}: ${err.message}\n`);
71
+ }
72
+ process.exit(1);
73
+ return;
74
+ }
75
+ const url = `http://127.0.0.1:${server.port}`;
76
+ process.stdout.write(`📊 dashboard live → ${url}\n`);
77
+ process.stdout.write(` Watching ${root}/contextkit/ — Ctrl+C to stop.\n`);
78
+ const shutdown = () => {
79
+ process.stdout.write(`\n dashboard stopped.\n`);
80
+ server.close();
81
+ process.exit(0);
82
+ };
83
+ process.on('SIGINT', shutdown);
84
+ process.on('SIGTERM', shutdown);
85
+ }
86
+
87
+ const isMain = (() => {
88
+ try {
89
+ const here = new URL(import.meta.url).pathname.toLowerCase();
90
+ const entry = process.argv[1]
91
+ ? new URL('file://' + process.argv[1].replace(/\\/g, '/')).pathname.toLowerCase()
92
+ : '';
93
+ return here === entry;
94
+ } catch { return false; }
95
+ })();
96
+
97
+ if (isMain) {
98
+ if (WANT_HELP) {
99
+ help();
100
+ process.exit(0);
101
+ }
102
+ const root = process.cwd();
103
+ (WANT_WATCH ? runLive(root) : runSnapshot(root)).catch((err) => {
104
+ process.stderr.write(`dashboard: ${err.message}\n`);
105
+ process.exit(1);
106
+ });
107
+ }