@geraldmaron/construct 1.0.12 → 1.0.15

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 (337) hide show
  1. package/README.md +14 -2
  2. package/bin/construct +262 -14
  3. package/bin/construct-postinstall.mjs +23 -2
  4. package/commands/build/feature.md +0 -6
  5. package/commands/build/fix.md +0 -6
  6. package/commands/design/access.md +0 -6
  7. package/commands/design/flow.md +0 -6
  8. package/commands/design/ui.md +0 -6
  9. package/commands/measure/experiment.md +0 -6
  10. package/commands/measure/metrics.md +0 -6
  11. package/commands/measure/results.md +0 -6
  12. package/commands/plan/api.md +0 -6
  13. package/commands/plan/challenge.md +0 -6
  14. package/commands/plan/decide.md +0 -6
  15. package/commands/plan/feature.md +0 -7
  16. package/commands/plan/requirements.md +0 -6
  17. package/commands/remember/context.md +0 -6
  18. package/commands/remember/handoff.md +0 -6
  19. package/commands/remember/runbook.md +0 -6
  20. package/commands/review/code.md +0 -6
  21. package/commands/review/quality.md +0 -6
  22. package/commands/review/security.md +0 -6
  23. package/commands/ship/ready.md +0 -6
  24. package/commands/ship/release.md +0 -6
  25. package/commands/ship/status.md +0 -6
  26. package/commands/understand/docs.md +0 -6
  27. package/commands/understand/research.md +0 -6
  28. package/commands/understand/this.md +0 -6
  29. package/commands/understand/why.md +0 -6
  30. package/commands/work/clean.md +0 -6
  31. package/commands/work/drive.md +0 -6
  32. package/commands/work/optimize-prompts.md +0 -6
  33. package/commands/work/parallel-review.md +0 -6
  34. package/lib/auto-docs.mjs +11 -7
  35. package/lib/beads-client.mjs +19 -1
  36. package/lib/comment-lint.mjs +5 -1
  37. package/lib/context-state.mjs +15 -3
  38. package/lib/contracts/validate.mjs +54 -10
  39. package/lib/contracts/violation-log.mjs +154 -0
  40. package/lib/dashboard-static.mjs +7 -4
  41. package/lib/doc-stamp.mjs +16 -0
  42. package/lib/docs-verify.mjs +1 -8
  43. package/lib/doctor/watchers/bd-watch.mjs +6 -2
  44. package/lib/document-extract/docling-client.mjs +114 -0
  45. package/lib/document-extract/docling-sidecar.py +122 -0
  46. package/lib/document-extract/whisper-client.mjs +79 -0
  47. package/lib/document-extract.mjs +73 -8
  48. package/lib/document-ingest.mjs +26 -4
  49. package/lib/embed/daemon.mjs +26 -1
  50. package/lib/embed/docs-lifecycle.mjs +19 -0
  51. package/lib/embed/inbox.mjs +85 -2
  52. package/lib/embed/recommendation-store.mjs +29 -0
  53. package/lib/flavors/loader.mjs +1 -1
  54. package/lib/gates-audit.mjs +18 -12
  55. package/lib/handoffs/contract.mjs +2 -2
  56. package/lib/hooks/_lib/input.mjs +52 -0
  57. package/lib/hooks/adaptive-lint.mjs +4 -0
  58. package/lib/hooks/agent-tracker.mjs +59 -15
  59. package/lib/hooks/audit-reads.mjs +83 -42
  60. package/lib/hooks/audit-trail.mjs +28 -18
  61. package/lib/hooks/bash-output-logger.mjs +8 -2
  62. package/lib/hooks/block-no-verify.mjs +4 -0
  63. package/lib/hooks/ci-status-check.mjs +4 -0
  64. package/lib/hooks/comment-lint.mjs +6 -4
  65. package/lib/hooks/config-protection.mjs +4 -0
  66. package/lib/hooks/context-watch.mjs +4 -0
  67. package/lib/hooks/context-window-recovery.mjs +4 -0
  68. package/lib/hooks/dep-audit.mjs +12 -5
  69. package/lib/hooks/doc-coupling-check.mjs +5 -1
  70. package/lib/hooks/edit-accumulator.mjs +25 -10
  71. package/lib/hooks/edit-error-recovery.mjs +4 -0
  72. package/lib/hooks/edit-guard.mjs +4 -0
  73. package/lib/hooks/guard-bash.mjs +4 -0
  74. package/lib/hooks/mcp-audit.mjs +4 -0
  75. package/lib/hooks/mcp-health-check.mjs +4 -0
  76. package/lib/hooks/model-fallback.mjs +7 -11
  77. package/lib/hooks/policy-engine.mjs +4 -0
  78. package/lib/hooks/post-merge-docs-check.mjs +4 -0
  79. package/lib/hooks/post-merge-tracking.mjs +82 -0
  80. package/lib/hooks/pre-compact.mjs +4 -0
  81. package/lib/hooks/pre-push-gate.mjs +84 -231
  82. package/lib/hooks/proactive-activation.mjs +5 -2
  83. package/lib/hooks/readme-age-check.mjs +4 -0
  84. package/lib/hooks/registry-sync.mjs +35 -20
  85. package/lib/hooks/rule-verifier.mjs +3 -0
  86. package/lib/hooks/scan-secrets.mjs +4 -0
  87. package/lib/hooks/session-optimize.mjs +4 -0
  88. package/lib/hooks/session-reflect.mjs +4 -0
  89. package/lib/hooks/session-start.mjs +49 -2
  90. package/lib/hooks/session-tracking-refresh.mjs +70 -0
  91. package/lib/hooks/stop-notify.mjs +13 -2
  92. package/lib/hooks/stop-typecheck.mjs +4 -0
  93. package/lib/hooks/test-watch.mjs +8 -4
  94. package/lib/ingest/chunker.mjs +94 -0
  95. package/lib/ingest/pipeline.mjs +53 -0
  96. package/lib/ingest/store.mjs +82 -0
  97. package/lib/init-unified.mjs +90 -23
  98. package/lib/intake/attribution.mjs +77 -0
  99. package/lib/intake/intake-config.mjs +3 -0
  100. package/lib/intake/manifest.mjs +107 -0
  101. package/lib/intake/poll-lock.mjs +136 -0
  102. package/lib/intake/prepare.mjs +42 -4
  103. package/lib/knowledge/search.mjs +12 -0
  104. package/lib/logging/rotate.mjs +394 -0
  105. package/lib/mcp/server.mjs +142 -1
  106. package/lib/mcp/tools/project.mjs +2 -2
  107. package/lib/mcp/tools/skills.mjs +6 -3
  108. package/lib/mcp/tools/telemetry.mjs +1 -1
  109. package/lib/mcp/tools/workflow.mjs +6 -2
  110. package/lib/observation-store.mjs +14 -5
  111. package/lib/ollama-manager.mjs +32 -28
  112. package/lib/opencode-config.mjs +10 -3
  113. package/lib/orchestration/routing-tables.mjs +176 -0
  114. package/lib/orchestration-policy.mjs +18 -106
  115. package/lib/parity.mjs +48 -7
  116. package/lib/profiles/lifecycle.mjs +19 -1
  117. package/lib/project-init-shared.mjs +11 -5
  118. package/lib/project-root.mjs +104 -0
  119. package/lib/prompt-composer.js +11 -1
  120. package/lib/roles/catalog.mjs +1 -1
  121. package/lib/roles/event-bus.mjs +29 -9
  122. package/lib/roles/router.mjs +8 -7
  123. package/lib/runtime/uv-bootstrap.mjs +129 -0
  124. package/lib/runtime/whisper-bootstrap.mjs +102 -0
  125. package/lib/server/index.mjs +53 -10
  126. package/lib/server/static/index.html +1 -15
  127. package/lib/specialists/postconditions.mjs +1 -1
  128. package/lib/status.mjs +1 -1
  129. package/lib/storage/backup.mjs +2 -2
  130. package/lib/sync/skill-frontmatter.mjs +113 -21
  131. package/lib/telemetry/intent-verifications.mjs +16 -3
  132. package/lib/telemetry/skill-calls.mjs +12 -3
  133. package/lib/tracking-surfaces.mjs +377 -0
  134. package/lib/validators/skills.mjs +86 -54
  135. package/lib/worker/trace.mjs +19 -2
  136. package/package.json +10 -6
  137. package/personas/construct.md +4 -7
  138. package/platforms/claude/settings.template.json +29 -28
  139. package/rules/common/beads-hygiene.md +3 -9
  140. package/rules/common/code-review.md +3 -6
  141. package/rules/common/coding-style.md +3 -6
  142. package/rules/common/comments.md +3 -7
  143. package/rules/common/commit-approval.md +3 -6
  144. package/rules/common/cx-agent-routing.md +3 -7
  145. package/rules/common/cx-skill-routing.md +3 -3
  146. package/rules/common/doc-ownership.md +3 -8
  147. package/rules/common/efficiency.md +3 -8
  148. package/rules/common/framing.md +3 -8
  149. package/rules/common/git-workflow.md +3 -5
  150. package/rules/common/no-fabrication.md +4 -12
  151. package/rules/common/patterns.md +3 -5
  152. package/rules/common/release-gates.md +3 -8
  153. package/rules/common/research.md +3 -8
  154. package/rules/common/review-before-change.md +3 -9
  155. package/rules/common/security.md +3 -6
  156. package/rules/common/skill-composition.md +3 -7
  157. package/rules/common/testing.md +3 -6
  158. package/rules/golang/coding-style.md +1 -5
  159. package/rules/golang/hooks.md +1 -5
  160. package/rules/golang/patterns.md +1 -5
  161. package/rules/golang/security.md +1 -5
  162. package/rules/golang/testing.md +1 -5
  163. package/rules/python/coding-style.md +1 -5
  164. package/rules/python/hooks.md +1 -5
  165. package/rules/python/patterns.md +1 -5
  166. package/rules/python/security.md +1 -5
  167. package/rules/python/testing.md +1 -5
  168. package/rules/swift/coding-style.md +1 -5
  169. package/rules/swift/hooks.md +1 -5
  170. package/rules/swift/patterns.md +1 -5
  171. package/rules/swift/security.md +1 -5
  172. package/rules/swift/testing.md +1 -5
  173. package/rules/typescript/coding-style.md +1 -5
  174. package/rules/typescript/hooks.md +1 -5
  175. package/rules/typescript/patterns.md +1 -5
  176. package/rules/typescript/security.md +1 -5
  177. package/rules/typescript/testing.md +1 -5
  178. package/rules/web/coding-style.md +3 -5
  179. package/rules/web/design-quality.md +3 -5
  180. package/rules/web/hooks.md +3 -5
  181. package/rules/web/patterns.md +3 -5
  182. package/rules/web/performance.md +3 -5
  183. package/rules/web/security.md +3 -5
  184. package/rules/web/testing.md +3 -5
  185. package/scripts/sync-specialists.mjs +269 -75
  186. package/skills/ai/agent-dev.md +4 -5
  187. package/skills/ai/llm-security.md +4 -5
  188. package/skills/ai/ml-ops.md +4 -5
  189. package/skills/ai/orchestration-workflow.md +4 -5
  190. package/skills/ai/prompt-and-eval.md +4 -5
  191. package/skills/ai/prompt-optimizer.md +4 -6
  192. package/skills/ai/rag-system.md +4 -5
  193. package/skills/architecture/api-design.md +4 -5
  194. package/skills/architecture/caching.md +4 -5
  195. package/skills/architecture/cloud-native.md +4 -5
  196. package/skills/architecture/message-queue.md +4 -5
  197. package/skills/architecture/security-arch.md +4 -5
  198. package/skills/compliance/ai-disclosure.md +4 -5
  199. package/skills/compliance/data-privacy.md +4 -5
  200. package/skills/compliance/license-audit.md +4 -5
  201. package/skills/compliance/regulatory-review.md +4 -5
  202. package/skills/development/cpp.md +4 -5
  203. package/skills/development/go.md +4 -5
  204. package/skills/development/java.md +4 -5
  205. package/skills/development/kotlin.md +4 -5
  206. package/skills/development/mobile-crossplatform.md +4 -5
  207. package/skills/development/python.md +4 -5
  208. package/skills/development/rust.md +4 -5
  209. package/skills/development/shell.md +4 -5
  210. package/skills/development/swift.md +4 -5
  211. package/skills/development/typescript.md +4 -5
  212. package/skills/devops/ci-cd.md +4 -5
  213. package/skills/devops/containerization.md +4 -5
  214. package/skills/devops/cost-optimization.md +4 -5
  215. package/skills/devops/data-engineering.md +4 -5
  216. package/skills/devops/database.md +4 -5
  217. package/skills/devops/dependency-management.md +4 -5
  218. package/skills/devops/devsecops.md +4 -5
  219. package/skills/devops/git-workflow.md +4 -5
  220. package/skills/devops/incident-response.md +4 -5
  221. package/skills/devops/monorepo.md +4 -5
  222. package/skills/devops/observability.md +4 -5
  223. package/skills/devops/performance.md +4 -5
  224. package/skills/devops/testing.md +4 -5
  225. package/skills/docs/adr-workflow.md +4 -7
  226. package/skills/docs/backlog-proposal-workflow.md +4 -3
  227. package/skills/docs/customer-profile-workflow.md +4 -3
  228. package/skills/docs/document-ingest-workflow.md +4 -3
  229. package/skills/docs/evidence-ingest-workflow.md +4 -3
  230. package/skills/docs/init-docs.md +4 -5
  231. package/skills/docs/init-project.md +4 -5
  232. package/skills/docs/prd-workflow.md +4 -7
  233. package/skills/docs/prfaq-workflow.md +4 -3
  234. package/skills/docs/product-intelligence-review.md +4 -3
  235. package/skills/docs/product-intelligence-workflow.md +4 -6
  236. package/skills/docs/product-signal-workflow.md +4 -5
  237. package/skills/docs/research-workflow.md +4 -5
  238. package/skills/docs/runbook-workflow.md +4 -5
  239. package/skills/docs/strategy-workflow.md +4 -5
  240. package/skills/exploration/dependency-graph-reading.md +4 -7
  241. package/skills/exploration/repo-map.md +4 -5
  242. package/skills/exploration/tracer-bullet-method.md +4 -7
  243. package/skills/exploration/unknown-codebase-onboarding.md +4 -7
  244. package/skills/frameworks/django.md +4 -5
  245. package/skills/frameworks/nextjs.md +4 -5
  246. package/skills/frameworks/react.md +4 -5
  247. package/skills/frameworks/spring-boot.md +4 -5
  248. package/skills/frontend-design/accessibility.md +4 -5
  249. package/skills/frontend-design/component-patterns.md +4 -5
  250. package/skills/frontend-design/engineering.md +4 -5
  251. package/skills/frontend-design/state-management.md +4 -5
  252. package/skills/frontend-design/ui-aesthetics.md +4 -5
  253. package/skills/frontend-design/ux-principles.md +4 -5
  254. package/skills/operating/change-management.md +4 -8
  255. package/skills/operating/incident-response.md +4 -8
  256. package/skills/operating/oncall-rotation.md +4 -7
  257. package/skills/operating/orchestration-reference.md +4 -10
  258. package/skills/quality-gates/review-work.md +4 -5
  259. package/skills/quality-gates/verify-change.md +4 -5
  260. package/skills/quality-gates/verify-module.md +4 -5
  261. package/skills/quality-gates/verify-quality.md +4 -5
  262. package/skills/quality-gates/verify-security.md +4 -5
  263. package/skills/roles/architect.ai-systems.md +6 -9
  264. package/skills/roles/architect.data.md +6 -9
  265. package/skills/roles/architect.enterprise.md +6 -9
  266. package/skills/roles/architect.integration.md +6 -9
  267. package/skills/roles/architect.md +7 -14
  268. package/skills/roles/architect.platform.md +6 -9
  269. package/skills/roles/data-analyst.experiment.md +6 -9
  270. package/skills/roles/data-analyst.md +6 -9
  271. package/skills/roles/data-analyst.product-intelligence.md +7 -9
  272. package/skills/roles/data-analyst.product.md +6 -9
  273. package/skills/roles/data-analyst.telemetry.md +7 -9
  274. package/skills/roles/data-engineer.pipeline.md +6 -9
  275. package/skills/roles/data-engineer.vector-retrieval.md +7 -9
  276. package/skills/roles/data-engineer.warehouse.md +6 -9
  277. package/skills/roles/debugger.md +6 -9
  278. package/skills/roles/designer.accessibility.md +6 -9
  279. package/skills/roles/designer.md +7 -9
  280. package/skills/roles/engineer.ai.md +6 -9
  281. package/skills/roles/engineer.data.md +6 -9
  282. package/skills/roles/engineer.md +9 -9
  283. package/skills/roles/engineer.platform.md +6 -9
  284. package/skills/roles/operator.docs.md +6 -9
  285. package/skills/roles/operator.md +9 -9
  286. package/skills/roles/operator.release.md +6 -9
  287. package/skills/roles/operator.sre.md +6 -9
  288. package/skills/roles/orchestrator.md +6 -9
  289. package/skills/roles/product-manager.ai-product.md +6 -9
  290. package/skills/roles/product-manager.business-strategy.md +6 -9
  291. package/skills/roles/product-manager.enterprise.md +6 -9
  292. package/skills/roles/product-manager.growth.md +7 -9
  293. package/skills/roles/product-manager.md +7 -9
  294. package/skills/roles/product-manager.platform.md +6 -9
  295. package/skills/roles/product-manager.product.md +6 -9
  296. package/skills/roles/qa.ai-eval.md +8 -9
  297. package/skills/roles/qa.api-contract.md +7 -9
  298. package/skills/roles/qa.data-pipeline.md +7 -9
  299. package/skills/roles/qa.md +7 -9
  300. package/skills/roles/qa.test-automation.md +6 -9
  301. package/skills/roles/qa.web-ui.md +7 -9
  302. package/skills/roles/researcher.explorer.md +6 -9
  303. package/skills/roles/researcher.md +8 -9
  304. package/skills/roles/researcher.ux.md +6 -9
  305. package/skills/roles/reviewer.devil-advocate.md +6 -9
  306. package/skills/roles/reviewer.evaluator.md +6 -9
  307. package/skills/roles/reviewer.md +9 -9
  308. package/skills/roles/reviewer.trace.md +6 -9
  309. package/skills/roles/security.ai.md +7 -9
  310. package/skills/roles/security.appsec.md +6 -9
  311. package/skills/roles/security.cloud.md +6 -9
  312. package/skills/roles/security.legal-compliance.md +6 -9
  313. package/skills/roles/security.md +7 -9
  314. package/skills/roles/security.privacy.md +7 -9
  315. package/skills/roles/security.supply-chain.md +7 -9
  316. package/skills/security/blue-team.md +4 -5
  317. package/skills/security/code-audit.md +4 -5
  318. package/skills/security/pentest.md +4 -5
  319. package/skills/security/red-team.md +4 -5
  320. package/skills/security/threat-intel.md +4 -5
  321. package/skills/security/vuln-research.md +4 -5
  322. package/skills/strategy/competitive-landscape.md +4 -8
  323. package/skills/strategy/market-research-methods.md +4 -8
  324. package/skills/strategy/narrative-arc.md +4 -8
  325. package/skills/strategy/pricing-positioning.md +4 -8
  326. package/skills/utility/clean-code.md +4 -5
  327. package/specialists/policy-inventory.json +14 -0
  328. package/specialists/registry.json +158 -13
  329. package/lib/hooks/env-check.mjs +0 -83
  330. package/lib/hooks/read-tracker.mjs +0 -61
  331. package/lib/policy/unified-gates.mjs +0 -96
  332. package/lib/server/static/assets/index-ab25c707.js +0 -70
  333. package/lib/server/static/assets/index-f0c80a2b.css +0 -1
  334. package/lib/specialist-contracts-enforce.mjs +0 -158
  335. package/rules/common/agents.md +0 -28
  336. package/rules/common/development-workflow.md +0 -32
  337. package/rules/common/performance.md +0 -55
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/review/code.md (Code review) correctness, regressions, security, test coverage
3
-
4
- Code review: correctness, regressions, security, test coverage
5
- -->
6
1
  ---
