@geraldmaron/construct 1.0.4 → 1.0.6

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 (293) hide show
  1. package/README.md +57 -44
  2. package/agents/prompts/cx-accessibility.md +3 -3
  3. package/agents/prompts/cx-ai-engineer.md +7 -7
  4. package/agents/prompts/cx-architect.md +6 -6
  5. package/agents/prompts/cx-business-strategist.md +6 -6
  6. package/agents/prompts/cx-data-analyst.md +7 -7
  7. package/agents/prompts/cx-data-engineer.md +3 -3
  8. package/agents/prompts/cx-debugger.md +6 -6
  9. package/agents/prompts/cx-designer.md +5 -5
  10. package/agents/prompts/cx-devil-advocate.md +4 -4
  11. package/agents/prompts/cx-docs-keeper.md +5 -5
  12. package/agents/prompts/cx-engineer.md +5 -5
  13. package/agents/prompts/cx-evaluator.md +2 -2
  14. package/agents/prompts/cx-explorer.md +8 -8
  15. package/agents/prompts/cx-legal-compliance.md +2 -2
  16. package/agents/prompts/cx-operations.md +5 -5
  17. package/agents/prompts/cx-orchestrator.md +10 -10
  18. package/agents/prompts/cx-platform-engineer.md +5 -5
  19. package/agents/prompts/cx-product-manager.md +4 -4
  20. package/agents/prompts/cx-qa.md +8 -8
  21. package/agents/prompts/cx-rd-lead.md +9 -9
  22. package/agents/prompts/cx-release-manager.md +5 -5
  23. package/agents/prompts/cx-researcher.md +22 -22
  24. package/agents/prompts/cx-reviewer.md +7 -7
  25. package/agents/prompts/cx-security.md +10 -10
  26. package/agents/prompts/cx-sre.md +7 -7
  27. package/agents/prompts/cx-test-automation.md +3 -3
  28. package/agents/prompts/cx-trace-reviewer.md +8 -8
  29. package/agents/prompts/cx-ux-researcher.md +3 -3
  30. package/bin/construct +470 -4
  31. package/commands/build/feature.md +4 -4
  32. package/commands/build/fix.md +8 -8
  33. package/commands/design/access.md +3 -3
  34. package/commands/design/flow.md +3 -3
  35. package/commands/design/ui.md +4 -4
  36. package/commands/measure/experiment.md +5 -5
  37. package/commands/measure/metrics.md +3 -3
  38. package/commands/measure/results.md +4 -4
  39. package/commands/plan/api.md +3 -3
  40. package/commands/plan/challenge.md +3 -3
  41. package/commands/plan/decide.md +3 -3
  42. package/commands/plan/feature.md +7 -7
  43. package/commands/plan/requirements.md +3 -3
  44. package/commands/remember/context.md +5 -5
  45. package/commands/remember/handoff.md +3 -3
  46. package/commands/remember/runbook.md +3 -3
  47. package/commands/review/code.md +8 -8
  48. package/commands/review/quality.md +4 -4
  49. package/commands/review/security.md +3 -3
  50. package/commands/ship/ready.md +3 -3
  51. package/commands/ship/release.md +3 -3
  52. package/commands/ship/status.md +4 -4
  53. package/commands/understand/docs.md +3 -3
  54. package/commands/understand/research.md +5 -3
  55. package/commands/understand/this.md +4 -4
  56. package/commands/understand/why.md +8 -8
  57. package/commands/work/clean.md +14 -14
  58. package/commands/work/drive.md +10 -10
  59. package/commands/work/optimize-prompts.md +9 -9
  60. package/commands/work/parallel-review.md +8 -8
  61. package/db/schema/006_graph.sql +24 -0
  62. package/examples/provider-plugin/README.md +7 -7
  63. package/examples/seed-observations/README.md +6 -6
  64. package/examples/seed-observations/anti-patterns.md +14 -14
  65. package/examples/seed-observations/decisions.md +4 -4
  66. package/examples/seed-observations/patterns.md +14 -14
  67. package/lib/auto-docs.mjs +10 -5
  68. package/lib/cli-commands.mjs +45 -1
  69. package/lib/comment-lint.mjs +7 -1
  70. package/lib/config/schema.mjs +3 -0
  71. package/lib/flavors/loader.mjs +136 -0
  72. package/lib/hooks/agent-tracker.mjs +22 -3
  73. package/lib/hooks/pre-push-gate.mjs +14 -1
  74. package/lib/hooks/session-optimize.mjs +3 -2
  75. package/lib/hooks/session-reflect.mjs +68 -0
  76. package/lib/init-unified.mjs +25 -2
  77. package/lib/intake/classify.mjs +61 -183
  78. package/lib/intake/prepare.mjs +7 -0
  79. package/lib/intake/tables/creative.mjs +94 -0
  80. package/lib/intake/tables/operations.mjs +85 -0
  81. package/lib/intake/tables/research.mjs +85 -0
  82. package/lib/intake/tables/rnd.mjs +175 -0
  83. package/lib/knowledge/graph.mjs +213 -0
  84. package/lib/knowledge/research-store.mjs +109 -0
  85. package/lib/mcp/server.mjs +187 -1
  86. package/lib/mcp/tools/profile.mjs +270 -0
  87. package/lib/observation-store.mjs +19 -0
  88. package/lib/outcomes/aggregate.mjs +104 -0
  89. package/lib/outcomes/record.mjs +115 -0
  90. package/lib/parity.mjs +6 -9
  91. package/lib/profiles/lifecycle.mjs +388 -0
  92. package/lib/profiles/loader.mjs +123 -0
  93. package/lib/profiles/validate-custom.mjs +114 -0
  94. package/lib/reflect/extractor.mjs +193 -0
  95. package/lib/reflect.mjs +89 -2
  96. package/lib/sandbox.mjs +102 -0
  97. package/package.json +6 -1
  98. package/personas/construct.md +20 -20
  99. package/platforms/claude/CLAUDE.md +6 -6
  100. package/platforms/claude/settings.template.json +13 -0
  101. package/rules/common/agents.md +2 -2
  102. package/rules/common/beads-hygiene.md +11 -11
  103. package/rules/common/code-review.md +1 -1
  104. package/rules/common/coding-style.md +1 -1
  105. package/rules/common/comments.md +8 -8
  106. package/rules/common/commit-approval.md +4 -4
  107. package/rules/common/cx-agent-routing.md +2 -2
  108. package/rules/common/cx-skill-routing.md +2 -2
  109. package/rules/common/development-workflow.md +1 -1
  110. package/rules/common/doc-ownership.md +2 -2
  111. package/rules/common/efficiency.md +3 -3
  112. package/rules/common/framing.md +1 -1
  113. package/rules/common/git-workflow.md +1 -1
  114. package/rules/common/patterns.md +1 -1
  115. package/rules/common/performance.md +1 -1
  116. package/rules/common/release-gates.md +7 -7
  117. package/rules/common/research.md +4 -4
  118. package/rules/common/security.md +1 -1
  119. package/rules/common/skill-composition.md +8 -8
  120. package/rules/common/testing.md +1 -1
  121. package/rules/golang/coding-style.md +2 -2
  122. package/rules/golang/hooks.md +1 -1
  123. package/rules/golang/patterns.md +1 -1
  124. package/rules/golang/security.md +1 -1
  125. package/rules/golang/testing.md +1 -1
  126. package/rules/python/coding-style.md +1 -1
  127. package/rules/python/hooks.md +1 -1
  128. package/rules/python/patterns.md +1 -1
  129. package/rules/python/security.md +1 -1
  130. package/rules/python/testing.md +1 -1
  131. package/rules/swift/coding-style.md +3 -3
  132. package/rules/swift/hooks.md +2 -2
  133. package/rules/swift/patterns.md +2 -2
  134. package/rules/swift/security.md +4 -4
  135. package/rules/swift/testing.md +2 -2
  136. package/rules/typescript/coding-style.md +1 -1
  137. package/rules/typescript/hooks.md +1 -1
  138. package/rules/typescript/patterns.md +1 -1
  139. package/rules/typescript/security.md +1 -1
  140. package/rules/typescript/testing.md +1 -1
  141. package/rules/web/coding-style.md +1 -1
  142. package/rules/web/design-quality.md +1 -1
  143. package/rules/web/hooks.md +1 -1
  144. package/rules/web/patterns.md +1 -1
  145. package/rules/web/performance.md +1 -1
  146. package/rules/web/security.md +1 -1
  147. package/rules/web/testing.md +1 -1
  148. package/scripts/sync-agents.mjs +11 -0
  149. package/skills/ai/agent-dev.md +1 -1
  150. package/skills/ai/llm-security.md +1 -1
  151. package/skills/ai/ml-ops.md +6 -6
  152. package/skills/ai/orchestration-workflow.md +1 -1
  153. package/skills/ai/prompt-and-eval.md +1 -1
  154. package/skills/ai/prompt-optimizer.md +13 -13
  155. package/skills/ai/rag-system.md +1 -1
  156. package/skills/architecture/api-design.md +1 -1
  157. package/skills/architecture/caching.md +1 -1
  158. package/skills/architecture/cloud-native.md +1 -1
  159. package/skills/architecture/message-queue.md +1 -1
  160. package/skills/architecture/security-arch.md +1 -1
  161. package/skills/compliance/ai-disclosure.md +1 -1
  162. package/skills/compliance/data-privacy.md +1 -1
  163. package/skills/compliance/license-audit.md +2 -2
  164. package/skills/compliance/regulatory-review.md +1 -1
  165. package/skills/development/cpp.md +1 -1
  166. package/skills/development/go.md +1 -1
  167. package/skills/development/java.md +1 -1
  168. package/skills/development/kotlin.md +9 -9
  169. package/skills/development/mobile-crossplatform.md +13 -13
  170. package/skills/development/python.md +1 -1
  171. package/skills/development/rust.md +1 -1
  172. package/skills/development/shell.md +1 -1
  173. package/skills/development/swift.md +6 -6
  174. package/skills/development/typescript.md +1 -1
  175. package/skills/devops/ci-cd.md +5 -5
  176. package/skills/devops/containerization.md +9 -9
  177. package/skills/devops/cost-optimization.md +1 -1
  178. package/skills/devops/data-engineering.md +2 -2
  179. package/skills/devops/database.md +1 -1
  180. package/skills/devops/dependency-management.md +3 -3
  181. package/skills/devops/devsecops.md +1 -1
  182. package/skills/devops/git-workflow.md +1 -1
  183. package/skills/devops/incident-response.md +18 -18
  184. package/skills/devops/monorepo.md +5 -5
  185. package/skills/devops/observability.md +1 -1
  186. package/skills/devops/performance.md +1 -1
  187. package/skills/devops/testing.md +1 -1
  188. package/skills/docs/adr-workflow.md +2 -2
  189. package/skills/docs/backlog-proposal-workflow.md +1 -1
  190. package/skills/docs/customer-profile-workflow.md +1 -1
  191. package/skills/docs/document-ingest-workflow.md +1 -1
  192. package/skills/docs/evidence-ingest-workflow.md +1 -1
  193. package/skills/docs/init-docs.md +15 -15
  194. package/skills/docs/init-project.md +1 -1
  195. package/skills/docs/prd-workflow.md +3 -3
  196. package/skills/docs/prfaq-workflow.md +1 -1
  197. package/skills/docs/product-intelligence-review.md +1 -1
  198. package/skills/docs/product-intelligence-workflow.md +1 -1
  199. package/skills/docs/product-signal-workflow.md +9 -9
  200. package/skills/docs/research-workflow.md +10 -10
  201. package/skills/docs/runbook-workflow.md +2 -2
  202. package/skills/docs/strategy-workflow.md +3 -3
  203. package/skills/exploration/repo-map.md +11 -11
  204. package/skills/frameworks/django.md +15 -15
  205. package/skills/frameworks/nextjs.md +16 -16
  206. package/skills/frameworks/react.md +12 -12
  207. package/skills/frameworks/spring-boot.md +12 -12
  208. package/skills/frontend-design/accessibility.md +6 -6
  209. package/skills/frontend-design/component-patterns.md +1 -1
  210. package/skills/frontend-design/engineering.md +1 -1
  211. package/skills/frontend-design/state-management.md +1 -1
  212. package/skills/frontend-design/ui-aesthetics.md +1 -1
  213. package/skills/frontend-design/ux-principles.md +1 -1
  214. package/skills/operating/orchestration-reference.md +27 -27
  215. package/skills/quality-gates/review-work.md +3 -3
  216. package/skills/quality-gates/verify-change.md +1 -1
  217. package/skills/quality-gates/verify-module.md +1 -1
  218. package/skills/quality-gates/verify-quality.md +1 -1
  219. package/skills/quality-gates/verify-security.md +1 -1
  220. package/skills/roles/architect.ai-systems.md +4 -2
  221. package/skills/roles/architect.data.md +4 -2
  222. package/skills/roles/architect.enterprise.md +4 -2
  223. package/skills/roles/architect.integration.md +4 -2
  224. package/skills/roles/architect.md +7 -5
  225. package/skills/roles/architect.platform.md +4 -2
  226. package/skills/roles/data-analyst.experiment.md +4 -2
  227. package/skills/roles/data-analyst.md +9 -7
  228. package/skills/roles/data-analyst.product-intelligence.md +4 -2
  229. package/skills/roles/data-analyst.product.md +4 -2
  230. package/skills/roles/data-analyst.telemetry.md +4 -2
  231. package/skills/roles/data-engineer.pipeline.md +4 -2
  232. package/skills/roles/data-engineer.vector-retrieval.md +4 -2
  233. package/skills/roles/data-engineer.warehouse.md +4 -2
  234. package/skills/roles/debugger.md +7 -5
  235. package/skills/roles/designer.accessibility.md +4 -2
  236. package/skills/roles/designer.md +10 -8
  237. package/skills/roles/engineer.ai.md +4 -2
  238. package/skills/roles/engineer.data.md +5 -3
  239. package/skills/roles/engineer.md +14 -12
  240. package/skills/roles/engineer.platform.md +5 -3
  241. package/skills/roles/operator.docs.md +6 -4
  242. package/skills/roles/operator.md +6 -4
  243. package/skills/roles/operator.release.md +4 -2
  244. package/skills/roles/operator.sre.md +5 -3
  245. package/skills/roles/orchestrator.md +5 -3
  246. package/skills/roles/product-manager.ai-product.md +4 -2
  247. package/skills/roles/product-manager.business-strategy.md +4 -2
  248. package/skills/roles/product-manager.enterprise.md +4 -2
  249. package/skills/roles/product-manager.growth.md +4 -2
  250. package/skills/roles/product-manager.md +6 -4
  251. package/skills/roles/product-manager.platform.md +4 -2
  252. package/skills/roles/product-manager.product.md +4 -2
  253. package/skills/roles/qa.ai-eval.md +4 -2
  254. package/skills/roles/qa.api-contract.md +4 -2
  255. package/skills/roles/qa.data-pipeline.md +4 -2
  256. package/skills/roles/qa.md +7 -5
  257. package/skills/roles/qa.test-automation.md +5 -3
  258. package/skills/roles/qa.web-ui.md +4 -2
  259. package/skills/roles/researcher.explorer.md +4 -2
  260. package/skills/roles/researcher.md +11 -9
  261. package/skills/roles/researcher.ux.md +4 -2
  262. package/skills/roles/reviewer.devil-advocate.md +4 -2
  263. package/skills/roles/reviewer.evaluator.md +4 -2
  264. package/skills/roles/reviewer.md +14 -12
  265. package/skills/roles/reviewer.trace.md +4 -2
  266. package/skills/roles/security.ai.md +4 -2
  267. package/skills/roles/security.appsec.md +4 -2
  268. package/skills/roles/security.cloud.md +4 -2
  269. package/skills/roles/security.legal-compliance.md +4 -2
  270. package/skills/roles/security.md +7 -5
  271. package/skills/roles/security.privacy.md +4 -2
  272. package/skills/roles/security.supply-chain.md +4 -2
  273. package/skills/routing.md +14 -14
  274. package/skills/security/blue-team.md +1 -1
  275. package/skills/security/code-audit.md +1 -1
  276. package/skills/security/pentest.md +1 -1
  277. package/skills/security/red-team.md +1 -1
  278. package/skills/security/threat-intel.md +1 -1
  279. package/skills/security/vuln-research.md +1 -1
  280. package/skills/utility/clean-code.md +2 -2
  281. package/templates/docs/changelog-entry.md +1 -1
  282. package/templates/docs/construct_guide.md +13 -13
  283. package/templates/docs/meta-prd.md +16 -16
  284. package/templates/docs/one-pager.md +1 -1
  285. package/templates/docs/persona-artifact.md +36 -0
  286. package/templates/docs/prd-business.md +1 -1
  287. package/templates/docs/prd-platform.md +1 -1
  288. package/templates/docs/prd.md +17 -17
  289. package/templates/docs/research-brief.md +8 -8
  290. package/templates/docs/research-finding.md +26 -0
  291. package/templates/docs/rfc.md +1 -1
  292. package/templates/docs/skill-artifact.md +27 -0
  293. package/templates/docs/strategy.md +1 -1
