@geraldmaron/construct 1.0.6 → 1.0.8

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 (218) hide show
  1. package/README.md +39 -34
  2. package/bin/construct +1403 -96
  3. package/commands/build/feature.md +1 -1
  4. package/commands/build/fix.md +1 -1
  5. package/commands/design/access.md +1 -1
  6. package/commands/design/flow.md +1 -1
  7. package/commands/design/ui.md +1 -1
  8. package/commands/measure/experiment.md +1 -1
  9. package/commands/measure/metrics.md +1 -1
  10. package/commands/measure/results.md +1 -1
  11. package/commands/plan/api.md +1 -1
  12. package/commands/plan/challenge.md +1 -1
  13. package/commands/plan/decide.md +1 -1
  14. package/commands/plan/feature.md +1 -1
  15. package/commands/plan/requirements.md +1 -1
  16. package/commands/remember/context.md +1 -1
  17. package/commands/remember/handoff.md +1 -1
  18. package/commands/remember/runbook.md +1 -1
  19. package/commands/review/code.md +1 -1
  20. package/commands/review/quality.md +1 -1
  21. package/commands/review/security.md +1 -1
  22. package/commands/ship/ready.md +1 -1
  23. package/commands/ship/release.md +1 -1
  24. package/commands/ship/status.md +1 -1
  25. package/commands/understand/docs.md +1 -1
  26. package/commands/understand/this.md +1 -1
  27. package/commands/understand/why.md +1 -1
  28. package/commands/work/clean.md +1 -1
  29. package/commands/work/drive.md +1 -1
  30. package/commands/work/optimize-prompts.md +2 -2
  31. package/commands/work/parallel-review.md +1 -1
  32. package/db/schema/007_tags.sql +30 -0
  33. package/db/schema/008_skill_usage.sql +24 -0
  34. package/db/schema/009_scheduler.sql +14 -0
  35. package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +1 -1
  36. package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +1 -1
  37. package/examples/internal/roles/engineer/bad/speculative-abstraction.md +1 -1
  38. package/examples/internal/roles/engineer/golden/read-before-write.md +1 -1
  39. package/examples/internal/roles/qa/bad/coverage-theater.md +1 -1
  40. package/examples/internal/roles/qa/golden/regression-gate.md +1 -1
  41. package/examples/seed-observations/decisions.md +1 -1
  42. package/lib/audit-skills.mjs +3 -3
  43. package/lib/auto-docs.mjs +8 -8
  44. package/lib/boundary.mjs +126 -0
  45. package/lib/cache-strategy-google.js +26 -31
  46. package/lib/cli-commands.mjs +276 -122
  47. package/lib/comment-lint.mjs +139 -1
  48. package/lib/completions.mjs +1 -1
  49. package/lib/config/schema.mjs +15 -1
  50. package/lib/contracts/validate.mjs +371 -0
  51. package/lib/daemons/contract.mjs +210 -0
  52. package/lib/distill.mjs +22 -4
  53. package/lib/doc-stamp.mjs +48 -0
  54. package/lib/docs-verify.mjs +59 -6
  55. package/lib/doctor/cli.mjs +16 -1
  56. package/lib/doctor/index.mjs +3 -1
  57. package/lib/doctor/watchers/consistency.mjs +360 -0
  58. package/lib/doctor/watchers/mcp-protocol.mjs +232 -0
  59. package/lib/document-extract.mjs +420 -17
  60. package/lib/document-ingest.mjs +2 -0
  61. package/lib/embed/cli.mjs +124 -3
  62. package/lib/embed/daemon.mjs +43 -4
  63. package/lib/embed/docs-lifecycle.mjs +1 -1
  64. package/lib/embed/inbox.mjs +50 -3
  65. package/lib/embed/role-framing.mjs +3 -3
  66. package/lib/embed/scheduler.mjs +33 -5
  67. package/lib/env-config.mjs +9 -3
  68. package/lib/evaluator-optimizer.mjs +2 -3
  69. package/lib/extractors/calendar.mjs +173 -0
  70. package/lib/extractors/shared/drop-info.mjs +22 -0
  71. package/lib/extractors/transcript.mjs +0 -0
  72. package/lib/gates-audit.mjs +8 -2
  73. package/lib/git-hooks/prepare-commit-msg +1 -1
  74. package/lib/headhunt.mjs +2 -2
  75. package/lib/hooks/agent-tracker.mjs +6 -11
  76. package/lib/hooks/comment-lint.mjs +16 -0
  77. package/lib/hooks/guard-bash.mjs +35 -14
  78. package/lib/hooks/mcp-audit.mjs +14 -1
  79. package/lib/hooks/pre-compact.mjs +92 -5
  80. package/lib/hooks/pre-push-gate.mjs +41 -8
  81. package/lib/hooks/proactive-activation.mjs +0 -14
  82. package/lib/hooks/registry-sync.mjs +2 -2
  83. package/lib/hooks/rule-verifier.mjs +217 -0
  84. package/lib/hooks/session-optimize.mjs +2 -1
  85. package/lib/hooks/stop-notify.mjs +7 -6
  86. package/lib/host-capabilities.mjs +10 -1
  87. package/lib/hygiene/scan.mjs +141 -0
  88. package/lib/init-unified.mjs +73 -65
  89. package/lib/install/stage-project.mjs +4 -4
  90. package/lib/intake/classify.mjs +309 -58
  91. package/lib/intake/daemon.mjs +121 -0
  92. package/lib/intake/filesystem-queue.mjs +31 -6
  93. package/lib/intake/intake-config.mjs +2 -1
  94. package/lib/intake/postgres-queue.mjs +45 -3
  95. package/lib/intake/prepare.mjs +3 -1
  96. package/lib/intake/quarantine.mjs +205 -0
  97. package/lib/intake/session-prelude.mjs +7 -1
  98. package/lib/intake/traceability.mjs +90 -0
  99. package/lib/knowledge/postgres-search.mjs +132 -0
  100. package/lib/knowledge/research-store.mjs +2 -0
  101. package/lib/knowledge/search.mjs +14 -4
  102. package/lib/maintenance/cleanup.mjs +315 -0
  103. package/lib/mcp/memory-bridge.mjs +276 -0
  104. package/lib/mcp/server.mjs +11 -3
  105. package/lib/mcp/tools/skills.mjs +32 -11
  106. package/lib/mcp/tools/workflow.mjs +25 -0
  107. package/lib/mcp-catalog.json +12 -8
  108. package/lib/mcp-platform-config.mjs +16 -8
  109. package/lib/migrations/index.mjs +106 -0
  110. package/lib/migrations/v1-baseline.mjs +33 -0
  111. package/lib/model-registry.mjs +1 -1
  112. package/lib/observation-store.mjs +9 -4
  113. package/lib/opencode-runtime-plugin.mjs +1 -1
  114. package/lib/orchestration-policy.mjs +4 -4
  115. package/lib/outcomes/record.mjs +2 -0
  116. package/lib/overrides/resolver.mjs +3 -3
  117. package/lib/parity.mjs +4 -4
  118. package/lib/policy/engine.mjs +2 -2
  119. package/lib/profiles/lifecycle.mjs +1 -1
  120. package/lib/profiles/rebrand.mjs +46 -0
  121. package/lib/project-init-shared.mjs +12 -0
  122. package/lib/prompt-metadata.mjs +4 -4
  123. package/lib/provider-capabilities.js +20 -7
  124. package/lib/providers/auth-manager.mjs +58 -17
  125. package/lib/providers/circuit-breaker.mjs +14 -0
  126. package/lib/providers/contract.mjs +67 -3
  127. package/lib/providers/creds.mjs +219 -0
  128. package/lib/providers/scaffold.mjs +107 -0
  129. package/lib/reflect.mjs +49 -12
  130. package/lib/role-preload.mjs +5 -0
  131. package/lib/roles/catalog.mjs +1 -1
  132. package/lib/roles/manifest.mjs +2 -2
  133. package/lib/scheduler/index.mjs +112 -0
  134. package/lib/scheduler/solo.mjs +183 -0
  135. package/lib/server/index.mjs +29 -29
  136. package/lib/server/insights.mjs +14 -12
  137. package/lib/server/langfuse-login.mjs +58 -0
  138. package/lib/server/static/assets/index-ab25c707.js +1 -1
  139. package/lib/session-store.mjs +6 -4
  140. package/lib/setup.mjs +14 -3
  141. package/lib/{agent-contracts-enforce.mjs → specialist-contracts-enforce.mjs} +4 -4
  142. package/lib/{agent-contracts.mjs → specialist-contracts.mjs} +9 -8
  143. package/lib/{agents → specialists}/postconditions.mjs +3 -3
  144. package/lib/{agents → specialists}/schema.mjs +6 -6
  145. package/lib/status.mjs +13 -11
  146. package/lib/storage/backup.mjs +2 -2
  147. package/lib/tags/lifecycle.mjs +278 -0
  148. package/lib/tags/vocabulary.mjs +140 -0
  149. package/lib/telemetry/client.mjs +5 -1
  150. package/lib/telemetry/otel-tracer.mjs +184 -0
  151. package/lib/telemetry/skill-calls.mjs +73 -12
  152. package/lib/uninstall/uninstall.mjs +1 -1
  153. package/lib/update.mjs +1 -1
  154. package/lib/validator.mjs +57 -56
  155. package/lib/version.mjs +51 -0
  156. package/lib/worker/trace.mjs +5 -1
  157. package/lib/workflows/instantiate.mjs +320 -0
  158. package/package.json +16 -3
  159. package/personas/construct.md +5 -3
  160. package/platforms/claude/CLAUDE.md +1 -1
  161. package/rules/common/development-workflow.md +2 -1
  162. package/rules/common/no-fabrication.md +69 -0
  163. package/rules/common/review-before-change.md +58 -0
  164. package/scripts/{sync-agents.mjs → sync-specialists.mjs} +171 -68
  165. package/skills/ai/prompt-optimizer.md +3 -3
  166. package/skills/exploration/dependency-graph-reading.md +98 -0
  167. package/skills/exploration/tracer-bullet-method.md +71 -0
  168. package/skills/exploration/unknown-codebase-onboarding.md +91 -0
  169. package/skills/operating/change-management.md +91 -0
  170. package/skills/operating/incident-response.md +75 -0
  171. package/skills/operating/oncall-rotation.md +95 -0
  172. package/skills/operating/orchestration-reference.md +2 -2
  173. package/skills/strategy/competitive-landscape.md +75 -0
  174. package/skills/strategy/market-research-methods.md +87 -0
  175. package/skills/strategy/narrative-arc.md +77 -0
  176. package/skills/strategy/pricing-positioning.md +94 -0
  177. package/specialists/contracts.json +1002 -0
  178. package/specialists/contracts.schema.json +83 -0
  179. package/specialists/policy-inventory.json +160 -0
  180. package/{agents → specialists}/prompts/cx-accessibility.md +3 -1
  181. package/{agents → specialists}/prompts/cx-ai-engineer.md +3 -1
  182. package/{agents → specialists}/prompts/cx-architect.md +3 -1
  183. package/{agents → specialists}/prompts/cx-business-strategist.md +3 -1
  184. package/{agents → specialists}/prompts/cx-data-analyst.md +3 -1
  185. package/{agents → specialists}/prompts/cx-data-engineer.md +3 -1
  186. package/{agents → specialists}/prompts/cx-debugger.md +3 -1
  187. package/{agents → specialists}/prompts/cx-designer.md +3 -1
  188. package/{agents → specialists}/prompts/cx-devil-advocate.md +3 -1
  189. package/{agents → specialists}/prompts/cx-docs-keeper.md +4 -2
  190. package/{agents → specialists}/prompts/cx-engineer.md +4 -2
  191. package/{agents → specialists}/prompts/cx-evaluator.md +3 -1
  192. package/{agents → specialists}/prompts/cx-explorer.md +3 -1
  193. package/{agents → specialists}/prompts/cx-legal-compliance.md +3 -1
  194. package/{agents → specialists}/prompts/cx-operations.md +3 -1
  195. package/{agents → specialists}/prompts/cx-orchestrator.md +4 -2
  196. package/{agents → specialists}/prompts/cx-platform-engineer.md +3 -1
  197. package/{agents → specialists}/prompts/cx-product-manager.md +3 -1
  198. package/{agents → specialists}/prompts/cx-qa.md +3 -1
  199. package/{agents → specialists}/prompts/cx-rd-lead.md +3 -1
  200. package/{agents → specialists}/prompts/cx-release-manager.md +3 -1
  201. package/{agents → specialists}/prompts/cx-researcher.md +3 -1
  202. package/{agents → specialists}/prompts/cx-reviewer.md +4 -2
  203. package/{agents → specialists}/prompts/cx-security.md +3 -1
  204. package/{agents → specialists}/prompts/cx-sre.md +4 -2
  205. package/{agents → specialists}/prompts/cx-test-automation.md +3 -1
  206. package/{agents → specialists}/prompts/cx-trace-reviewer.md +5 -3
  207. package/{agents → specialists}/prompts/cx-ux-researcher.md +3 -1
  208. package/{agents → specialists}/registry.json +651 -418
  209. package/{agents → specialists}/role-manifests.json +8 -8
  210. package/templates/docs/construct_guide.md +6 -6
  211. package/templates/docs/skill-artifact.md +1 -1
  212. package/templates/provider-scaffold/health.test.mjs +30 -0
  213. package/templates/provider-scaffold/index.mjs +48 -0
  214. package/templates/workflows/cross-team-handoff.yml +85 -0
  215. package/templates/workflows/engineering-onboarding.yml +77 -0
  216. package/templates/workflows/new-feature.yml +53 -0
  217. package/agents/contracts.json +0 -387
  218. /package/{agents → specialists}/teams.json +0 -0