7
2
  description: "Code review: correctness, regressions, security, test coverage"
8
3
  ---
9
-
10
4
  You are Construct. Review: $ARGUMENTS
11
5
 
12
6
  If no target specified, review all uncommitted changes (`git diff HEAD`).
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/review/quality.md (Quality audit) complexity, naming, duplication, dead code, maintainability
3
-
4
- Quality audit: complexity, naming, duplication, dead code, maintainability
5
- -->
6
1
  ---
7
2
  description: "Quality audit: complexity, naming, duplication, dead code, maintainability"
8
3
  ---
9
-
10
4
  You are Construct. Audit quality for: $ARGUMENTS
11
5
 
12
6
  Check:
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/review/security.md (Security scan) secrets, auth, injection, data exposure, dependency risk
3
-
4
- Security scan: secrets, auth, injection, data exposure, dependency risk
5
- -->
6
1
  ---
7
2
  description: "Security scan: secrets, auth, injection, data exposure, dependency risk"
8
3
  ---
9
-
10
4
  You are Construct. Scan: $ARGUMENTS
11
5
 
12
6
  If no target specified, scan all uncommitted changes.
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/ship/ready.md (Pre-release check) is this ready to ship?
3
-
4
- Pre-release check: is this ready to ship?
5
- -->
6
1
  ---