@@ -1,4 +1,4 @@
1
- You have inherited enough flaky test suites to know that bad automation is worse than no automation it creates false confidence while hiding real failures. The test that passes intermittently isn't catching bugs; it's teaching the team to ignore red builds.
1
+ You have inherited enough flaky test suites to know that bad automation is worse than no automation: it creates false confidence while hiding real failures. The test that passes intermittently isn't catching bugs; it's teaching the team to ignore red builds.
2
2
 
3
3
  **What you're instinctively suspicious of:**
4
4
  - Tests that pass intermittently and are dismissed as "infrastructure issues"
@@ -7,7 +7,7 @@ You have inherited enough flaky test suites to know that bad automation is worse
7
7
  - Tests with arbitrary sleeps instead of deterministic waits
8
8
  - "The test infrastructure is complex" as an explanation for low coverage
9
9
 
10
- **Your productive tension**: cx-qa QA plans the verification strategy; you implement it, and you know when the planned approach is untestable as written
10
+ **Your productive tension**: cx-qa: QA plans the verification strategy; you implement it, and you know when the planned approach is untestable as written
11
11
 
12
12
  **Your opening question**: Is this test deterministic, and does it actually fail when the behavior it's testing breaks?
13
13
 
@@ -17,7 +17,7 @@ You have inherited enough flaky test suites to know that bad automation is worse
17
17
 
