@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
@@ -0,0 +1,114 @@
1
+ /**
2
+ * lib/profiles/validate-custom.mjs — Schema validator for user-defined profiles.
3
+ *
4
+ * Custom profiles live at `<project>/.cx/profile.json` with `custom: true`.
5
+ * They are validated on `construct sync` and via the pre-push gate. Rejected
6
+ * profiles never get loaded; resolveActiveProfile falls back to the default
7
+ * so a malformed escape-hatch file never breaks a project.
8
+ *
9
+ * Hard limits the validator enforces (kept in sync with schemas/profile.schema.json
10
+ * and scripts/lint-profiles.mjs):
11
+ * - max 12 stages
12
+ * - max 24 intake types
13
+ * - max 80 roles per profile
14
+ * - max 12 departments per profile
15
+ * - max 20 roles per department
16
+ * - classificationTable path must stay inside .cx/ (no repo escapes)
17
+ *
18
+ * Rationale for each cap lives in docs/concepts/persona-research.md.
19
+ */
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+
23
+ const MAX_STAGES = 12;
24
+ const MAX_INTAKE_TYPES = 24;
25
+ const MAX_ROLES = 80;
26
+ const MAX_DEPARTMENTS = 12;
27
+ const MAX_ROLES_PER_DEPARTMENT = 20;
28
+
29
+ /**
30
+ * @returns {string[]} array of error strings; empty means valid.
31
+ */
32
+ export function validateCustomProfile(profile, { cwd } = {}) {
33
+ const errors = [];
34
+ if (!profile || typeof profile !== 'object') return ['profile is not an object'];
35
+ if (profile.custom !== true) errors.push('custom profiles must set custom: true');
36
+
37
+ if (!profile.id || !/^[a-z][a-z0-9-]{1,30}$/.test(profile.id)) {
38
+ errors.push('id must match ^[a-z][a-z0-9-]{1,30}$');
39
+ }
40
+ if (!profile.displayName || typeof profile.displayName !== 'string') {
41
+ errors.push('displayName is required');
42
+ }
43
+ if (!Array.isArray(profile.roles) || profile.roles.length === 0) {
44
+ errors.push('roles must be a non-empty array');
45
+ } else if (profile.roles.length > MAX_ROLES) {
46
+ errors.push(`roles exceeds max of ${MAX_ROLES}`);
47
+ }
48
+ if (!profile.intake || typeof profile.intake !== 'object') {
49
+ errors.push('intake is required');
50
+ } else {
51
+ if (!Array.isArray(profile.intake.types) || profile.intake.types.length === 0) {
52
+ errors.push('intake.types must be a non-empty array');
53
+ } else if (profile.intake.types.length > MAX_INTAKE_TYPES) {
54
+ errors.push(`intake.types exceeds max of ${MAX_INTAKE_TYPES}`);
55
+ }
56
+ if (!Array.isArray(profile.intake.stages) || profile.intake.stages.length === 0) {
57
+ errors.push('intake.stages must be a non-empty array');
58
+ } else if (profile.intake.stages.length > MAX_STAGES) {
59
+ errors.push(`intake.stages exceeds max of ${MAX_STAGES}`);
60
+ }
61
+ if (typeof profile.intake.classificationTable === 'string') {
62
+ const t = profile.intake.classificationTable;
63
+ if (path.isAbsolute(t)) {
64
+ errors.push('intake.classificationTable must be a relative path');
65
+ } else if (cwd && !t.startsWith('.cx/')) {
66
+ errors.push('intake.classificationTable must live under .cx/ for custom profiles');
67
+ }
68
+ }
69
+ }
70
+ if (profile.departments !== undefined) {
71
+ if (!Array.isArray(profile.departments)) {
72
+ errors.push('departments must be an array');
73
+ } else {
74
+ if (profile.departments.length > MAX_DEPARTMENTS) {
75
+ errors.push(`departments exceeds max of ${MAX_DEPARTMENTS}`);
76
+ }
77
+ for (const [i, dept] of profile.departments.entries()) {
78
+ if (!dept || typeof dept !== 'object') {
79
+ errors.push(`departments[${i}] must be an object`);
80
+ continue;
81
+ }
82
+ if (!dept.id || !/^[a-z][a-z0-9-]{1,40}$/.test(dept.id)) {
83
+ errors.push(`departments[${i}].id is missing or malformed`);
84
+ }
85
+ if (!dept.charter || dept.charter.length < 20) {
86
+ errors.push(`departments[${i}].charter must be at least 20 chars (mission statement, not a label)`);
87
+ }
88
+ if (!Array.isArray(dept.roles) || dept.roles.length === 0) {
89
+ errors.push(`departments[${i}].roles must be a non-empty array`);
90
+ } else if (dept.roles.length > MAX_ROLES_PER_DEPARTMENT) {
91
+ errors.push(`departments[${i}].roles exceeds max of ${MAX_ROLES_PER_DEPARTMENT}`);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ return errors;
97
+ }
98
+
99
+ /**
100
+ * Read and validate `<cwd>/.cx/profile.json`. Returns:
101
+ * { status: 'absent' } if the file does not exist
102
+ * { status: 'invalid', errors: [...] } if validation failed
103
+ * { status: 'ok', profile } if the file is a valid custom profile
104
+ */
105
+ export function validateCustomProfileFile(cwd) {
106
+ const p = path.join(cwd, '.cx', 'profile.json');
107
+ if (!fs.existsSync(p)) return { status: 'absent' };
108
+ let raw;
109
+ try { raw = JSON.parse(fs.readFileSync(p, 'utf8')); }
110
+ catch (err) { return { status: 'invalid', errors: [`malformed JSON: ${err.message}`] }; }
111
+ const errors = validateCustomProfile(raw, { cwd });
112
+ if (errors.length > 0) return { status: 'invalid', errors };
113
+ return { status: 'ok', profile: raw };
114
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * lib/reflect/extractor.mjs — Deterministic transcript → observation extractor.
3
+ *
4
+ * Pure-function pass over a Claude Code transcript JSONL. Produces a compact
5
+ * structured summary suitable for `addObservation()` at session end. No LLM
6
+ * call — the transcript itself is the source of truth.
7
+ *
8
+ * Caller responsibility: read the transcript file and pass parsed lines.
9
+ * Output is capped at MAX_BYTES to honor the A1 hook's per-session budget.
10
+ */
11
+
12
+ // Hard cap on the summary content so a runaway session can't blow up the
13
+ // observation store. 5 KB matches the budget set in the A1 plan.
14
+ const MAX_BYTES = 5 * 1024;
15
+ const MAX_TAGS = 10;
16
+ const MAX_TOOL_TYPES = 8;
17
+ const MAX_FILES = 12;
18
+
19
+ /**
20
+ * Extract a session-summary observation from a parsed transcript.
21
+ *
22
+ * @param {object} args
23
+ * @param {object[]} args.entries — parsed transcript JSONL lines (objects)
24
+ * @param {string} args.cwd — session working directory
25
+ * @param {string} [args.sessionId] — session identifier
26
+ * @param {number} [args.durationMs] — wall-clock session duration
27
+ * @returns {object|null} observation payload, or null if transcript is unusable
28
+ */
29
+ export function extractSessionObservation({ entries, cwd, sessionId, durationMs }) {
30
+ if (!Array.isArray(entries) || entries.length === 0) return null;
31
+
32
+ const stats = collectStats(entries);
33
+ if (stats.assistantTurns === 0 && stats.userTurns === 0) return null;
34
+
35
+ const finalAssistant = stats.lastAssistantText;
36
+ const headline = deriveHeadline(finalAssistant, stats);
37
+
38
+ const lines = [];
39
+ lines.push(`Session: ${stats.userTurns} user turns, ${stats.assistantTurns} assistant turns`);
40
+ if (stats.toolCallCount > 0) {
41
+ const tools = Array.from(stats.toolTypes.entries())
42
+ .sort((a, b) => b[1] - a[1])
43
+ .slice(0, MAX_TOOL_TYPES)
44
+ .map(([name, n]) => `${name}×${n}`)
45
+ .join(', ');
46
+ lines.push(`Tools: ${stats.toolCallCount} calls (${tools})`);
47
+ }
48
+ if (stats.filesTouched.size > 0) {
49
+ const files = Array.from(stats.filesTouched).slice(0, MAX_FILES);
50
+ lines.push(`Files touched: ${files.join(', ')}${stats.filesTouched.size > MAX_FILES ? ', …' : ''}`);
51
+ }
52
+ if (durationMs && Number.isFinite(durationMs)) {
53
+ lines.push(`Duration: ${Math.round(durationMs / 1000)}s`);
54
+ }
55
+ if (finalAssistant) {
56
+ lines.push('', 'Final response excerpt:', clamp(finalAssistant, 800));
57
+ }
58
+
59
+ const content = clamp(lines.join('\n'), MAX_BYTES);
60
+
61
+ return {
62
+ role: stats.lastAgent || 'construct',
63
+ category: 'session-summary',
64
+ summary: headline,
65
+ content,
66
+ tags: buildTags(stats),
67
+ confidence: 0.7,
68
+ source: 'auto-reflect',
69
+ extras: {
70
+ sessionId: sessionId || null,
71
+ cwd,
72
+ durationMs: durationMs ?? null,
73
+ userTurns: stats.userTurns,
74
+ assistantTurns: stats.assistantTurns,
75
+ toolCallCount: stats.toolCallCount,
76
+ filesTouched: Array.from(stats.filesTouched).slice(0, MAX_FILES),
77
+ },
78
+ };
79
+ }
80
+
81
+ // Walk the transcript once and gather everything we need in one pass. Keeps
82
+ // the hook well under its latency budget even on long sessions.
83
+
84
+ function collectStats(entries) {
85
+ const toolTypes = new Map();
86
+ const filesTouched = new Set();
87
+ let assistantTurns = 0;
88
+ let userTurns = 0;
89
+ let toolCallCount = 0;
90
+ let lastAssistantText = '';
91
+ let lastAgent = null;
92
+
93
+ for (const entry of entries) {
94
+ if (!entry || typeof entry !== 'object') continue;
95
+ const type = entry.type;
96
+
97
+ if (type === 'user') {
98
+ userTurns += 1;
99
+ continue;
100
+ }
101
+
102
+ if (type === 'assistant') {
103
+ assistantTurns += 1;
104
+ const text = extractAssistantText(entry);
105
+ if (text) lastAssistantText = text;
106
+ const calls = extractToolCalls(entry);
107
+ for (const call of calls) {
108
+ toolCallCount += 1;
109
+ toolTypes.set(call.name, (toolTypes.get(call.name) || 0) + 1);
110
+ for (const f of call.files) filesTouched.add(f);
111
+ }
112
+ const agent = extractAgentName(entry);
113
+ if (agent) lastAgent = agent;
114
+ }
115
+ }
116
+
117
+ return { assistantTurns, userTurns, toolCallCount, toolTypes, filesTouched, lastAssistantText, lastAgent };
118
+ }
119
+
120
+ function extractAssistantText(entry) {
121
+ const message = entry.message ?? entry;
122
+ const content = message?.content;
123
+ if (typeof content === 'string') return content.trim();
124
+ if (Array.isArray(content)) {
125
+ const text = content
126
+ .filter((p) => p?.type === 'text' && typeof p.text === 'string')
127
+ .map((p) => p.text)
128
+ .join('\n')
129
+ .trim();
130
+ return text;
131
+ }
132
+ return '';
133
+ }
134
+
135
+ function extractToolCalls(entry) {
136
+ const message = entry.message ?? entry;
137
+ const content = message?.content;
138
+ if (!Array.isArray(content)) return [];
139
+ const calls = [];
140
+ for (const part of content) {
141
+ if (part?.type !== 'tool_use') continue;
142
+ const name = part.name || 'unknown';
143
+ const files = [];
144
+ const input = part.input || {};
145
+ if (typeof input.file_path === 'string') files.push(shortenPath(input.file_path));
146
+ if (typeof input.path === 'string') files.push(shortenPath(input.path));
147
+ calls.push({ name, files });
148
+ }
149
+ return calls;
150
+ }
151
+
152
+ function extractAgentName(entry) {
153
+ // Claude Code may stamp the agent name on subagent transcripts. Fall through
154
+ // to null if not present — caller defaults to 'construct'.
155
+ return entry?.agent || entry?.subagent_type || entry?.message?.agent || null;
156
+ }
157
+
158
+ function deriveHeadline(finalText, stats) {
159
+ // First non-empty line of the final assistant turn is usually a good lead.
160
+ // Fall back to a structural summary when nothing useful is available.
161
+ if (finalText) {
162
+ const firstLine = finalText.split('\n').map((l) => l.trim()).find((l) => l.length > 0);
163
+ if (firstLine) {
164
+ const clean = firstLine.replace(/^[#*\->]\s*/, '').replace(/[`*_]/g, '');
165
+ return `[session] ${clamp(clean, 200)}`;
166
+ }
167
+ }
168
+ return `[session] ${stats.assistantTurns} turn${stats.assistantTurns === 1 ? '' : 's'}, ${stats.toolCallCount} tool call${stats.toolCallCount === 1 ? '' : 's'}`;
169
+ }
170
+
171
+ function buildTags(stats) {
172
+ const tags = ['auto-reflect', 'session-summary'];
173
+ // Surface the most-used tool family so search can filter on "Bash-heavy
174
+ // session", etc. without parsing content.
175
+ const top = Array.from(stats.toolTypes.entries()).sort((a, b) => b[1] - a[1]).slice(0, 3);
176
+ for (const [name] of top) tags.push(`tool:${name.toLowerCase()}`);
177
+ if (stats.filesTouched.size > 0) tags.push('edits');
178
+ return tags.slice(0, MAX_TAGS);
179
+ }
180
+
181
+ function shortenPath(p) {
182
+ if (typeof p !== 'string') return '';
183
+ // Strip a repo-root prefix when present so observations stay portable.
184
+ const cwd = process.cwd();
185
+ if (p.startsWith(cwd + '/')) return p.slice(cwd.length + 1);
186
+ return p;
187
+ }
188
+
189
+ function clamp(str, max) {
190
+ if (!str) return '';
191
+ if (str.length <= max) return str;
192
+ return str.slice(0, max - 1) + '…';
193
+ }
package/lib/reflect.mjs CHANGED
@@ -5,11 +5,13 @@
5
5
  * knowledge base with actionable insights for continuous self-improvement.
6
6
  */
7
7
 
8
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
9
- import { join, resolve } from 'node:path';
8
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
9
+ import { join, resolve, basename } from 'node:path';
10
+ import { execSync } from 'node:child_process';
10
11
  import { addObservation } from './observation-store.mjs';
11
12
  import { loadConstructEnv } from './env-config.mjs';
12
13
  import { KNOWLEDGE_ROOT, KNOWLEDGE_SUBDIRS, inferKnowledgeTarget } from './knowledge/layout.mjs';
14
+ import { extractSessionObservation } from './reflect/extractor.mjs';
13
15
 
14
16
  const HOME = process.env.HOME || process.env.USERPROFILE;
15
17
  const USER_ENV_PATH = join(HOME, '.construct', 'config.env');
@@ -125,6 +127,91 @@ export async function runReflectCli(args) {
125
127
  console.log(` Use 'construct ingest --target=${target} <file>' to manually add similar feedback in the future`);
126
128
  }
127
129
 
130
+ /**
131
+ * Auto-reflect from a Claude Code Stop hook payload.
132
+ *
133
+ * Reads the transcript JSONL, extracts a deterministic session summary, and
134
+ * persists it via the standard observation store. Contract: exit fast, fail
135
+ * silent, never throw — every error path returns null so the hook always
136
+ * exits 0.
137
+ *
138
+ * Returns the recorded observation id on success, null on no-op.
139
+ *
140
+ * @param {object} args
141
+ * @param {string} args.transcriptPath — path to the Stop hook's transcript_path
142
+ * @param {string} args.cwd — session cwd (the project root)
143
+ * @param {string} [args.sessionId]
144
+ * @param {number} [args.durationMs]
145
+ * @returns {Promise<string|null>}
146
+ */
147
+ export async function runReflectAuto({ transcriptPath, cwd, sessionId, durationMs }) {
148
+ if (!transcriptPath || !cwd) return null;
149
+ if (!existsSync(transcriptPath)) return null;
150
+
151
+ const entries = parseTranscript(transcriptPath);
152
+ if (entries.length === 0) return null;
153
+
154
+ const observation = extractSessionObservation({ entries, cwd, sessionId, durationMs });
155
+ if (!observation) return null;
156
+
157
+ // Skip trivial sessions: no tool calls AND no substantive final text. Avoids
158
+ // polluting the observation store with one-line acknowledgements.
159
+ const trivial =
160
+ observation.extras?.toolCallCount === 0 &&
161
+ (observation.content?.length || 0) < 120;
162
+ if (trivial) return null;
163
+
164
+ const project = safeProject(cwd);
165
+ const gitSha = safeGitSha(cwd);
166
+
167
+ // Session-id stays in tags so the index can filter by it without parsing.
168
+ // Structured metadata goes into the new extras field, not into content.
169
+ const sessionTag = sessionId ? `session:${sessionId.slice(0, 24)}` : null;
170
+ const tags = [...observation.tags];
171
+ if (sessionTag) tags.push(sessionTag);
172
+
173
+ const result = await addObservation(cwd, {
174
+ role: observation.role,
175
+ category: observation.category,
176
+ summary: observation.summary,
177
+ content: observation.content,
178
+ tags,
179
+ project,
180
+ gitSha,
181
+ confidence: observation.confidence,
182
+ source: observation.source,
183
+ extras: observation.extras ?? null,
184
+ });
185
+
186
+ return result?.id ?? null;
187
+ }
188
+
189
+ function safeProject(cwd) {
190
+ try { return basename(cwd) || null; } catch { return null; }
191
+ }
192
+
193
+ function safeGitSha(cwd) {
194
+ try {
195
+ return execSync('git rev-parse HEAD', { cwd, stdio: ['ignore', 'pipe', 'ignore'], timeout: 200 })
196
+ .toString().trim().slice(0, 40) || null;
197
+ } catch { return null; }
198
+ }
199
+
200
+ // Transcripts are JSONL; one JSON object per line. Be permissive — a single
201
+ // malformed line shouldn't lose the rest of the session.
202
+
203
+ function parseTranscript(path) {
204
+ let raw = '';
205
+ try { raw = readFileSync(path, 'utf8'); } catch { return []; }
206
+ const out = [];
207
+ for (const line of raw.split('\n')) {
208
+ const trimmed = line.trim();
209
+ if (!trimmed) continue;
210
+ try { out.push(JSON.parse(trimmed)); } catch { /* skip malformed */ }
211
+ }
212
+ return out;
213
+ }
214
+
128
215
  /**
129
216
  * CLI entry point for direct execution.
130
217
  */
@@ -0,0 +1,102 @@
1
+ /**
2
+ * lib/sandbox.mjs — Lightweight isolated environment for QA validation.
3
+ *
4
+ * Spins up a fresh tmpdir with a minimal `.cx/` so a specialist (or operator)
5
+ * can run end-to-end checks against a candidate change without touching the
6
+ * working repo. Designed to be the cheapest possible alternative to a full
7
+ * Docker container: when Docker is unavailable, an isolated tmpdir is enough
8
+ * for almost every validation Construct needs.
9
+ *
10
+ * Layout created:
11
+ * <sandbox>/.cx/
12
+ * context.md stub
13
+ * observations/ empty
14
+ * intake/pending/ empty
15
+ * <sandbox>/.git/ empty repo init, so git-aware tools work
16
+ *
17
+ * Tear-down is opt-in via `construct sandbox prune` or by hand. Each sandbox
18
+ * lives under `~/.cx/sandboxes/<id>/` so they survive across CLI sessions.
19
+ */
20
+ import fs from 'node:fs';
21
+ import os from 'node:os';
22
+ import path from 'node:path';
23
+ import { spawnSync } from 'node:child_process';
24
+
25
+ const ROOT = path.join(os.homedir(), '.cx', 'sandboxes');
26
+
27
+ function ensureRoot() {
28
+ fs.mkdirSync(ROOT, { recursive: true });
29
+ }
30
+
31
+ function newSandboxId() {
32
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
33
+ const rand = Math.random().toString(36).slice(2, 8);
34
+ return `${stamp}-${rand}`;
35
+ }
36
+
37
+ /**
38
+ * Create a new sandbox. Returns its absolute path.
39
+ *
40
+ * @param {object} [opts]
41
+ * @param {string} [opts.profile] - profile id to write into construct.config.json
42
+ * @param {string} [opts.seedContext] - text to drop into .cx/context.md
43
+ * @returns {{ id: string, path: string }}
44
+ */
45
+ export function createSandbox({ profile = null, seedContext = null } = {}) {
46
+ ensureRoot();
47
+ const id = newSandboxId();
48
+ const root = path.join(ROOT, id);
49
+ fs.mkdirSync(root, { recursive: true });
50
+ fs.mkdirSync(path.join(root, '.cx', 'observations'), { recursive: true });
51
+ fs.mkdirSync(path.join(root, '.cx', 'intake', 'pending'), { recursive: true });
52
+
53
+ const ctx = seedContext || `# Sandbox ${id}\n\nIsolated environment for QA validation. Created ${new Date().toISOString()}.\n`;
54
+ fs.writeFileSync(path.join(root, '.cx', 'context.md'), ctx);
55
+
56
+ const cfg = { version: 1, alias: 'Construct', deployment: { mode: 'solo' } };
57
+ if (profile) cfg.profile = profile;
58
+ fs.writeFileSync(path.join(root, 'construct.config.json'), JSON.stringify(cfg, null, 2) + '\n');
59
+
60
+ spawnSync('git', ['init', '--quiet'], { cwd: root, stdio: 'ignore' });
61
+
62
+ return { id, path: root };
63
+ }
64
+
65
+ /**
66
+ * List existing sandboxes, newest first.
67
+ */
68
+ export function listSandboxes() {
69
+ if (!fs.existsSync(ROOT)) return [];
70
+ const entries = fs.readdirSync(ROOT, { withFileTypes: true })
71
+ .filter((d) => d.isDirectory())
72
+ .map((d) => {
73
+ const full = path.join(ROOT, d.name);
74
+ const stat = fs.statSync(full);
75
+ return { id: d.name, path: full, createdAt: stat.birthtime.toISOString() };
76
+ });
77
+ return entries.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
78
+ }
79
+
80
+ /**
81
+ * Delete a sandbox by id. Returns true if removed, false if not found.
82
+ */
83
+ export function deleteSandbox(id) {
84
+ const full = path.join(ROOT, id);
85
+ if (!fs.existsSync(full)) return false;
86
+ fs.rmSync(full, { recursive: true, force: true });
87
+ return true;
88
+ }
89
+
90
+ /**
91
+ * Prune sandboxes older than N days. Returns the count removed.
92
+ */
93
+ export function pruneSandboxes({ olderThanDays = 7 } = {}) {
94
+ const cutoff = Date.now() - olderThanDays * 24 * 60 * 60 * 1000;
95
+ let removed = 0;
96
+ for (const s of listSandboxes()) {
97
+ if (Date.parse(s.createdAt) < cutoff) {
98
+ if (deleteSandbox(s.id)) removed++;
99
+ }
100
+ }
101
+ return removed;
102
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geraldmaron/construct",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "Construct — agent orchestration layer for OpenCode, Claude Code, and other coding surfaces",
6
6
  "bin": {
@@ -42,12 +42,17 @@
42
42
  "scripts": {
43
43
  "mcp": "node lib/mcp/server.mjs",
44
44
  "test": "node scripts/run-tests.mjs",
45
+ "test:functional": "node --test --test-timeout=30000 tests/functional/*.functional.test.mjs",
46
+ "test:unit": "node scripts/run-tests.mjs --exclude=tests/functional",
45
47
  "docs:init": "node lib/init-docs.mjs --yes",
46
48
  "docs:update": "node ./bin/construct docs:update",
47
49
  "docs:site": "node ./bin/construct docs:site",
48
50
  "docs:dev": "npm --prefix apps/docs run dev",
49
51
  "docs:build": "npm --prefix apps/docs run build",
50
52
  "lint:comments": "node ./bin/construct lint:comments",
53
+ "lint:prose": "node scripts/lint-prose.mjs",
54
+ "lint:profiles": "node scripts/lint-profiles.mjs",
55
+ "learning:status": "node scripts/learning-status.mjs",
51
56
  "lint:templates": "node scripts/lint-commits-pr.mjs",
52
57
  "eval:routing": "node scripts/eval/score-intent-classifier.mjs",
53
58
  "release:check": "node ./bin/construct doctor && npm test && node ./bin/construct docs:update --check && node ./bin/construct dashboard:sync --check && node ./bin/construct lint:comments && node scripts/lint-commits-pr.mjs",
@@ -1,24 +1,24 @@
1
1
  <!--
2
- personas/construct.md Construct persona prompt.
2
+ personas/construct.md. Construct persona prompt.
3
3
 
4
4
  Defines the single user-facing AI interface and its session-start behavior,
5
5
  routing rules, approval boundaries, and output contract. Loaded by sync-agents
6
6
  and emitted to every supported platform.
7
7
  -->
8
- You are Construct the single AI interface for everything from a quick question to a full software lifecycle. The user talks only to you; internal routing and specialist dispatch are implementation detail.
8
+ You are Construct. the single AI interface for everything from a quick question to a full software lifecycle. The user talks only to you; internal routing and specialist dispatch are implementation detail.
9
9
 
10
10
  ## Start of every session
11
11
 
12
- Before responding, run in parallel do not narrate:
13
- 1. `project_context` state from `.cx/context.md`
14
- 2. `memory_search` with the basename of CWD prior session context and user preferences
12
+ Before responding, run in parallel. do not narrate:
13
+ 1. `project_context`. state from `.cx/context.md`
14
+ 2. `memory_search` with the basename of CWD. prior session context and user preferences
15
15
  3. Read `AGENTS.md`, `plan.md`, and the relevant docs for the current task when present
16
- 4. Check `.cx/handoffs/` for the most recent handoff if another session was active, read it to understand what was in progress and what NOT to touch
16
+ 4. Check `.cx/handoffs/` for the most recent handoff. if another session was active, read it to understand what was in progress and what NOT to touch
17
17
 
18
18
  Apply results silently. If memory returns preferences or past decisions, honor them without asking the user to repeat.
19
19
 
20
20
  Honor the project operating hierarchy:
21
- - Beads (`bd`) is the durable source of truth for tasks; hygiene contract claim, close, supersede, prune lives in `rules/common/beads-hygiene.md`. `bd ready` for unblocked work, `bd show <id>` for the active issue.
21
+ - Beads (`bd`) is the durable source of truth for tasks; hygiene contract. claim, close, supersede, prune. lives in `rules/common/beads-hygiene.md`. `bd ready` for unblocked work, `bd show <id>` for the active issue.
22
22
  - `plan.md` is the human-readable implementation plan
23
23
  - cass-memory via MCP `memory` is for cross-tool/session recall, not task tracking
24
24
 
@@ -26,12 +26,12 @@ Use the single-writer rule whenever multiple sessions are active: if two session
26
26
 
27
27
  ## Classify before acting
28
28
 
29
- Use the code-backed orchestration policy for intent, execution track, specialist selection, escalation, and approval boundaries. Visual deliverables (wireframes, diagrams, decks) are first-class use real visual tools, not bullet prose.
29
+ Use the code-backed orchestration policy for intent, execution track, specialist selection, escalation, and approval boundaries. Visual deliverables (wireframes, diagrams, decks) are first-class. use real visual tools, not bullet prose.
30
30
 
31
31
  Execution model:
32
- - **Immediate** answer or act directly when no hidden worker is needed.
33
- - **Focused** dispatch one bounded specialist path; return in Construct's voice.
34
- - **Orchestrated** plan → challenge → build → validate, with tracker-backed slices and explicit file ownership.
32
+ - **Immediate**. answer or act directly when no hidden worker is needed.
33
+ - **Focused**. dispatch one bounded specialist path; return in Construct's voice.
34
+ - **Orchestrated**. plan → challenge → build → validate, with tracker-backed slices and explicit file ownership.
35
35
 
36
36
  Devil's advocate is mandatory for new architectural directions, AI/agent workflow changes, security or data-integrity changes, and promoting a temporary capability to persistent.
37
37
 
@@ -39,9 +39,9 @@ Devil's advocate is mandatory for new architectural directions, AI/agent workflo
39
39
 
40
40
  `routeRequest` returns three artifacts; honor all three:
41
41
 
42
- 1. **Gates** `framingChallenge`, `externalResearch`, `docAuthoring`
43
- 2. **Contract chain** typed handoffs from `agents/contracts.json`. Call `agent_contract` MCP tool at handoff.
44
- 3. **Specialist sequence** dispatch plan with ordering/parallel markers.
42
+ 1. **Gates**. `framingChallenge`, `externalResearch`, `docAuthoring`
43
+ 2. **Contract chain**. typed handoffs from `agents/contracts.json`. Call `agent_contract` MCP tool at handoff.
44
+ 3. **Specialist sequence**. dispatch plan with ordering/parallel markers.
45
45
 
46
46
  Before DONE: postconditions met · sources cited · framing logged · ADRs have Rejected alternatives.
47
47
 
@@ -50,9 +50,9 @@ Before DONE: postconditions met · sources cited · framing logged · ADRs have
50
50
  - **Working branch is surfaced every session** at the top of session-start. Restate it before any mutating operation.
51
51
  - **Never commit, push, or merge without asking first.** Before `git commit`, `git push`, or `gh pr merge`: state branch, show the proposed message / refspec / PR number verbatim, wait for explicit yes. A batch go-ahead covers a defined sequence; new commits later are their own gate. See `rules/common/commit-approval.md`.
52
52
 
53
- ## R&D intake surface
53
+ ## Intake surface
54
54
 
55
- Session-start surfaces pending intake at `.cx/intake/pending/<id>.json`. Read with `construct intake show <id>`; the triage block names the primary owner, recommended chain, and next action. For non-trivial signals, plan with `construct graph from-intake <id>` and update node status with evidence (`construct graph status … done --evidence=…`). A node cannot reach `done` without an evidence record. Team / enterprise mode wraps tool calls in the MCP broker when it returns `ApprovalRequired`, surface the question; never bypass.
55
+ The active profile (`construct profile show`) sets the intake taxonomy. Session-start surfaces pending intake at `.cx/intake/pending/<id>.json`. Read with `construct intake show <id>`; the triage block names the primary owner, recommended chain, and next action. For non-trivial signals, plan with `construct graph from-intake <id>` and update node status with evidence (`construct graph status … done --evidence=…`). A node cannot reach `done` without an evidence record. Team / enterprise mode wraps tool calls in the MCP broker; when it returns `ApprovalRequired`, surface the question and never bypass.
56
56
 
57
57
  ## Action discipline
58
58
 
@@ -73,15 +73,15 @@ Load-bearing state: `AGENTS.md`, `.cx/context.md`/`.json`, `docs/README.md`, `do
73
73
  ## Quality gates
74
74
 
75
75
  After any implementation, dispatch validation before marking done:
76
- 1. cx-reviewer correctness, regression, coverage
77
- 2. cx-qa tests pass, coverage meets threshold
76
+ 1. cx-reviewer. correctness, regression, coverage
77
+ 2. cx-qa. tests pass, coverage meets threshold
78
78
  3. cx-security if auth/secrets/user data touched
79
79
 
80
80
  Do not mark `done` until cx-reviewer and cx-qa return verdicts. BLOCKED or any CRITICAL finding stops shipping.
81
81
 
82
82
  ## Hard release gates
83
83
 
84
- Run `npm run release:check` before any commit or push never wait for CI. Commits follow `.gitmessage`; PRs follow `.github/pull_request_template.md`. Full policy: `rules/common/release-gates.md`.
84
+ Run `npm run release:check` before any commit or push. never wait for CI. Commits follow `.gitmessage`; PRs follow `.github/pull_request_template.md`. Full policy: `rules/common/release-gates.md`.
85
85
 
86
86
  ## Loop guard
87
87
 
@@ -91,6 +91,6 @@ Before stopping: surface incomplete tracker-linked plan slices and unmet accepta
91
91
 
92
92
  ## Drive mode
93
93
 
94
- Activates on word-boundary triggers `/work:drive`, standalone `drive`, or `full send`. Substring matches do not count.
94
+ Activates on word-boundary triggers. `/work:drive`, standalone `drive`, or `full send`. Substring matches do not count.
95
95
 
96
96
  On trigger: orchestrated track, skip planning confirmation, continue until verification or a real blocker. State the dispatch plan upfront; brief status at phase transitions. User sees plan and outcomes, not deliberation.
@@ -1,4 +1,4 @@
1
- # Construct Claude Code Integration
1
+ # Construct: Claude Code Integration
2
2
 
3
3
  This project uses Construct. Personas and specialists are defined in `agents/registry.json` and synced to Claude Code via `construct sync`.
4
4
 
@@ -6,11 +6,11 @@ This project uses Construct. Personas and specialists are defined in `agents/reg
6
6
 
7
7
  Construct is the only intended user-facing surface.
8
8
 
9
- - **Planning** requirements, strategy, architecture
10
- - **Implementation** builds features and fixes bugs
11
- - **Validation** quality gates, security, accessibility
12
- - **Research** docs, debugging, codebase exploration
13
- - **Operations** releases, dev servers, health checks
9
+ - **Planning**: requirements, strategy, architecture
10
+ - **Implementation**: builds features and fixes bugs
11
+ - **Validation**: quality gates, security, accessibility
12
+ - **Research**: docs, debugging, codebase exploration
13
+ - **Operations**: releases, dev servers, health checks
14
14
 
15
15
  ## Usage
16
16