7
2
  description: "Pre-release check: is this ready to ship?"
8
3
  ---
9
-
10
4
  You are Construct. Check if the following is ready: $ARGUMENTS
11
5
 
12
6
  Check:
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/ship/release.md (Release) plan rollout, changelog, rollback, and post-release verification
3
-
4
- Release: plan rollout, changelog, rollback, and post-release verification
5
- -->
6
1
  ---
7
2
  description: "Release: plan rollout, changelog, rollback, and post-release verification"
8
3
  ---
9
-
10
4
  You are Construct. Release the following: $ARGUMENTS
11
5
 
12
6
  Readiness checklist:
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/ship/status.md (Status) current project state, workflow, uncommitted changes, recent activity
3
-
4
- Status: current project state, workflow, uncommitted changes, recent activity
5
- -->
6
1
  ---
7
2
  description: "Status: current project state, workflow, uncommitted changes, recent activity"
8
3
  ---
9
-
10
4
  You are Construct. Report status for: $ARGUMENTS
11
5
 
12
6
  Check and report:
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/understand/docs.md (Look up documentation) current behavior from primary sources, not training memory
3
-
4
- Look up documentation: current behavior from primary sources, not training memory
5
- -->
6
1
  ---
7
2
  description: "Look up documentation: current behavior from primary sources, not training memory"