18
18
  Your scope: designing and implementing automated test suites (unit, integration, E2E, contract, visual regression, load), test framework selection and configuration, flaky test diagnosis, CI/CD test pipeline optimization, test data management, coverage tooling, and test parallelization.
19
19
 
20
- You are distinct from cx-qa (who owns test strategy, planning, and quality assurance methodology) you own the automation implementation and infrastructure that executes that strategy.
20
+ You are distinct from cx-qa (who owns test strategy, planning, and quality assurance methodology): you own the automation implementation and infrastructure that executes that strategy.
21
21
 
22
22
  When given a task:
23
23
  1. Understand the existing test infrastructure before proposing new frameworks
@@ -1,4 +1,4 @@
1
- You track whether agents are actually performing in production not in demos, not in cherry-picked examples, but in the distribution of real usage. Trace patterns reveal what prompt reviews miss: the degradation that's invisible until you look at variance, the agent that's fine on median but catastrophically wrong on the 30th percentile.
1
+ You track whether agents are actually performing in production: not in demos, not in cherry-picked examples, but in the distribution of real usage. Trace patterns reveal what prompt reviews miss: the degradation that's invisible until you look at variance, the agent that's fine on median but catastrophically wrong on the 30th percentile.
2
2
 
3
3
  **What you're instinctively suspicious of:**
4
4
  - Stable median scores that hide high-variance agents
@@ -7,7 +7,7 @@ You track whether agents are actually performing in production — not in demos,
7
7
  - "The prompt looks fine" without checking actual trace behavior
8
8
  - Agents with no quality scoring that haven't been reviewed
9
9
 
10
- **Your productive tension**: cx-ai-engineer AI engineer optimizes individual prompts; you see the fleet-level patterns and the agents that need attention
10
+ **Your productive tension**: cx-ai-engineer: AI engineer optimizes individual prompts; you see the fleet-level patterns and the agents that need attention
11
11
 
12
12
  **Your opening question**: Which agents have degraded since the last cycle, and what does the trace evidence actually say about why?
13
13
 
@@ -15,11 +15,11 @@ You track whether agents are actually performing in production — not in demos,
15
15
 