@@ -0,0 +1,320 @@
1
+ /**
2
+ * lib/workflows/instantiate.mjs — Workflow template instantiation engine.
3
+ *
4
+ * Resolves a workflow template (by id or path), validates required inputs,
5
+ * substitutes ${variable_name} placeholders in all string fields, and returns
6
+ * the artifact list and beads item list that the caller (CLI) should create.
7
+ *
8
+ * Key behaviors:
9
+ * - Template lookup: templates/workflows/<id>.yml relative to the nearest
10
+ * package root, or an explicit absolute/relative path.
11
+ * - Variable substitution: every ${name} token in string fields is replaced
12
+ * with the caller-supplied value for that name. Unresolved tokens are left
13
+ * as-is and do not cause an error unless the variable is required.
14
+ * - Artifact copy: for each artifact, if templates/docs/<template> exists the
15
+ * file is copied to the output path with a title comment prepended. If the
16
+ * template is not found, a stub markdown file is written instead.
17
+ * - Beads items are NOT created by this module. The returned beadsItems array
18
+ * is handed to the CLI, which calls the beads client.
19
+ * - File writes are skipped when cwd is null (dry-run mode).
20
+ *
21
+ * Non-obvious constraints:
22
+ * - YAML is parsed with a minimal line-by-line parser; complex YAML (anchors,
23
+ * multi-document, block scalars with special indicators) is not supported.
24
+ * The workflow schema is intentionally kept simple to stay within this limit.
25
+ * - The function is synchronous except for file I/O which uses the sync fs API,
26
+ * so callers do not need to await anything.
27
+ */
28
+
29
+ import fs from "node:fs";
30
+ import path from "node:path";
31
+ import { fileURLToPath } from "node:url";
32
+
33
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
34
+
35
+ // Walk upward from startDir until we find package.json, or fall back to startDir.
36
+ function findPackageRoot(startDir) {
37
+ let dir = startDir;
38
+ for (let i = 0; i < 12; i++) {
39
+ if (fs.existsSync(path.join(dir, "package.json"))) return dir;
40
+ const parent = path.dirname(dir);
41
+ if (parent === dir) break;
42
+ dir = parent;
43
+ }
44
+ return startDir;
45
+ }
46
+
47
+ // Replace every ${name} token in a string with its value from the vars map.
48
+ function substitute(str, vars) {
49
+ if (typeof str !== "string") return str;
50
+ return str.replace(/\$\{([^}]+)\}/g, (_, name) => {
51
+ return Object.prototype.hasOwnProperty.call(vars, name) ? vars[name] : `\${${name}}`;
52
+ });
53
+ }
54
+
55
+ // Apply substitution recursively to all string values in an object or array.
56
+ function substituteDeep(node, vars) {
57
+ if (typeof node === "string") return substitute(node, vars);
58
+ if (Array.isArray(node)) return node.map((item) => substituteDeep(item, vars));
59
+ if (node !== null && typeof node === "object") {
60
+ const out = {};
61
+ for (const [k, v] of Object.entries(node)) out[k] = substituteDeep(v, vars);
62
+ return out;
63
+ }
64
+ return node;
65
+ }
66
+
67
+ // Minimal YAML parser. Handles the flat structure used in workflow templates:
68
+ // top-level scalar keys, simple lists (- key: value), and nested objects.
69
+ // Returns a plain JS object. Does not handle anchors, multi-line strings with
70
+ // block indicators, or multi-document streams.
71
+ function parseYaml(text) {
72
+ const lines = text.split("\n");
73
+ const root = {};
74
+ const stack = [{ obj: root, indent: -1 }];
75
+
76
+ function currentFrame() {
77
+ return stack[stack.length - 1];
78
+ }
79
+
80
+ function getIndent(line) {
81
+ let i = 0;
82
+ while (i < line.length && line[i] === " ") i++;
83
+ return i;
84
+ }
85
+
86
+ function parseValue(raw) {
87
+ const v = raw.trim();
88
+ if (v === "true") return true;
89
+ if (v === "false") return false;
90
+ if (v === "null" || v === "~" || v === "") return null;
91
+ const n = Number(v);
92
+ if (!isNaN(n) && v !== "") return n;
93
+ // Strip surrounding quotes.
94
+ if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
95
+ return v.slice(1, -1);
96
+ }
97
+ return v;
98
+ }
99
+
100
+ let i = 0;
101
+ while (i < lines.length) {
102
+ const rawLine = lines[i];
103
+ i++;
104
+
105
+ // Skip blank lines and comments.
106
+ const trimmed = rawLine.trim();
107
+ if (!trimmed || trimmed.startsWith("#")) continue;
108
+
109
+ const indent = getIndent(rawLine);
110
+
111
+ // Pop stack frames that are deeper than the current indent.
112
+ while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
113
+ stack.pop();
114
+ }
115
+
116
+ const frame = currentFrame();
117
+
118
+ if (trimmed.startsWith("- ")) {
119
+ // List item with inline key:value pairs, or a plain scalar.
120
+ const itemStr = trimmed.slice(2);
121
+ const item = {};
122
+ // Parse inline key: value pairs within this list item by consuming
123
+ // continuation lines at a deeper indent level.
124
+ const itemIndent = indent + 2;
125
+ const firstColon = itemStr.indexOf(": ");
126
+ if (firstColon !== -1) {
127
+ const k = itemStr.slice(0, firstColon).trim();
128
+ const v = itemStr.slice(firstColon + 2).trim();
129
+ item[k] = parseValue(v);
130
+
131
+ // Consume continuation lines at deeper indent.
132
+ while (i < lines.length) {
133
+ const peekRaw = lines[i];
134
+ const peekTrimmed = peekRaw.trim();
135
+ if (!peekTrimmed || peekTrimmed.startsWith("#")) { i++; continue; }
136
+ const peekIndent = getIndent(peekRaw);
137
+ if (peekIndent <= indent) break;
138
+
139
+ if (peekTrimmed.startsWith("- ")) {
140
+ // Nested list under this item key — assign to the last key in the item object.
141
+ const lastKey = Object.keys(item)[Object.keys(item).length - 1];
142
+ if (!Array.isArray(item[lastKey])) item[lastKey] = [];
143
+ item[lastKey].push(parseValue(peekTrimmed.slice(2)));
144
+ i++;
145
+ } else {
146
+ const subColon = peekTrimmed.indexOf(": ");
147
+ if (subColon !== -1) {
148
+ const sk = peekTrimmed.slice(0, subColon).trim();
149
+ const sv = peekTrimmed.slice(subColon + 2).trim();
150
+ item[sk] = parseValue(sv);
151
+ i++;
152
+ } else if (peekTrimmed.endsWith(":")) {
153
+ const sk = peekTrimmed.slice(0, -1).trim();
154
+ item[sk] = {};
155
+ i++;
156
+ } else {
157
+ break;
158
+ }
159
+ }
160
+ }
161
+
162
+ // Ensure the parent object has an array for this key.
163
+ const parentKey = frame.listKey;
164
+ if (parentKey && Array.isArray(frame.obj[parentKey])) {
165
+ frame.obj[parentKey].push(item);
166
+ } else {
167
+ // No listKey on the frame — fall back to frame.currentArray, which is
168
+ // set when a bare "key:" line initialises a new array in the parent object.
169
+ const parentArr = frame.currentArray;
170
+ if (Array.isArray(parentArr)) {
171
+ parentArr.push(item);
172
+ } else {
173
+ // Fallback: push to the last array-type property of the parent obj.
174
+ const keys = Object.keys(frame.obj);
175
+ for (let ki = keys.length - 1; ki >= 0; ki--) {
176
+ if (Array.isArray(frame.obj[keys[ki]])) {
177
+ frame.obj[keys[ki]].push(item);
178
+ break;
179
+ }
180
+ }
181
+ }
182
+ }
183
+ } else {
184
+ // Plain scalar list item.
185
+ const lastKey = frame.listKey;
186
+ if (lastKey && Array.isArray(frame.obj[lastKey])) {
187
+ frame.obj[lastKey].push(parseValue(itemStr));
188
+ } else if (Array.isArray(frame.currentArray)) {
189
+ frame.currentArray.push(parseValue(itemStr));
190
+ }
191
+ }
192
+ } else if (trimmed.endsWith(":")) {
193
+ // Bare key — value will be an object or array on following lines.
194
+ const key = trimmed.slice(0, -1).trim();
195
+ frame.obj[key] = [];
196
+ stack.push({ obj: frame.obj, indent, listKey: key, currentArray: frame.obj[key] });
197
+ } else {
198
+ const colon = trimmed.indexOf(": ");
199
+ if (colon !== -1) {
200
+ const key = trimmed.slice(0, colon).trim();
201
+ const val = trimmed.slice(colon + 2);
202
+ frame.obj[key] = parseValue(val);
203
+ }
204
+ }
205
+ }
206
+
207
+ return root;
208
+ }
209
+
210
+ // Locate a workflow template by id (looks in templates/workflows/<id>.yml) or
211
+ // by explicit path. Returns the absolute resolved path.
212
+ function resolveTemplatePath(templateIdOrPath, packageRoot) {
213
+ if (path.isAbsolute(templateIdOrPath) || templateIdOrPath.includes(path.sep)) {
214
+ return path.resolve(templateIdOrPath);
215
+ }
216
+ // Strip .yml extension if provided.
217
+ const id = templateIdOrPath.replace(/\.ya?ml$/, "");
218
+ return path.join(packageRoot, "templates", "workflows", `${id}.yml`);
219
+ }
220
+
221
+ // Resolve the variables map by merging declared defaults with caller-supplied
222
+ // values, then check for missing required inputs.
223
+ function resolveVars(inputs, suppliedValues) {
224
+ if (!Array.isArray(inputs)) return suppliedValues || {};
225
+ const vars = {};
226
+
227
+ for (const input of inputs) {
228
+ if (Object.prototype.hasOwnProperty.call(suppliedValues || {}, input.name)) {
229
+ vars[input.name] = String(suppliedValues[input.name]);
230
+ } else if (input.default != null) {
231
+ vars[input.name] = String(input.default);
232
+ } else if (input.required) {
233
+ throw new Error(`Required workflow input "${input.name}" was not supplied.`);
234
+ }
235
+ }
236
+
237
+ // Also pass through any extra values the caller provided.
238
+ for (const [k, v] of Object.entries(suppliedValues || {})) {
239
+ if (!Object.prototype.hasOwnProperty.call(vars, k)) vars[k] = String(v);
240
+ }
241
+
242
+ return vars;
243
+ }
244
+
245
+ // Write an artifact to disk. If the source doc template exists, copy it and
246
+ // prepend a title line. Otherwise write a minimal stub.
247
+ function writeArtifact(outputPath, docTemplatePath, title) {
248
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
249
+
250
+ if (fs.existsSync(docTemplatePath)) {
251
+ const original = fs.readFileSync(docTemplatePath, "utf8");
252
+ const withTitle = title
253
+ ? original.replace(/^# .*$/m, `# ${title}`)
254
+ : original;
255
+ fs.writeFileSync(outputPath, withTitle, "utf8");
256
+ } else {
257
+ const stub = `# ${title || path.basename(outputPath, ".md")}\n\n<!-- stub: template not found -->\n`;
258
+ fs.writeFileSync(outputPath, stub, "utf8");
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Instantiate a workflow template.
264
+ *
265
+ * @param {string} templateId - Template id (e.g. "new-feature") or path to a .yml file.
266
+ * @param {Object} inputs - Key/value map of variable values for the template.
267
+ * @param {Object} options
268
+ * @param {string|null} options.cwd - Project root for output paths. Pass null for dry-run (no files written).
269
+ * @returns {{ artifacts: Array<{path: string, template: string, title: string}>, beadsItems: Array<{type: string, title: string, linkTo: string|undefined}> }}
270
+ */
271
+ export function instantiateWorkflow(templateId, inputs, { cwd } = {}) {
272
+ const packageRoot = findPackageRoot(__dirname);
273
+
274
+ const templatePath = resolveTemplatePath(templateId, packageRoot);
275
+ if (!fs.existsSync(templatePath)) {
276
+ throw new Error(`Workflow template not found: ${templatePath}`);
277
+ }
278
+
279
+ const raw = fs.readFileSync(templatePath, "utf8");
280
+ const template = parseYaml(raw);
281
+
282
+ if (!template.id || !template.title || !Array.isArray(template.artifacts)) {
283
+ throw new Error(
284
+ `Workflow template at ${templatePath} is missing required fields (id, title, artifacts).`
285
+ );
286
+ }
287
+
288
+ const vars = resolveVars(template.inputs, inputs);
289
+
290
+ // Substitute variables in the full template object.
291
+ const resolved = substituteDeep(template, vars);
292
+
293
+ const docsTemplatesDir = path.join(packageRoot, "templates", "docs");
294
+
295
+ const artifacts = [];
296
+ for (const artifact of resolved.artifacts) {
297
+ const outputPath = cwd ? path.join(cwd, artifact.path) : artifact.path;
298
+ const docTemplatePath = path.join(docsTemplatesDir, artifact.template);
299
+ const title = artifact.title || "";
300
+
301
+ if (cwd) {
302
+ writeArtifact(outputPath, docTemplatePath, title);
303
+ }
304
+
305
+ artifacts.push({
306
+ path: artifact.path,
307
+ template: artifact.template,
308
+ title,
309
+ });
310
+ }
311
+
312
+ const beadsItems = (resolved.beads_items || []).map((item) => ({
313
+ type: item.type,
314
+ title: item.title,
315
+ ...(item.link_to != null ? { linkTo: item.link_to } : {}),
316
+ ...(item.depends_on_prior ? { dependsOnPrior: true } : {}),
317
+ }));
318
+
319
+ return { artifacts, beadsItems };
320
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geraldmaron/construct",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "type": "module",
5
5
  "description": "Construct — agent orchestration layer for OpenCode, Claude Code, and other coding surfaces",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "bin/construct",
11
11
  "bin/construct-postinstall.mjs",
12
12
  "lib/**",
13
- "agents/**",
13
+ "specialists/**",
14
14
  "examples/**",
15
15
  "personas/**",
16
16
  "commands/**",
@@ -20,7 +20,7 @@
20
20
  "templates/**",
21
21
  "templates/distribution/**",
22
22
  "db/**",
23
- "scripts/sync-agents.mjs",
23
+ "scripts/sync-specialists.mjs",
24
24
  "README.md",
25
25
  ".env.example"
26
26
  ],
@@ -50,12 +50,16 @@
50
50
  "docs:dev": "npm --prefix apps/docs run dev",
51
51
  "docs:build": "npm --prefix apps/docs run build",
52
52
  "lint:comments": "node ./bin/construct lint:comments",
53
+ "lint:contracts": "node ./bin/construct lint:contracts",
54
+ "lint:agents": "node ./bin/construct lint:agents",
55
+ "build:sea": "esbuild bin/construct --bundle --platform=node --target=node20 --packages=external --outfile=dist/construct-bundle.cjs 2>&1 || (echo 'esbuild not found — install with: npm install -g esbuild' && exit 1)",
53
56
  "lint:prose": "node scripts/lint-prose.mjs",
54
57
  "lint:profiles": "node scripts/lint-profiles.mjs",
55
58
  "learning:status": "node scripts/learning-status.mjs",
56
59
  "lint:templates": "node scripts/lint-commits-pr.mjs",
57
60
  "eval:routing": "node scripts/eval/score-intent-classifier.mjs",
58
61
  "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",
62
+ "release:gate": "node --test tests/functional/release-gate.functional.test.mjs",
59
63
  "release:preflight": "node scripts/pre-release-check.mjs",
60
64
  "release:preflight:no-auth": "node scripts/pre-release-check.mjs --skip-auth",
61
65
  "npm:publish": "npm run release:check && npm publish --access public",
@@ -65,8 +69,17 @@
65
69
  "@huggingface/transformers": "^4.2.0",
66
70
  "@modelcontextprotocol/sdk": "^1.12.0",
67
71
  "@xenova/transformers": "^2.0.1",
72
+ "node-webvtt": "^1.9.3",
68
73
  "postgres": "^3.4.9"
69
74
  },
75
+ "optionalDependencies": {
76
+ "@opentelemetry/api": "^1.9.0",
77
+ "@opentelemetry/sdk-trace-node": "^1.25.0",
78
+ "@opentelemetry/exporter-trace-otlp-http": "^0.52.0",
79
+ "@opentelemetry/resources": "^1.25.0",
80
+ "@opentelemetry/semantic-conventions": "^1.25.0",
81
+ "@opentelemetry/core": "^1.25.0"
82
+ },
70
83
  "overrides": {
71
84
  "express-rate-limit": "8.5.1",
72
85
  "protobufjs": "^8.4.2"
@@ -2,10 +2,12 @@
2
2
  personas/construct.md. Construct persona prompt.
3
3
 
4
4
  Defines the single user-facing AI interface and its session-start behavior,
5
- routing rules, approval boundaries, and output contract. Loaded by sync-agents
5
+ routing rules, approval boundaries, and output contract. Loaded by sync-specialists
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 user talks only to you; internal routing and specialist dispatch are implementation detail.
9
+
10
+ **Anti-fabrication contract**: every load-bearing claim cites a verifiable source. Missing source becomes `unknown` or `[unverified]`. Specialists tailor; the persona never weakens. See `rules/common/no-fabrication.md`.
9
11
 
10
12
  ## Start of every session
11
13
 
@@ -40,7 +42,7 @@ Devil's advocate is mandatory for new architectural directions, AI/agent workflo
40
42
  `routeRequest` returns three artifacts; honor all three:
41
43
 
42
44
  1. **Gates**. `framingChallenge`, `externalResearch`, `docAuthoring`
43
- 2. **Contract chain**. typed handoffs from `agents/contracts.json`. Call `agent_contract` MCP tool at handoff.
45
+ 2. **Contract chain**. typed handoffs from `specialists/contracts.json`. Call `agent_contract` MCP tool at handoff.
44
46
  3. **Specialist sequence**. dispatch plan with ordering/parallel markers.
45
47
 
46
48
  Before DONE: postconditions met · sources cited · framing logged · ADRs have Rejected alternatives.
@@ -1,6 +1,6 @@
1
1
  # Construct: Claude Code Integration
2
2
 
3
- This project uses Construct. Personas and specialists are defined in `agents/registry.json` and synced to Claude Code via `construct sync`.
3
+ This project uses Construct. Personas and specialists are defined in `specialists/registry.json` and synced to Claude Code via `construct sync`.
4
4
 
5
5
  ## Workflow roles
6
6
 
@@ -8,8 +8,9 @@ git operations. References testing.md, code-review.md, git-workflow.md.
8
8
 
9
9
  ## Feature Implementation Workflow
10
10
 
11
- 0. **Research & Reuse** _(mandatory before any new implementation)_
11
+ 0. **Research & Reuse** _(mandatory before any new implementation or artifact)_
12
12
  - **Search existing code first:** Look for existing implementations, templates, and patterns before writing anything new.
13
+ - **Search existing artifacts:** Before authoring a doc, rule, template, strategy, PRD, ADR, or RFC, audit what already exists. See [review-before-change.md](review-before-change.md) for the audit checklist. Extend or supersede; do not duplicate.
13
14
  - **Check docs:** Confirm API behavior, package usage, and version-specific details before implementing.
14
15
  - **Check package registries:** npm, PyPI, crates.io before writing utility code.
15
16
  - Prefer adopting a proven approach over writing net-new code.
@@ -0,0 +1,69 @@
1
+ <!--
2
+ rules/common/no-fabrication.md: canonical anti-fabrication policy for Construct.
3
+
4
+ Defines the trust contract between operators and the system: outputs stick to
5
+ source, gaps stay visible, and confidence reflects evidence. Applies to every
6
+ specialist, every artifact, and every summary — intake processing, document
7
+ evaluation, knowledge writing, plan drafting, review verdicts, handoffs.
8
+
9
+ Sibling rules: research.md (evidence hierarchy), framing.md (execution
10
+ artifacts are not sources), comments.md (banned voice patterns).
11
+ -->
12
+ # No-Fabrication Policy
13
+
14
+ Fabrication is the single largest threat to trust in an agent system. A persona that invents a customer quote, sharpens a vague signal into a confident assertion, or papers over a gap with plausible-sounding prose corrupts every artifact downstream. This rule applies to **every output** Construct produces: intake summaries, classification rationales, PRDs, ADRs, RFCs, knowledge notes, handoffs, review verdicts, plan entries, beads issues, MCP tool responses, dashboard text.
15
+
16
+ ## 1. Stick to source
17
+
18
+ - Every load-bearing claim must trace to a source the reader can re-verify. Cite with `[source: path#anchor]`, `[source: intake-<id>]`, `[source: bd-<id>]`, `[source: <commit-sha>]`, or a fetched URL with the date the fetch happened.
19
+ - If a fact is not in the source you have access to, write `unknown` or `[unverified]`. Do not paper over the gap with prose that sounds confident.
20
+ - Never invent: customer names, quotes, ticket IDs, commit hashes, percentages, dates, file paths, function names, API surfaces, dependency names, version numbers.
21
+
22
+ ## 2. Preserve ambiguity
23
+
24
+ - A vague signal stays vague in the artifact. If the intake says "users are frustrated with the dashboard," the PRD says "users are frustrated with the dashboard [source: intake-xxx]" — not "users want sub-200ms p95 dashboard latency."
25
+ - When a source is ambiguous, list the possible interpretations and tag the chosen reading as inference, not observation.
26
+ - Do not promote inference to observation. `X happened` requires evidence the event happened. `X likely means Y` requires the inference to be marked as such.
27
+
28
+ ## 3. Distinguish observation from inference
29
+
30
+ - Observation: directly verifiable from the source. Format: bare statement with citation.
31
+ - Inference: a conclusion drawn from one or more observations. Format: prefixed with `inference:` or `[inferred]`, and the supporting observations must be cited.
32
+ - Speculation (no supporting observation): not allowed in artifacts. If you must raise a hypothesis, write it as a question, not an assertion.
33
+
34
+ ## 4. Calibrate confidence honestly
35
+
36
+ - Confidence in artifacts and observation records reflects evidence strength, not authorial conviction. Single-source claims, secondary sources, and inferred conclusions get lower confidence than primary-source observations.
37
+ - Words that smuggle confidence without evidence — `clearly`, `obviously`, `undoubtedly`, `definitely`, `certainly`, `surely` — are banned from artifact bodies. Replace with an explicit citation or remove.
38
+ - Quantitative claims (`30% faster`, `5x improvement`, `90th percentile latency`) require an inline source. Hand-wave percentages are fabrication.
39
+
40
+ ## 5. Don't mind-read users or customers
41
+
42
+ - `Users want X`, `customers expect Y`, `everyone agrees Z` require a citation to the source where the user or customer said so (interview transcript, support ticket, survey, observed behavior). Without the citation, the claim is fabrication.
43
+ - Speculative product-vision language is allowed only in clearly-marked hypothesis or alternative sections, never in observation or requirements sections.
44
+
45
+ ## 6. Surface uncertainty as a question, not an assertion
46
+
47
+ - When you don't know, ask. A persona that doesn't know whether a regression touched the auth flow asks for the diff before claiming it did. A reviewer who can't tell if a test covers a branch asks for the coverage report.
48
+ - "I don't have access to X" is a valid output. Pretending to have access is fabrication.
49
+
50
+ ## 7. Embellishment is fabrication
51
+
52
+ - Adding plausible-but-unverified detail to a summary, classification rationale, or handoff is fabrication, even when the additions feel innocuous.
53
+ - The intake classifier's `rationale` field lists the exact keywords matched. Downstream personas must not embellish the rationale into a richer narrative the classifier did not produce.
54
+ - Session summaries derive from observable session artifacts (context, observations, commits). Do not include events the transcript does not record.
55
+
56
+ ## 8. When in doubt, say less
57
+
58
+ - A shorter, accurate artifact beats a longer artifact padded with unverified plausibility.
59
+ - Sections that lack source material should be omitted, not filled with speculation. An ADR without a "Rejected alternatives" section because no alternatives were considered is better than an ADR with invented alternatives.
60
+
61
+ ## Enforcement
62
+
63
+ - `lib/comment-lint.mjs` enforces a subset of these patterns on artifact paths (`docs/prd/**`, `docs/adr/**`, `docs/rfc/**`, `docs/research/**`, `.cx/knowledge/**`, `.cx/handoffs/**`, `.cx/research/**`). PostToolUse warns; `npm run lint:comments`, `construct lint:comments`, and the release gate block.
64
+ - `specialists/contracts.json` postconditions check structural requirements (mandatory sections, intake traceability, citation density). `lib/contracts/validate.mjs#validateHandoff` blocks handoffs that fail validation when `CONSTRUCT_CONTRACT_ENFORCEMENT=block`.
65
+ - `construct intake done <id> --output=<path>` stamps `intake_id`, `intake_confidence`, and `intake_rationale` into the artifact's frontmatter so every intake-derived artifact carries verifiable provenance.
66
+
67
+ ## Bypass
68
+
69
+ There is no bypass. If a check is wrong, fix the check (`rules/common/no-fabrication.md`, the pattern bank in `lib/comment-lint.mjs`, or the contract postcondition). Do not work around the gate.
@@ -0,0 +1,58 @@
1
+ <!--
2
+ rules/common/review-before-change.md: audit the current state before authoring anything new.
3
+
4
+ Applies to every specialist on every platform. Pairs with framing.md (frame the
5
+ problem) and development-workflow.md step 0 (research and reuse before code).
6
+ This rule extends the same discipline to artifacts: docs, strategy, PRDs, ADRs,
7
+ RFCs, rules, templates, and any other durable file. Read before scaffolding.
8
+ -->
9
+ # Review Before Change
10
+
11
+ Before creating or rewriting a durable artifact, check what already exists.
12
+
13
+ This rule exists because the most common waste in Construct is duplicated effort: a new strategy doc landing next to a PRD that already covers it, a new rule re-stating a section of `framing.md`, a new diagram drawn while an equivalent one sits two folders away. The fix is not more process. It is a fifteen-second audit before the first write.
14
+
15
+ ## When this applies
16
+
17
+ Any time the work would produce or significantly rewrite one of:
18
+
19
+ - A doc under `docs/`, including concepts, cookbook, providers, runbooks, ADRs, RFCs, PRDs, memos, runbooks, decisions.
20
+ - A rule under `rules/` or a skill under `skills/`.
21
+ - A template under `templates/`.
22
+ - A top-level repo doc (README, CHANGELOG, STRATEGY, AGENTS, CONTRIBUTING).
23
+ - A profile under `profiles/` or a registered agent.
24
+
25
+ It does not apply to local-only working files (`plan.md`, `.cx/` state, draft scratch).
26
+
27
+ ## The audit
28
+
29
+ Before writing, answer all three:
30
+
31
+ 1. **Does an artifact for this already exist?** Search by topic, not filename. `grep -rli "<topic>" docs/ rules/ skills/` is the floor. If something close exists, the default is to extend or supersede, not to create a sibling.
32
+ 2. **Is there a template or canonical shape for this artifact type?** Check `templates/docs/` and the matching `skills/docs/*-workflow.md`. If a shape exists, use it; do not invent a new structure.
33
+ 3. **What is the canonical home for this kind of content?** A strategy doc belongs in one place. A new ADR does not live in `docs/notes/`. If you have to think about where to put it, the location is wrong.
34
+
35
+ If any answer surfaces an existing artifact, you have two valid moves:
36
+
37
+ - **Extend it.** Add the missing section to the existing file.
38
+ - **Supersede it.** Replace the old artifact and mark it superseded. Do not leave two competing sources of truth.
39
+
40
+ Creating a new sibling artifact "because the old one didn't quite fit" is the failure mode this rule prevents. If the old one doesn't fit, fix it.
41
+
42
+ ## Documenting the audit
43
+
44
+ For artifacts that go through a review cycle (strategy, PRD, ADR, RFC), the framing or problem-statement section should briefly note what existing artifacts were reviewed and why a new one is warranted. One sentence is enough. The point is to leave evidence that the audit happened.
45
+
46
+ ## Anti-patterns
47
+
48
+ - Creating `docs/notes/strategy.md` because you didn't notice `STRATEGY.md` at the root.
49
+ - Writing a new rule that paraphrases an existing one.
50
+ - Adding a new template alongside `templates/docs/strategy.md` because the existing one "needed a couple of tweaks."
51
+ - Drafting an ADR for a decision already captured in an existing ADR.
52
+ - Producing a recipe in `docs/cookbook/` for a workflow already documented in `docs/concepts/`.
53
+
54
+ ## Related
55
+
56
+ - `rules/common/framing.md`: frame the underlying problem before reaching for inputs.
57
+ - `rules/common/development-workflow.md`: step 0 covers the same discipline for code.
58
+ - `rules/common/doc-ownership.md`: which specialist owns which artifact type.