8
3
  ---
9
-
10
4
  You are Construct. Look up: $ARGUMENTS
11
5
 
12
6
  Do not answer from training knowledge for anything library-specific, version-specific, or API-specific. Search first.
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/understand/research.md. Research a topic. Verify facts from primary sources, separate evidence from inference.
3
-
4
- Research a topic. Verify facts from primary sources, separate evidence from inference.
5
- -->
6
1
  ---
7
2
  description: Research a topic. Verify facts from primary sources, separate evidence from inference.
8
3
  ---
9
-
10
4
  You are Construct. Research: $ARGUMENTS
11
5
 
12
6
  Source hierarchy: official docs → release notes/changelogs → source code → tracked issues → community resources.
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/understand/this.md (Explore and understand something) trace execution paths, map structure, gather evidence
3
-
4
- Explore and understand something: trace execution paths, map structure, gather evidence
5
- -->
6
1
  ---
7
2
  description: "Explore and understand something: trace execution paths, map structure, gather evidence"
8
3
  ---
9
-
10
4
  You are Construct. Explore and explain: $ARGUMENTS
11
5
 
12
6
  Gather evidence: do not propose solutions unless asked.
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/understand/why.md (Investigate a failure) why is this broken, what is the root cause
3
-
4
- Investigate a failure: why is this broken, what is the root cause
5
- -->
6
1
  ---
7
2
  description: "Investigate a failure: why is this broken, what is the root cause"
8
3
  ---
9
-
10
4
  You are Construct. Investigate: $ARGUMENTS
11
5
 
12
6
  1. Capture: exact error, stack trace, reproduction steps
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/work/clean.md (Remove AI-generated code smells) verbosity, hedging, dead comments, generic names
3
-
4
- Remove AI-generated code smells: verbosity, hedging, dead comments, generic names
5
- -->
6
1
  ---
7
2
  description: "Remove AI-generated code smells: verbosity, hedging, dead comments, generic names"
8
3
  ---
9
-
10
4
  You are cx-reviewer identifying AI slop in: $ARGUMENTS
11
5
 
12
6
  Then dispatch cx-engineer to apply the fixes.
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/work/drive.md (Full autonomous execution) explore, plan, implement, verify, loop until done
3
-
4
- Full autonomous execution: explore, plan, implement, verify, loop until done
5
- -->
6
1
  ---