16
16
  **Role guidance**: call `get_skill("roles/reviewer.trace")` before drafting.
17
17
 
18
- You support pluggable trace backends (configured via CONSTRUCT_TRACE_BACKEND env var). All trace access goes through the configured backend adapter do not hardcode provider-specific API calls without checking CONSTRUCT_TRACE_BACKEND first.
18
+ You support pluggable trace backends (configured via CONSTRUCT_TRACE_BACKEND env var). All trace access goes through the configured backend adapter: do not hardcode provider-specific API calls without checking CONSTRUCT_TRACE_BACKEND first.
19
19
 
20
20
  Backend: Telemetry (`CONSTRUCT_TELEMETRY_URL`, `CONSTRUCT_TELEMETRY_PUBLIC_KEY`, `CONSTRUCT_TELEMETRY_SECRET_KEY`)
21
21
 
22
- ## Step 1 Triage
22
+ ## Step 1: Triage
23
23
 
24
24
  Fetch recent quality scores across all agents via the configured backend:
25
25
  GET {CONSTRUCT_TELEMETRY_URL}/api/public/scores?name=quality&limit=200
@@ -27,16 +27,16 @@ Fetch recent quality scores across all agents via the configured backend:
27
27
  Group by agent name (extracted from trace metadata). Compute median per agent. Flag any agent with:
28
28
  - Median quality score < 0.65 over the past 7 days
29
29
  - Downward trend (last-7-day median worse than prior-7-day median by more than 0.05)
30
- - High variance (stddev > 0.25) indicates inconsistent behavior
30
+ - High variance (stddev > 0.25): indicates inconsistent behavior
31
31
 
32
- ## Step 2 Deep analysis per flagged agent
32
+ ## Step 2: Deep analysis per flagged agent
33
33
 
34
34
  For each flagged agent, fetch its low-scoring traces (score < 0.7) and high-scoring traces (score > 0.8) as a contrast pair. Extract:
35
35
  - What inputs correlate with low scores?
36
36
  - What tool usage patterns appear in high vs low traces?
37
37
  - What output characteristics are present in high-scoring traces that are absent in low-scoring ones?
38
38
 
39
- ## Step 3 Optimization cycle
39
+ ## Step 3: Optimization cycle
40
40
 
41
41
  Follow skills/ai/prompt-optimizer.md. For each agent meeting the minimum trace threshold (20+):
42
42
  1. Read current production prompt from agents/registry.json
@@ -45,7 +45,7 @@ Follow skills/ai/prompt-optimizer.md. For each agent meeting the minimum trace t
45
45
  4. Log staging candidate via cx_trace with promptVersion attribute
46
46
  5. Record the optimization in .cx/decisions/
47
47
 
48
- ## Step 4 Promotion decisions
48
+ ## Step 4: Promotion decisions
49
49
 
50
50
  For agents with staging versions that have accumulated 20+ new traces:
51
51
  - Compare staging vs production median quality scores
@@ -7,14 +7,14 @@ You have watched enough users fail to know that what they say they want and what
7
7
  - "Users will figure it out" as a UX strategy
8
8
  - Assumptions in the product brief that were never tested against real behavior
9
9
 
10
- **Your productive tension**: cx-designer designers have visual ideas; you insist on behavioral grounding before the design locks
10
+ **Your productive tension**: cx-designer: designers have visual ideas; you insist on behavioral grounding before the design locks
11
11
 
12
- **Your opening question**: Who specifically is the user, what is their actual context, and what would success feel like to them not to us?
12
+ **Your opening question**: Who specifically is the user, what is their actual context, and what would success feel like to them: not to us?
13
13
 
14
14
  **Failure mode warning**: If your brief has no friction points, you haven't talked to users. Every product has places where users get stuck.
15
15
 
16
16
  **Role guidance**: call `get_skill("roles/researcher.ux")` before drafting.
17
- **Evidence policy**: for any external claims (benchmark data, published studies, platform statistics), follow `rules/common/research.md` most-recent-first, primary sources, verified URLs. UX findings based on direct user observation are primary evidence; stated preferences and self-reported data are secondary.
17
+ **Evidence policy**: for any external claims (benchmark data, published studies, platform statistics), follow `rules/common/research.md`: most-recent-first, primary sources, verified URLs. UX findings based on direct user observation are primary evidence; stated preferences and self-reported data are secondary.
18
18
 
19
19
  Produce a UX brief:
20
20
 
package/bin/construct CHANGED
@@ -186,14 +186,49 @@ function runNodeScript(scriptPath, args = [], extraEnv = {}) {
186
186
  async function cmdStatus() {
187
187
  const jsonOutput = restArgsCache.includes('--json');
188
188
  const status = await buildStatus({ rootDir: ROOT_DIR, cwd: process.cwd(), homeDir: HOME, env: process.env });
189
-
189
+
190
190
  if (jsonOutput) {
191
191
  println(JSON.stringify(status, null, 2));
192
192
  return;
193
193
  }
194
-
194
+
195
195
  println(formatStatusReport(status));
196
-
196
+
197
+ // Append the learning-loops summary so the operator gets a single answer
198
+ // to "is this thing actually learning?" without remembering a second
199
+ // command. Cheap: just reads .cx/observations + .cx/outcomes summaries.
200
+ try {
201
+ const { resolveActiveProfile } = await import('../lib/profiles/loader.mjs');
202
+ const { readSummary } = await import('../lib/outcomes/aggregate.mjs');
203
+ const fs = await import('node:fs');
204
+ const path = await import('node:path');
205
+ const cwd = process.cwd();
206
+ const active = resolveActiveProfile(cwd);
207
+ const obsIdxPath = path.join(cwd, '.cx', 'observations', 'index.json');
208
+ let obsTotal = 0;
209
+ let obs24h = 0;
210
+ if (fs.existsSync(obsIdxPath)) {
211
+ const idx = JSON.parse(fs.readFileSync(obsIdxPath, 'utf8'));
212
+ obsTotal = idx.length;
213
+ const since = Date.now() - 24 * 60 * 60 * 1000;
214
+ obs24h = idx.filter((e) => Date.parse(e.createdAt) >= since).length;
215
+ }
216
+ const researchDir = path.join(cwd, '.cx', 'knowledge', 'external', 'research');
217
+ const researchCount = fs.existsSync(researchDir)
218
+ ? fs.readdirSync(researchDir).filter((f) => f.endsWith('.md')).length
219
+ : 0;
220
+ const summary = readSummary(cwd);
221
+ const outcomeRoles = summary?.roles ? Object.keys(summary.roles).length : 0;
222
+
223
+ println('');
224
+ println('Learning loops');
225
+ println('──────────────');
226
+ println(` Profile ${active.id} (${active.displayName || ''})`);
227
+ println(` Observations ${obsTotal} total, ${obs24h} in last 24h`);
228
+ println(` Research files ${researchCount}`);
229
+ println(` Outcome roles ${outcomeRoles}`);
230
+ println(` Detail: npm run learning:status`);
231
+ } catch { /* best-effort surface; never block status */ }
197
232
  }
198
233
 
199
234
  async function cmdShow() {
@@ -3233,6 +3268,27 @@ async function cmdHook(args) {
3233
3268
  const [command, ...rest] = process.argv.slice(2);
3234
3269
  const restArgsCache = rest;
3235
3270
 
3271
+ // Parse `--key=value` and repeatable `--source-url=<url>` for `construct knowledge add`.
3272
+ function parseKnowledgeAddArgs(args) {
3273
+ const opts = { slug: null, topic: null, confidence: 'inferred', sources: [] };
3274
+ for (const arg of args) {
3275
+ if (arg.startsWith('--slug=')) opts.slug = arg.slice('--slug='.length);
3276
+ else if (arg.startsWith('--topic=')) opts.topic = arg.slice('--topic='.length);
3277
+ else if (arg.startsWith('--confidence=')) opts.confidence = arg.slice('--confidence='.length);
3278
+ else if (arg.startsWith('--source-url=')) opts.sources.push({ url: arg.slice('--source-url='.length) });
3279
+ else if (arg.startsWith('--source=')) { /* tag, ignored for now */ }
3280
+ }
3281
+ return opts;
3282
+ }
3283
+
3284
+ // Read all of stdin as a UTF-8 string. Returns '' if nothing was piped.
3285
+ async function readStdin() {
3286
+ if (process.stdin.isTTY) return '';
3287
+ let raw = '';
3288
+ for await (const chunk of process.stdin) raw += chunk;
3289
+ return raw;
3290
+ }
3291
+
3236
3292
  const handlers = new Map([
3237
3293
  // Core
3238
3294
  ['dev', cmdUp],
@@ -3268,6 +3324,24 @@ const handlers = new Map([
3268
3324
  ['handoffs', cmdHandoffs],
3269
3325
  ['headhunt', cmdHeadhunt],
3270
3326
  ['init', cmdInit],
3327
+ ['drop', async (args) => {
3328
+ const { runDropCli } = await import('../lib/drop.mjs');
3329
+ return runDropCli(args, { cwd: process.cwd(), env: process.env });
3330
+ }],
3331
+ ['docs', async (args) => {
3332
+ const sub = args[0];
3333
+ if (!sub || sub === '--help' || sub === '-h') {
3334
+ println('Usage: construct docs <check|verify|update|site>');
3335
+ return;
3336
+ }
3337
+ const rest = args.slice(1);
3338
+ if (sub === 'verify') return cmdDocsVerify(rest);
3339
+ if (sub === 'update') return cmdDocsUpdate(rest);
3340
+ if (sub === 'check') return cmdDocsCheck(rest);
3341
+ if (sub === 'site') return cmdDocsSite();
3342
+ errorln(`Unknown docs subcommand: ${sub}. Available: check, verify, update, site`);
3343
+ process.exit(1);
3344
+ }],
3271
3345
  ['docs:verify', cmdDocsVerify],
3272
3346
  ['init:update', cmdInitUpdate],
3273
3347
  ['models', cmdModels],
@@ -3833,7 +3907,399 @@ const handlers = new Map([
3833
3907
  }
3834
3908
  return;
3835
3909
  }
3836
- errorln(`Unknown knowledge subcommand: ${sub}. Available: trends, index`);
3910
+ if (sub === 'add') {
3911
+ const opts = parseKnowledgeAddArgs(args.slice(1));
3912
+ if (!opts.slug || !opts.topic) {
3913
+ errorln('Usage: construct knowledge add --source=research --slug=<id> --topic="..." [--confidence=confirmed|inferred|weak] [--source-url=<url>] (body on stdin)');
3914
+ process.exit(1);
3915
+ }
3916
+ const body = await readStdin();
3917
+ if (!body || body.trim().length < 10) {
3918
+ errorln('construct knowledge add: stdin body is empty or too short');
3919
+ process.exit(1);
3920
+ }
3921
+ const { addResearchFinding } = await import('../lib/knowledge/research-store.mjs');
3922
+ try {
3923
+ const { path: outPath, bytes } = await addResearchFinding({
3924
+ cwd: process.cwd(),
3925
+ slug: opts.slug,
3926
+ topic: opts.topic,
3927
+ body,
3928
+ confidence: opts.confidence,
3929
+ sources: opts.sources,
3930
+ });
3931
+ process.stdout.write(`✓ wrote ${outPath} (${bytes} bytes)\n`);
3932
+ return;
3933
+ } catch (err) {
3934
+ errorln(`construct knowledge add: ${err.message}`);
3935
+ process.exit(1);
3936
+ }
3937
+ }
3938
+ errorln(`Unknown knowledge subcommand: ${sub}. Available: trends, index, add`);
3939
+ process.exit(1);
3940
+ }],
3941
+ ['sandbox', async (args) => {
3942
+ const sub = args[0] || 'create';
3943
+ const { createSandbox, listSandboxes, deleteSandbox, pruneSandboxes } = await import('../lib/sandbox.mjs');
3944
+ if (sub === 'create') {
3945
+ const profileArg = args.find((a) => a.startsWith('--profile='));
3946
+ const profile = profileArg ? profileArg.split('=')[1] : null;
3947
+ const { id, path: p } = createSandbox({ profile });
3948
+ process.stdout.write(`✓ created sandbox ${id}\n path: ${p}\n`);
3949
+ if (profile) process.stdout.write(` profile: ${profile}\n`);
3950
+ return;
3951
+ }
3952
+ if (sub === 'list') {
3953
+ const all = listSandboxes();
3954
+ if (all.length === 0) { process.stdout.write(' (no sandboxes)\n'); return; }
3955
+ for (const s of all) process.stdout.write(` ${s.id.padEnd(30)} ${s.path}\n`);
3956
+ return;
3957
+ }
3958
+ if (sub === 'delete') {
3959
+ const id = args[1];
3960
+ if (!id) { errorln('Usage: construct sandbox delete <id>'); process.exit(1); }
3961
+ process.stdout.write(deleteSandbox(id) ? `✓ removed ${id}\n` : `not found: ${id}\n`);
3962
+ return;
3963
+ }
3964
+ if (sub === 'prune') {
3965
+ const daysArg = args.find((a) => a.startsWith('--days='));
3966
+ const days = daysArg ? Number(daysArg.split('=')[1]) || 7 : 7;
3967
+ const removed = pruneSandboxes({ olderThanDays: days });
3968
+ process.stdout.write(`✓ pruned ${removed} sandbox${removed === 1 ? '' : 'es'} older than ${days} day${days === 1 ? '' : 's'}\n`);
3969
+ return;
3970
+ }
3971
+ errorln(`Unknown sandbox subcommand: ${sub}. Available: create, list, delete, prune`);
3972
+ process.exit(1);
3973
+ }],
3974
+ ['profile', async (args) => {
3975
+ const sub = args[0] || 'show';
3976
+ const { listProfiles, loadProfile, resolveActiveProfile } = await import('../lib/profiles/loader.mjs');
3977
+ if (sub === 'list') {
3978
+ for (const id of listProfiles()) {
3979
+ const p = loadProfile(id);
3980
+ process.stdout.write(` ${id.padEnd(14)} ${p?.displayName || ''}\n`);
3981
+ }
3982
+ return;
3983
+ }
3984
+ if (sub === 'show') {
3985
+ const active = resolveActiveProfile(process.cwd());
3986
+ process.stdout.write(JSON.stringify({
3987
+ id: active.id,
3988
+ displayName: active.displayName,
3989
+ tagline: active.tagline,
3990
+ roles: active.roles?.length || 0,
3991
+ intakeTypes: active.intake?.types?.length || 0,
3992
+ }, null, 2) + '\n');
3993
+ return;
3994
+ }
3995
+ if (sub === 'set') {
3996
+ const id = args[1];
3997
+ if (!id) {
3998
+ errorln('Usage: construct profile set <id> [--yes] [--dry-run]');
3999
+ process.exit(1);
4000
+ }
4001
+ const target = loadProfile(id);
4002
+ if (!target) {
4003
+ errorln(`Unknown profile: ${id}. Available: ${listProfiles().join(', ')}`);
4004
+ process.exit(1);
4005
+ }
4006
+ const skipConfirm = args.includes('--yes') || args.includes('-y') || args.includes('--non-interactive');
4007
+ const dryRun = args.includes('--dry-run');
4008
+ const isTTY = process.stdin.isTTY && process.stdout.isTTY;
4009
+
4010
+ const { findProjectConfigPath, loadProjectConfig, writeProjectConfig, PROJECT_CONFIG_FILENAME } = await import('../lib/config/project-config.mjs');
4011
+ const pathMod = await import('node:path');
4012
+ const found = findProjectConfigPath(process.cwd());
4013
+ const cfgPath = found || pathMod.join(process.cwd(), PROJECT_CONFIG_FILENAME);
4014
+ // loadProjectConfig returns a wrapper { path, raw, config, source, errors };
4015
+ // operate on the raw user-authored object so the write call sees the
4016
+ // version field that validateProjectConfig requires.
4017
+ const loaded = found ? loadProjectConfig(process.cwd()) : null;
4018
+ const cfg = loaded?.raw ? structuredClone(loaded.raw) : { version: 1 };
4019
+ if (!cfg.version) cfg.version = 1;
4020
+
4021
+ const currentId = cfg.profile || 'rnd';
4022
+ if (currentId === id) {
4023
+ println(`Profile already set to ${id} (${target.displayName}). Nothing to do.`);
4024
+ return;
4025
+ }
4026
+ const current = loadProfile(currentId) || { id: currentId, displayName: currentId, roles: [], intake: { types: [], stages: [] }, departments: [] };
4027
+
4028
+ // Render a structural diff so the operator sees what loop they are switching into.
4029
+ const setDiff = (label, before, after) => {
4030
+ const b = new Set(before || []);
4031
+ const a = new Set(after || []);
4032
+ const added = [...a].filter((x) => !b.has(x));
4033
+ const removed = [...b].filter((x) => !a.has(x));
4034
+ const unchanged = [...a].filter((x) => b.has(x));
4035
+ println(` ${label}:`);
4036
+ if (added.length) println(` + ${added.join(', ')}`);
4037
+ if (removed.length) println(` - ${removed.join(', ')}`);
4038
+ if (!added.length && !removed.length) println(` (no change, ${unchanged.length} entr${unchanged.length === 1 ? 'y' : 'ies'})`);
4039
+ };
4040
+
4041
+ println('');
4042
+ println(`About to switch active profile:`);
4043
+ println(` from: ${currentId} (${current.displayName})`);
4044
+ println(` to: ${id} (${target.displayName})`);
4045
+ println('');
4046
+ println(`Structural diff:`);
4047
+ setDiff('roles', current.roles, target.roles);
4048
+ setDiff('intake.types', current.intake?.types, target.intake?.types);
4049
+ setDiff('intake.stages', current.intake?.stages, target.intake?.stages);
4050
+ const cDept = (current.departments || []).map((d) => d.id);
4051
+ const tDept = (target.departments || []).map((d) => d.id);
4052
+ setDiff('departments', cDept, tDept);
4053
+ println('');
4054
+ println(`Files that will change:`);
4055
+ println(` ${pathMod.relative(process.cwd(), cfgPath)} (profile field: ${currentId} -> ${id})`);
4056
+ println('');
4057
+ println(`The change only writes the profile field. No specialists are added or removed.`);
4058
+ println(`Sync runs against the new profile on next \`construct sync\`.`);
4059
+ println('');
4060
+
4061
+ if (dryRun) {
4062
+ println(`[dry-run] No files were written.`);
4063
+ return;
4064
+ }
4065
+ if (!skipConfirm && isTTY) {
4066
+ const readline = await import('node:readline');
4067
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
4068
+ const answer = await new Promise((res) => rl.question(`Switch profile to ${id}? (Y/n) `, (a) => res(a.trim().toLowerCase())));
4069
+ rl.close();
4070
+ if (answer && answer !== 'y' && answer !== 'yes') {
4071
+ println('Aborted. No config was changed.');
4072
+ return;
4073
+ }
4074
+ }
4075
+
4076
+ cfg.profile = id;
4077
+ writeProjectConfig(cfgPath, cfg, { validate: true });
4078
+ process.stdout.write(`✓ profile set to ${id} (${target.displayName})\n`);
4079
+ return;
4080
+ }
4081
+ if (sub === 'create') {
4082
+ const id = args[1];
4083
+ if (!id) {
4084
+ errorln('Usage: construct profile create <id> [--display="..."] [--role=<id>...] [--department=<id>:<displayName>...] [--yes]');
4085
+ errorln(' Run with no flags for interactive mode; pass --yes to skip the confirmation prompt.');
4086
+ process.exit(1);
4087
+ }
4088
+ if (!/^[a-z][a-z0-9-]{1,30}$/.test(id)) {
4089
+ errorln(`Invalid id "${id}". Must match ^[a-z][a-z0-9-]{1,30}$.`);
4090
+ process.exit(1);
4091
+ }
4092
+
4093
+ const displayArg = args.find((a) => a.startsWith('--display='));
4094
+ const skipConfirm = args.includes('--yes') || args.includes('-y') || args.includes('--non-interactive');
4095
+ const dryRun = args.includes('--dry-run');
4096
+ const isTTY = process.stdin.isTTY && process.stdout.isTTY;
4097
+
4098
+ let displayName = displayArg ? displayArg.split('=')[1] : null;
4099
+ let seedRoles = args
4100
+ .filter((a) => a.startsWith('--role='))
4101
+ .map((a) => a.slice('--role='.length))
4102
+ .filter(Boolean);
4103
+ let seedDepartments = args
4104
+ .filter((a) => a.startsWith('--department='))
4105
+ .map((a) => a.slice('--department='.length))
4106
+ .map((spec) => {
4107
+ const [deptId, deptDisplay] = spec.split(':');
4108
+ return { id: deptId, displayName: deptDisplay || deptId };
4109
+ });
4110
+
4111
+ // Interactive gather. Only prompt when stdin is a real terminal and the
4112
+ // operator did not pass enough flags to scaffold without input. A
4113
+ // non-TTY invocation with no flags falls through to an empty scaffold
4114
+ // so scripts and CI can still create drafts non-interactively.
4115
+ const needsInteractive = isTTY && !skipConfirm && (!displayName || (seedRoles.length === 0 && seedDepartments.length === 0));
4116
+ if (needsInteractive) {
4117
+ const readline = await import('node:readline');
4118
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
4119
+ const ask = (q, def = '') => new Promise((res) => rl.question(`${q}${def ? ` [${def}]` : ''} `, (a) => res(a.trim() || def)));
4120
+
4121
+ println('');
4122
+ println(`Scaffolding a new profile draft. Three questions, then a preview.`);
4123
+ println(`Background: docs/concepts/persona-research.md and docs/concepts/profile-lifecycle.md`);
4124
+ println('');
4125
+ if (!displayName) displayName = await ask('Display name?', id);
4126
+ if (seedDepartments.length === 0) {
4127
+ const dInput = await ask('Departments (id:Name, id:Name, ...)?', '');
4128
+ if (dInput) seedDepartments = dInput.split(',').map((s) => s.trim()).filter(Boolean).map((spec) => {
4129
+ const [dId, dName] = spec.split(':').map((x) => x.trim());
4130
+ return { id: dId, displayName: dName || dId };
4131
+ });
4132
+ }
4133
+ if (seedRoles.length === 0) {
4134
+ const rInput = await ask('Roles (role-id, role-id, ...)?', '');
4135
+ if (rInput) seedRoles = rInput.split(',').map((s) => s.trim()).filter(Boolean);
4136
+ }
4137
+ rl.close();
4138
+ }
4139
+ if (!displayName) displayName = id;
4140
+
4141
+ const pathMod = await import('node:path');
4142
+ const draftRoot = pathMod.join(process.cwd(), '.cx', 'profiles', `draft-${id}`);
4143
+
4144
+ println('');
4145
+ println(`About to scaffold a draft profile:`);
4146
+ println(` id: ${id}`);
4147
+ println(` displayName: ${displayName}`);
4148
+ println(` departments: ${seedDepartments.length}${seedDepartments.length ? ` (${seedDepartments.map((d) => d.id).join(', ')})` : ''}`);
4149
+ println(` roles: ${seedRoles.length}${seedRoles.length ? ` (${seedRoles.join(', ')})` : ''}`);
4150
+ println('');
4151
+ println(`Files that will be written under ${pathMod.relative(process.cwd(), draftRoot)}/:`);
4152
+ println(` profile.json structural manifest`);
4153
+ println(` requirements.md research brief naming each cx-* specialist`);
4154
+ for (const r of seedRoles) println(` personas/${r}.md`.padEnd(40) + ` persona artifact (filled during discover)`);
4155
+ for (const d of seedDepartments) println(` departments/${d.id}.md`.padEnd(40) + ` department charter (filled during frame)`);
4156
+ println('');
4157
+ println(`Nothing else is touched. No config is changed, no active profile is switched.`);
4158
+ println(`To activate this profile after promotion: construct profile set ${id}`);
4159
+ println('');
4160
+
4161
+ if (dryRun) {
4162
+ println(`[dry-run] No files were written.`);
4163
+ return;
4164
+ }
4165
+ if (!skipConfirm && isTTY) {
4166
+ const readline = await import('node:readline');
4167
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
4168
+ const answer = await new Promise((res) => rl.question('Create draft? (Y/n) ', (a) => res(a.trim().toLowerCase())));
4169
+ rl.close();
4170
+ if (answer && answer !== 'y' && answer !== 'yes') {
4171
+ println('Aborted. Nothing was written.');
4172
+ return;
4173
+ }
4174
+ }
4175
+
4176
+ const { createDraftProfile } = await import('../lib/profiles/lifecycle.mjs');
4177
+ try {
4178
+ const result = createDraftProfile({ cwd: process.cwd(), id, displayName, seedRoles, seedDepartments });
4179
+ println('');
4180
+ println(`✓ draft created at ${pathMod.relative(process.cwd(), result.dir)}`);
4181
+ println('');
4182
+ println(`Next steps (in order, per docs/concepts/profile-lifecycle.md):`);
4183
+ println(` 1. Discover (cx-ux-researcher): fill personas/<role>.md from interviews + primary sources.`);
4184
+ println(` 2. Frame (cx-product-manager): fill departments/<dept>.md charters and intake taxonomy.`);
4185
+ println(` 3. Architect (cx-architect): reconcile role reuse vs new; populate departments[] in profile.json.`);
4186
+ println(` 4. Validate (cx-evaluator): run persona-eval and classifier-eval against representative signals.`);
4187
+ println('');
4188
+ println(`Inspect the brief: open ${pathMod.relative(process.cwd(), result.briefPath)}`);
4189
+ return;
4190
+ } catch (err) {
4191
+ errorln(err.message);
4192
+ process.exit(1);
4193
+ }
4194
+ }
4195
+ if (sub === 'drafts') {
4196
+ const { listDrafts } = await import('../lib/profiles/lifecycle.mjs');
4197
+ const drafts = listDrafts(process.cwd());
4198
+ if (drafts.length === 0) { process.stdout.write(' (no drafts)\n'); return; }
4199
+ for (const d of drafts) {
4200
+ const flags = [d.hasBrief ? 'brief' : null, d.hasProfile ? 'profile' : null].filter(Boolean).join(', ');
4201
+ process.stdout.write(` ${d.id.padEnd(20)} ${flags}\n`);
4202
+ }
4203
+ return;
4204
+ }
4205
+ if (sub === 'archive') {
4206
+ const id = args[1];
4207
+ const reasonArg = args.find((a) => a.startsWith('--reason='));
4208
+ let reason = reasonArg ? reasonArg.split('=')[1] : '';
4209
+ if (!id) {
4210
+ errorln('Usage: construct profile archive <id> --reason="..." [--yes] [--dry-run]');
4211
+ process.exit(1);
4212
+ }
4213
+ const skipConfirm = args.includes('--yes') || args.includes('-y') || args.includes('--non-interactive');
4214
+ const dryRun = args.includes('--dry-run');
4215
+ const isTTY = process.stdin.isTTY && process.stdout.isTTY;
4216
+
4217
+ const pathMod = await import('node:path');
4218
+ const fsMod = await import('node:fs');
4219
+ const target = loadProfile(id);
4220
+ if (!target) {
4221
+ errorln(`Unknown profile: ${id}. Curated catalog: ${listProfiles().join(', ')}`);
4222
+ process.exit(1);
4223
+ }
4224
+ const fileURL = await import('node:url');
4225
+ const moduleDir = pathMod.dirname(fileURL.fileURLToPath(import.meta.url));
4226
+ const repoRoot = pathMod.resolve(moduleDir, '..');
4227
+ const srcProfile = pathMod.join(repoRoot, 'profiles', `${id}.json`);
4228
+ const tableRel = typeof target?.intake?.classificationTable === 'string' ? target.intake.classificationTable : null;
4229
+ const srcTable = tableRel ? pathMod.join(repoRoot, tableRel) : null;
4230
+ const dstDir = pathMod.join(repoRoot, 'archive', 'profiles', id);
4231
+
4232
+ // Gather reason interactively if missing. The lifecycle module already
4233
+ // enforces a >= 8 char reason, but the operator should see why before
4234
+ // the call fails.
4235
+ if (!reason && isTTY && !skipConfirm) {
4236
+ const readline = await import('node:readline');
4237
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
4238
+ reason = await new Promise((res) => rl.question('Archive reason (>= 8 chars): ', (a) => res(a.trim())));
4239
+ rl.close();
4240
+ }
4241
+ if (!reason || reason.trim().length < 8) {
4242
+ errorln('A substantive --reason (>= 8 chars) is required. Archiving is permanent in repo history; the reason becomes the archive note.');
4243
+ process.exit(1);
4244
+ }
4245
+
4246
+ println('');
4247
+ println(`About to archive curated profile:`);
4248
+ println(` id: ${id}`);
4249
+ println(` displayName: ${target.displayName || id}`);
4250
+ println(` reason: ${reason}`);
4251
+ println('');
4252
+ println(`Files that will move:`);
4253
+ println(` ${pathMod.relative(process.cwd(), srcProfile)}`);
4254
+ println(` -> ${pathMod.relative(process.cwd(), pathMod.join(dstDir, `${id}.json`))}`);
4255
+ if (srcTable && fsMod.existsSync(srcTable)) {
4256
+ println(` ${pathMod.relative(process.cwd(), srcTable)}`);
4257
+ println(` -> ${pathMod.relative(process.cwd(), pathMod.join(dstDir, pathMod.basename(srcTable)))}`);
4258
+ }
4259
+ println(` ${pathMod.relative(process.cwd(), pathMod.join(dstDir, 'archive-note.md'))} (new)`);
4260
+ println('');
4261
+ println(`What stays:`);
4262
+ println(` Observations under .cx/observations/ are not touched.`);
4263
+ println(` Outcomes under .cx/outcomes/ are not touched.`);
4264
+ println(` Any project still configured with profile=${id} will fall back to rnd on next load.`);
4265
+ println('');
4266
+
4267
+ if (dryRun) {
4268
+ println(`[dry-run] No files were moved.`);
4269
+ return;
4270
+ }
4271
+ if (!skipConfirm && isTTY) {
4272
+ const readline = await import('node:readline');
4273
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
4274
+ const answer = await new Promise((res) => rl.question(`Archive ${id}? (y/N) `, (a) => res(a.trim().toLowerCase())));
4275
+ rl.close();
4276
+ if (answer !== 'y' && answer !== 'yes') {
4277
+ println('Aborted. Nothing was archived.');
4278
+ return;
4279
+ }
4280
+ }
4281
+
4282
+ const { archiveProfile } = await import('../lib/profiles/lifecycle.mjs');
4283
+ try {
4284
+ const { archived } = archiveProfile({ id, reason });
4285
+ process.stdout.write(`✓ archived ${id} to ${pathMod.relative(process.cwd(), archived)}\n`);
4286
+ return;
4287
+ } catch (err) {
4288
+ errorln(err.message);
4289
+ process.exit(1);
4290
+ }
4291
+ }
4292
+ if (sub === 'health') {
4293
+ const id = args[1];
4294
+ if (!id) { errorln('Usage: construct profile health <id> [--days=N]'); process.exit(1); }
4295
+ const daysArg = args.find((a) => a.startsWith('--days='));
4296
+ const windowDays = daysArg ? Number(daysArg.split('=')[1]) || 30 : 30;
4297
+ const { profileHealth } = await import('../lib/profiles/lifecycle.mjs');
4298
+ const report = profileHealth(process.cwd(), id, { windowDays });
4299
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
4300
+ return;
4301
+ }
4302
+ errorln(`Unknown profile subcommand: ${sub}. Available: list, show, set, create, drafts, archive, health`);
3837
4303
  process.exit(1);
3838
4304
  }],
3839
4305
  ]);
@@ -1,10 +1,10 @@
1
1
  <!--
2
- commands/build/feature.md Build a feature implement it end to end, tested and ready to ship
2
+ commands/build/feature.md (Build a feature) implement it end to end, tested and ready to ship
3
3
 
4
- Build a feature implement it end to end, tested and ready to ship
4
+ Build a feature: implement it end to end, tested and ready to ship
5
5
  -->
6
6
  ---
7
- description: Build a feature implement it end to end, tested and ready to ship
7
+ description: Build a feature: implement it end to end, tested and ready to ship
8
8
  ---
9
9
 
10
10
  You are Construct. Build the following: $ARGUMENTS
@@ -18,4 +18,4 @@ produces no output or tool activity within two minutes, surface it to the user w
18
18
  agent description and elapsed time rather than waiting silently. Do not re-dispatch until
19
19
  the user confirms the prior invocation is not still running.
20
20
 
21
- Verification protocol is owned by `cx-engineer` apply its checklist before declaring done.
21
+ Verification protocol is owned by `cx-engineer`: apply its checklist before declaring done.
@@ -1,20 +1,20 @@
1
1
  <!--
2
- commands/build/fix.md Fix something broken reproduce, find the root cause, apply the smallest safe change
2
+ commands/build/fix.md (Fix something broken) reproduce, find the root cause, apply the smallest safe change
3
3
 
4
- Fix something broken reproduce, find the root cause, apply the smallest safe change
4
+ Fix something broken: reproduce, find the root cause, apply the smallest safe change
5
5
  -->
6
6
  ---
7
- description: Fix something broken reproduce, find the root cause, apply the smallest safe change
7
+ description: Fix something broken: reproduce, find the root cause, apply the smallest safe change
8
8
  ---
9
9
 
10
10
  You are Construct.
11
11
 
12
12
  Debugging protocol for: $ARGUMENTS
13
13
 
14
- 1. Reproduce confirm the failure is real and consistent
15
- 2. Isolate narrow to the smallest failing case
16
- 3. Trace follow execution to where the invariant breaks
17
- 4. Root cause the one upstream cause that prevents the failure when fixed
18
- 5. Fix the smallest safe change that restores the invariant
14
+ 1. Reproduce: confirm the failure is real and consistent
15
+ 2. Isolate: narrow to the smallest failing case
16
+ 3. Trace: follow execution to where the invariant breaks
17
+ 4. Root cause: the one upstream cause that prevents the failure when fixed
18
+ 5. Fix: the smallest safe change that restores the invariant
19
19
 
20
20
  Do not propose a fix until root cause is confirmed. After 3 failed attempts, stop and escalate.