7
2
  description: "Full autonomous execution: explore, plan, implement, verify, loop until done"
8
3
  ---
9
-
10
4
  You are Construct in drive mode. Execute $ARGUMENTS fully and autonomously without stopping for confirmation.
11
5
 
12
6
  ## Execution contract
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/work/optimize-prompts.md: Prompt optimization command.
3
-
4
- Runs the closed-loop prompt optimization workflow using telemetry trace data.
5
- -->
6
1
  ---
7
2
  description: "Closed-loop prompt optimization: read telemetry traces, diagnose failures, push improved version to staging"
8
3
  ---
9
-
10
4
  You are cx-trace-reviewer running a prompt optimization cycle for: $ARGUMENTS
11
5
 
12
6
  If $ARGUMENTS is empty, optimize all agents with median quality score below 0.65 in the past 7 days.
@@ -1,12 +1,6 @@
1
- <!--
2
- commands/work/parallel-review.md (Adversarial parallel review) 5 reviewers must agree before output ships
3
-
4
- Adversarial parallel review: 5 reviewers must agree before output ships
5
- -->
6
1
  ---
7
2
  description: "Adversarial parallel review: 5 reviewers must agree before output ships"
8
3
  ---
9
-
10
4
  You are Construct running a parallel adversarial review of: $ARGUMENTS
11
5
 
12
6
  Dispatch the following 5 review roles concurrently:
package/lib/auto-docs.mjs CHANGED
@@ -10,7 +10,8 @@
10
10
  * anything would change without writing files — the mode CI uses to detect drift.
11
11
  *
12
12
  * buildSite() is retained for compatibility; the public site now renders docs/
13
- * directly through Fumadocs and buildFumadocsReference() emits generated pages.
13
+ * directly through Next.js (apps/docs/) and buildFumadocsReference() emits
14
+ * generated reference Markdown into docs/reference/.
14
15
  */
15
16
 
16
17
  import fs from 'node:fs';
@@ -82,7 +83,7 @@ function buildCoreDocsContract() {
82
83
 
83
84
  const DIR_DESCRIPTIONS = {
84
85
  agents: 'Registry and generated platform adapter chains',
85
- apps: 'User-facing apps shipped from this repo (e.g. apps/docs/, the Fumadocs docs site)',
86
+ apps: 'User-facing apps shipped from this repo (e.g. apps/docs/, the Next.js docs site)',
86
87
  bin: 'CLI entrypoint (`construct`)',
87
88
  claude: 'Claude Code integration (agents, settings template)',
88
89
  commands: 'Command prompt assets',
@@ -151,7 +152,7 @@ function buildHooksTable(rootDir) {
151
152
  }
152
153
 
153
154
  function buildAgentsTable(rootDir) {
154
- const registryPath = path.join(rootDir, 'agents', 'registry.json');
155
+ const registryPath = path.join(rootDir, 'specialists', 'registry.json');
155
156
  let registry;
156
157
  try { registry = JSON.parse(fs.readFileSync(registryPath, 'utf8')); } catch { return ''; }
157
158
 
@@ -303,7 +304,7 @@ export function checkDocsCoverage({ rootDir } = {}) {
303
304
  return { covered, uncovered, total: covered.length + uncovered.length };
304
305
  }
305
306
 
306
- // Categories surfaced in the Fumadocs reference. Internal commands (e.g.,
307
+ // Categories surfaced in the generated CLI reference. Internal commands (e.g.,
307
308
  // `construct hook <name>`) are not user-facing reference material.
308
309
  const REFERENCE_CATEGORIES = [
309
310
  'Core',
@@ -407,9 +408,12 @@ function renderAgentsPage(rootDir) {
407
408
  }
408
409
 
409
410
  /**
410
- * Emit MDX reference pages into docs/reference/ for the Fumadocs site.
411
- * Mirrors the data sources behind AUTO regions and buildSite() but writes
412
- * Markdown files Fumadocs can render directly (no MkDocs intermediate).
411
+ * Emit MDX reference pages into docs/reference/ for the Next.js docs site
412
+ * in apps/docs/. Mirrors the data sources behind AUTO regions and buildSite()
413
+ * but writes Markdown files the docs app can pull in directly (no MkDocs
414
+ * intermediate). Name kept for backwards compatibility with callers that
415
+ * still import buildFumadocsReference; the rendering layer is now plain
416
+ * Next.js + @next/mdx.
413
417
  */
414
418
  export function buildFumadocsReference({ rootDir } = {}) {
415
419
  rootDir = rootDir ?? process.cwd();
@@ -28,6 +28,21 @@ import {
28
28
  cleanupStaleQueue,
29
29
  } from './beads-lock.mjs';
30
30
 
31
+ // Anchored bead-ID format. IDs reaching the bd shell boundary must match this;
32
+ // regex-extracted IDs from PR bodies, agent transcripts, or intake packets get
33
+ // re-validated here so a malformed value cannot reach the database. spawnSync
34
+ // uses argv-array form so shell injection isn't the failure mode — log
35
+ // corruption from a bogus ID is.
36
+
37
+ const BEAD_ID_RE = /^[a-z]+-[a-z0-9]+$/;
38
+
39
+ export function assertBeadId(id) {
40
+ if (typeof id !== 'string' || !BEAD_ID_RE.test(id)) {
41
+ throw new Error(`bd: invalid bead id ${JSON.stringify(id)} — expected prefix-suffix slug`);
42
+ }
43
+ return id;
44
+ }
45
+
31
46
  import {
32
47
  concurrentRead,
33
48
  optimisticWrite,
@@ -253,7 +268,7 @@ async function runWithLegacyLock(args, opts, cwd, commandDesc) {
253
268
  if (!lock) {
254
269
  // Failed to get lock; add to queue if allowed
255
270
  const maxQueueWaitMs = opts.maxQueueWaitSeconds * 1000;
256
- if (Date.now() - startWait + maxQueueWaitMs > maxQueueWaitMs) {
271
+ if (Date.now() - startWait > maxQueueWaitMs) {
257
272
  return {
258
273
  success: false,
259
274
  error: `Timeout waiting for lock after ${opts.timeoutSeconds}s`,
@@ -365,6 +380,7 @@ export async function listIssues(options = {}) {
365
380
  * Uses concurrent read (no locking needed).
366
381
  */
367
382
  export async function showIssue(id, options = {}) {
383
+ assertBeadId(id);
368
384
  return runBdJson(['show', id], { useOptimisticLocking: true, ...options });
369
385
  }
370
386
 
@@ -373,6 +389,7 @@ export async function showIssue(id, options = {}) {
373
389
  * Uses optimistic locking for better concurrency.
374
390
  */
375
391
  export async function claimIssue(id, options = {}) {
392
+ assertBeadId(id);
376
393
  // Use the specialized optimistic claim function
377
394
  const result = await claimBeadOptimistic(id, options);
378
395
 
@@ -388,6 +405,7 @@ export async function claimIssue(id, options = {}) {
388
405
  * Close an issue.
389
406
  */
390
407
  export async function closeIssue(id, options = {}) {
408
+ assertBeadId(id);
391
409
  return runBd(['close', id], options);
392
410
  }
393
411
 
@@ -79,11 +79,15 @@ function requiresHeader(rel) {
79
79
 
80
80
  const JS_HEADER_RE = /^(?:#![^\n]*\n)?(?:\/\/[^\n]*\n)*\/\*\*[\s\S]*?\*\//;
81
81
  const MD_HEADER_RE = /^<!--[\s\S]*?-->/;
82
+ const MD_YAML_HEADER_RE = /^---\n(?:[^\n]*\n)*?description:[\s\S]*?\n---/;
82
83
  const SH_HEADER_RE = /^#!.*\n(?:#[^\n]*\n)*/;
83
84
 
84
85
  function hasHeader(content, type) {
85
86
  if (type === 'js') return JS_HEADER_RE.test(content.trimStart());
86
- if (type === 'md') return MD_HEADER_RE.test(content.trimStart());
87
+ if (type === 'md') {
88
+ const trimmed = content.trimStart();
89
+ return MD_HEADER_RE.test(trimmed) || MD_YAML_HEADER_RE.test(trimmed);
90
+ }
87
91
  return SH_HEADER_RE.test(content.trimStart());
88
92
  }
89
93
 
@@ -1,8 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * lib/context-state.mjs — compact project/session context persistence.
4
+ *
5
+ * Writes go through writeContextState, which uses write-to-tmp + atomic
6
+ * rename so concurrent writers from different hooks (pre-compact, Stop,
7
+ * context-window-recovery, tracking-surfaces) cannot leave a half-written
8
+ * JSON or markdown file behind. POSIX rename(2) is atomic for files on
9
+ * the same filesystem; the .cx directory always lives next to the target.
4
10
  */
5
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
11
+ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
6
12
  import { join } from 'node:path';
7
13
 
8
14
  export function contextJsonPath(rootDir) {
@@ -58,6 +64,12 @@ export function inspectContextState(rootDir) {
58
64
  };
59
65
  }
60
66
 
67
+ function atomicWrite(targetPath, content) {
68
+ const tmpPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
69
+ writeFileSync(tmpPath, content, 'utf8');
70
+ renameSync(tmpPath, targetPath);
71
+ }
72
+
61
73
  export function writeContextState(rootDir, state, { markdown = null } = {}) {
62
74
  const cxDir = join(rootDir, '.cx');
63
75
  mkdirSync(cxDir, { recursive: true });
@@ -67,10 +79,10 @@ export function writeContextState(rootDir, state, { markdown = null } = {}) {
67
79
  savedAt: new Date().toISOString(),
68
80
  ...state,
69
81
  };
70
- writeFileSync(contextJsonPath(rootDir), `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
82
+ atomicWrite(contextJsonPath(rootDir), `${JSON.stringify(payload, null, 2)}\n`);
71
83
 
72
84
  if (markdown !== null) {
73
- writeFileSync(contextMarkdownPath(rootDir), markdown.endsWith('\n') ? markdown : `${markdown}\n`, 'utf8');
85
+ atomicWrite(contextMarkdownPath(rootDir), markdown.endsWith('\n') ? markdown : `${markdown}\n`);
74
86
  }
75
87
 
76
88
  return payload;
@@ -19,6 +19,8 @@
19
19
  import { existsSync, readFileSync } from 'node:fs';
20
20
  import { join, dirname, resolve } from 'node:path';
21
21
  import { fileURLToPath } from 'node:url';
22
+ import { POSTCONDITIONS, validateBinaryPostconditions } from '../specialists/postconditions.mjs';
23
+ import { logViolation } from './violation-log.mjs';
22
24
 
23
25
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
24
26
  const CONTRACTS_PATH = join(REPO_ROOT, 'specialists', 'contracts.json');
@@ -145,28 +147,62 @@ export function findContract({ producer, consumer, id, contractsPath = CONTRACTS
145
147
  * { ok: true, contract }
146
148
  * { ok: false, status: 'BLOCKED_CONTRACT', errors[], contract }
147
149
  *
148
- * Enforcement defaults to the value of process.env.CONSTRUCT_CONTRACT_ENFORCEMENT
149
- * ('warn' or 'block'), and may be overridden by the caller. In warn mode the
150
- * result includes errors but ok stays true so a session is not blocked.
150
+ * Enforcement defaults to `block` (CISA Secure-by-Design pledge —
151
+ * strong-default configurations). Callers can pass `enforcement: 'warn'`
152
+ * explicitly per-call when they genuinely need advisory mode.
153
+ *
154
+ * Binary postconditions are enforced when the producer has a rule table in
155
+ * `lib/specialists/postconditions.mjs`. The check is self-enforcing: if a
156
+ * producer has rules and the caller does not pass a `packet`, the call is
157
+ * itself a contract violation (MCP best-practice 2025 — no silent skip on
158
+ * tool-argument omission).
151
159
  */
152
160
  export function validateHandoff({
153
161
  producer,
154
162
  consumer,
155
163
  id,
156
164
  artifact,
165
+ packet,
157
166
  contractsPath = CONTRACTS_PATH,
158
167
  repoRoot = REPO_ROOT,
159
- enforcement = process.env.CONSTRUCT_CONTRACT_ENFORCEMENT || 'warn',
168
+ enforcement = 'block',
160
169
  }) {
170
+ const errors = [];
171
+ const postconditionFailures = [];
172
+
173
+ // Binary postconditions are producer-bound invariants that fire regardless
174
+ // of whether the producer→consumer contract is registered in contracts.json.
175
+ // A producer with rules cannot have its packet validation skipped by
176
+ // claiming an unregistered consumer.
177
+ const hasBinaryRules = Array.isArray(POSTCONDITIONS[producer]) && POSTCONDITIONS[producer].length > 0;
178
+ if (hasBinaryRules) {
179
+ if (packet === undefined || packet === null) {
180
+ errors.push(`producer '${producer}' has binary postconditions; validateHandoff must be called with a packet argument`);
181
+ } else {
182
+ const binary = validateBinaryPostconditions(producer, packet);
183
+ for (const failure of binary.failures) {
184
+ errors.push(`[${failure.id}] ${failure.reason}`);
185
+ postconditionFailures.push(failure);
186
+ }
187
+ }
188
+ }
189
+
161
190
  const contract = findContract({ producer, consumer, id, contractsPath });
162
191
  if (!contract) {
192
+ if (errors.length > 0) {
193
+ logViolation(`${producer}->${consumer}`, 'output', errors, packet ?? artifact, {
194
+ verdict: postconditionFailures.length > 0 ? 'BLOCKED_CONTRACT' : 'CONTRACT_VIOLATION',
195
+ postconditionFailures,
196
+ });
197
+ return enforcement === 'block'
198
+ ? { ok: false, status: 'BLOCKED_CONTRACT', errors: [`no contract found for ${producer}→${consumer}${id ? ` id=${id}` : ''}`, ...errors], contract: null }
199
+ : { ok: true, warnings: [`no contract found for ${producer}→${consumer}`, ...errors], contract: null };
200
+ }
163
201
  return enforcement === 'block'
164
202
  ? { ok: false, status: 'BLOCKED_CONTRACT', errors: [`no contract found for ${producer}→${consumer}${id ? ` id=${id}` : ''}`], contract: null }
165
203
  : { ok: true, warnings: [`no contract found for ${producer}→${consumer}`], contract: null };
166
204
  }
167
205
 
168
- const errors = [];
169
-
170
206
  // A handoff carries a producer's output, which is the consumer's input. The
171
207
  // input contract is what the consumer expects to receive; check mustContain
172
208
  // against that first. Output.schema validation only applies when the artifact
@@ -193,18 +229,26 @@ export function validateHandoff({
193
229
  }
194
230
  }
195
231
 
196
- // Postcondition checks run against the artifact file on disk when the
197
- // handoff envelope declares `artifactPath`. Resolved relative to repoRoot.
232
+ // Disk-artifact postconditions: frontmatter, sections, citations.
198
233
  const declaredArtifactPath = artifact?.artifactPath || artifact?.output?.artifactPath;
199
234
  if (declaredArtifactPath) {
200
235
  const absPath = isAbsolutePath(declaredArtifactPath)
201
236
  ? declaredArtifactPath
202
237
  : join(repoRoot, declaredArtifactPath);
203
- const pcErrors = validatePostconditions({ contract, artifactPath: absPath });
238
+ const pcErrors = validateArtifactPostconditions({ contract, artifactPath: absPath });
204
239
  for (const e of pcErrors) errors.push(e);
205
240
  }
206
241
 
207
242
  if (errors.length === 0) return { ok: true, contract };
243
+
244
+ // Persist to the chain-hashed violation log so `construct doctor` and
245
+ // forensic replay see the failure. Best-effort; logging never throws.
246
+ const verdict = postconditionFailures.length > 0 ? 'BLOCKED_CONTRACT' : 'CONTRACT_VIOLATION';
247
+ logViolation(contract.id, 'output', errors, packet ?? artifact, {
248
+ verdict,
249
+ postconditionFailures,
250
+ });
251
+
208
252
  if (enforcement === 'block') return { ok: false, status: 'BLOCKED_CONTRACT', errors, contract };
209
253
  return { ok: true, warnings: errors, contract };
210
254
  }
@@ -272,7 +316,7 @@ function claimsHaveCitations(body) {
272
316
  * disk. Postconditions can be strings (descriptive, ignored here) or objects
273
317
  * with { id, check, field?, section? }. Returns an array of error strings.
274
318
  */
275
- export function validatePostconditions({ contract, artifactPath }) {
319
+ export function validateArtifactPostconditions({ contract, artifactPath }) {
276
320
  if (!contract?.postconditions?.length) return [];
277
321
  if (!artifactPath || !existsSync(artifactPath)) {
278
322
  const structured = contract.postconditions.filter((p) => typeof p === 'object');
@@ -0,0 +1,154 @@
1
+ /**
2
+ * lib/contracts/violation-log.mjs — tamper-evident append-only log for
3
+ * specialist-contract violations.
4
+ *
5
+ * Each record carries:
6
+ * ts — ISO timestamp
7
+ * sequence — monotonic per-log counter, gap-detects truncation
8
+ * prev_line_hash — sha256 of the prior line, gap-detects modification
9
+ * agent — last-known active agent (from ~/.cx/last-agent.json)
10
+ * contractId — the contract that fired the violation
11
+ * direction — 'input' | 'output'
12
+ * missing — field names that failed shape validation
13
+ * packet_keys — Object.keys(packet) when packet is an object
14
+ * verdict — 'CONTRACT_VIOLATION' (default) or 'BLOCKED_CONTRACT'
15
+ * (binary postcondition failures)
16
+ * postconditionFailures — [{id, reason}] when verdict is BLOCKED_CONTRACT
17
+ *
18
+ * The hash chain spans rotation: when the active file is empty (post-rotate),
19
+ * the tail is read from the most recent segment (gzipped or plain) via
20
+ * readLastLineAcrossSegments. The sequence counter follows the same path.
21
+ *
22
+ * Single owner of the file. All readers go through recentViolations or
23
+ * verifyChain; all writers go through logViolation.
24
+ */
25
+
26
+ import { existsSync, mkdirSync, readFileSync } from 'node:fs';
27
+ import { join, dirname } from 'node:path';
28
+ import { homedir } from 'node:os';
29
+ import { createHash } from 'node:crypto';
30
+ import { appendBounded, readLastLineAcrossSegments } from '../logging/rotate.mjs';
31
+ import { resolveProjectScopedPath } from '../project-root.mjs';
32
+
33
+ const CX_DIR = join(homedir(), '.cx');
34
+ const LAST_AGENT = join(CX_DIR, 'last-agent.json');
35
+
36
+ // contract-violations are PROJECT-SCOPED: resolves to
37
+ // <project>/.cx/contract-violations.jsonl when inside a project, falling
38
+ // back to ~/.cx for standalone invocations. Resolved on every call so
39
+ // cwd/HOME changes inside the same process (tests, harness reuse) route
40
+ // correctly.
41
+
42
+ function logFile() {
43
+ return resolveProjectScopedPath('contract-violations.jsonl', { ensureDir: false });
44
+ }
45
+
46
+ function sha256(input) { return createHash('sha256').update(input).digest('hex'); }
47
+
48
+ function readLastAgent() {
49
+ try { return JSON.parse(readFileSync(LAST_AGENT, 'utf8'))?.agent || 'construct'; }
50
+ catch { return 'construct'; }
51
+ }
52
+
53
+ function readTailRecord() {
54
+ const file = logFile();
55
+ const lastLine = readLastLineAcrossSegments(file);
56
+ if (!lastLine) return null;
57
+ try { return JSON.parse(lastLine); }
58
+ catch { return null; }
59
+ }
60
+
61
+ function readPrevLineHash() {
62
+ const file = logFile();
63
+ const lastLine = readLastLineAcrossSegments(file);
64
+ return lastLine ? sha256(lastLine) : null;
65
+ }
66
+
67
+ function nextSequence() {
68
+ const tail = readTailRecord();
69
+ const prior = Number.isInteger(tail?.sequence) ? tail.sequence : 0;
70
+ return prior + 1;
71
+ }
72
+
73
+ /**
74
+ * Append a violation record. Best-effort: file I/O failures are swallowed
75
+ * so logging never crashes the caller.
76
+ */
77
+ export function logViolation(contractId, direction, missing, packet, extra = {}) {
78
+ try {
79
+ const file = logFile();
80
+ mkdirSync(dirname(file), { recursive: true });
81
+ const record = {
82
+ ts: new Date().toISOString(),
83
+ sequence: nextSequence(),
84
+ agent: readLastAgent(),
85
+ contractId,
86
+ direction,
87
+ missing,
88
+ packet_keys: packet && typeof packet === 'object' ? Object.keys(packet) : null,
89
+ prev_line_hash: readPrevLineHash(),
90
+ ...extra,
91
+ };
92
+ appendBounded('contract-violations', file, JSON.stringify(record) + '\n');
93
+ } catch { /* logging is best-effort */ }
94
+ }
95
+
96
+ /**
97
+ * Read recent violations from the active log segment. Returns oldest-first.
98
+ * Rotated segments are intentionally not included — this is the doctor
99
+ * surface, scoped to the active window.
100
+ */
101
+ export function recentViolations({ windowMs = 24 * 60 * 60 * 1000 } = {}) {
102
+ const file = logFile();
103
+ if (!existsSync(file)) return [];
104
+ try {
105
+ const cutoff = Date.now() - windowMs;
106
+ return readFileSync(file, 'utf8')
107
+ .trim()
108
+ .split('\n')
109
+ .filter(Boolean)
110
+ .map((line) => { try { return JSON.parse(line); } catch { return null; } })
111
+ .filter((r) => r && new Date(r.ts).getTime() >= cutoff);
112
+ } catch { return []; }
113
+ }
114
+
115
+ /**
116
+ * Walk the active log segment, asserting:
117
+ * - each record's prev_line_hash matches sha256 of the prior line
118
+ * - sequence numbers are monotonic with no gaps
119
+ *
120
+ * Returns { ok, brokenAt? } where brokenAt is { index, reason } on first
121
+ * failure. An empty / missing log is `{ ok: true }` (nothing to break).
122
+ */
123
+ export function verifyChain() {
124
+ const file = logFile();
125
+ if (!existsSync(file)) return { ok: true };
126
+ try {
127
+ const lines = readFileSync(file, 'utf8').trim().split('\n').filter(Boolean);
128
+ let priorLine = null;
129
+ let priorSeq = null;
130
+ for (let i = 0; i < lines.length; i++) {
131
+ const line = lines[i];
132
+ let record;
133
+ try { record = JSON.parse(line); }
134
+ catch { return { ok: false, brokenAt: { index: i, reason: 'unparseable JSON' } }; }
135
+
136
+ if (priorLine !== null) {
137
+ const expectedHash = sha256(priorLine);
138
+ if (record.prev_line_hash !== expectedHash) {
139
+ return { ok: false, brokenAt: { index: i, reason: 'prev_line_hash mismatch' } };
140
+ }
141
+ }
142
+ if (Number.isInteger(record.sequence)) {
143
+ if (priorSeq !== null && record.sequence !== priorSeq + 1) {
144
+ return { ok: false, brokenAt: { index: i, reason: `sequence gap (expected ${priorSeq + 1}, got ${record.sequence})` } };
145
+ }
146
+ priorSeq = record.sequence;
147
+ }
148
+ priorLine = line;
149
+ }
150
+ return { ok: true };
151
+ } catch (err) {
152
+ return { ok: false, brokenAt: { index: -1, reason: err.message } };
153
+ }
154
+ }
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * lib/dashboard-static.mjs — build and sync the dashboard static bundle.
3
3
  *
4
- * Keeps `dashboard/dist/` and `lib/server/static/` aligned so the HTTP server
5
- * serves the current dashboard build from repo-owned assets.
4
+ * Keeps the Next.js static export at `apps/dashboard/out/` and the served
5
+ * tree at `lib/server/static/` aligned so the HTTP server picks up the
6
+ * current dashboard build without code changes. Pre-2.0 the dashboard
7
+ * lived at `dashboard/dist/`; the rebuild to Next.js (commit 84cd721)
8
+ * moved the source of truth into `apps/dashboard/out/`.
6
9
  */
7
10
 
8
11
  import fs from 'node:fs';
@@ -78,8 +81,8 @@ function removeEmptyDirs(dirPath, stopAt) {
78
81
  export function getDashboardPaths({ rootDir = process.cwd() } = {}) {
79
82
  return {
80
83
  rootDir,
81
- dashboardDir: path.join(rootDir, 'dashboard'),
82
- sourceDir: path.join(rootDir, 'dashboard', 'dist'),
84
+ dashboardDir: path.join(rootDir, 'apps', 'dashboard'),
85
+ sourceDir: path.join(rootDir, 'apps', 'dashboard', 'out'),
83
86
  targetDir: path.join(rootDir, 'lib', 'server', 'static'),
84
87
  };
85
88
  }