@geraldmaron/construct 1.0.2 → 1.0.4

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 (97) hide show
  1. package/.env.example +1 -1
  2. package/README.md +4 -4
  3. package/agents/prompts/cx-ai-engineer.md +6 -26
  4. package/agents/prompts/cx-architect.md +1 -0
  5. package/agents/prompts/cx-business-strategist.md +2 -0
  6. package/agents/prompts/cx-data-analyst.md +6 -26
  7. package/agents/prompts/cx-docs-keeper.md +1 -31
  8. package/agents/prompts/cx-explorer.md +1 -0
  9. package/agents/prompts/cx-orchestrator.md +40 -112
  10. package/agents/prompts/cx-platform-engineer.md +2 -22
  11. package/agents/prompts/cx-product-manager.md +2 -1
  12. package/agents/prompts/cx-qa.md +0 -20
  13. package/agents/prompts/cx-rd-lead.md +2 -0
  14. package/agents/prompts/cx-researcher.md +77 -31
  15. package/agents/prompts/cx-security.md +11 -49
  16. package/agents/prompts/cx-sre.md +9 -43
  17. package/agents/prompts/cx-ux-researcher.md +1 -0
  18. package/agents/role-manifests.json +4 -4
  19. package/bin/construct +72 -11
  20. package/bin/construct-postinstall.mjs +1 -1
  21. package/db/schema/004_recommendations.sql +46 -0
  22. package/db/schema/005_strategy.sql +21 -0
  23. package/lib/auto-docs.mjs +1 -2
  24. package/lib/beads-automation.mjs +16 -7
  25. package/lib/bootstrap/resources.mjs +2 -2
  26. package/lib/cli-commands.mjs +8 -2
  27. package/lib/document-ingest.mjs +6 -5
  28. package/lib/embed/cli.mjs +16 -3
  29. package/lib/embed/conflict-detection.mjs +26 -9
  30. package/lib/embed/customer-profiles.mjs +38 -18
  31. package/lib/embed/daemon.mjs +59 -50
  32. package/lib/embed/inbox.mjs +30 -0
  33. package/lib/embed/recommendation-store.mjs +214 -15
  34. package/lib/embed/workspaces.mjs +53 -18
  35. package/lib/evaluator-optimizer.mjs +0 -2
  36. package/lib/features.mjs +11 -0
  37. package/lib/gates-audit.mjs +3 -3
  38. package/lib/health-check.mjs +3 -5
  39. package/lib/hooks/pre-compact.mjs +3 -0
  40. package/lib/hooks/read-tracker.mjs +10 -101
  41. package/lib/host-capabilities.mjs +90 -1
  42. package/lib/init-unified.mjs +119 -3
  43. package/lib/init-update.mjs +246 -131
  44. package/lib/install/first-invocation.mjs +4 -4
  45. package/lib/intake/queue.mjs +1 -1
  46. package/lib/integrations/intake-integrations.mjs +4 -5
  47. package/lib/intent-classifier.mjs +1 -0
  48. package/lib/knowledge/layout.mjs +10 -0
  49. package/lib/knowledge/rag.mjs +16 -0
  50. package/lib/mcp/tools/telemetry.mjs +30 -78
  51. package/lib/model-cheapest-provider.mjs +231 -0
  52. package/lib/model-router.mjs +68 -9
  53. package/lib/ollama-manager.mjs +1 -1
  54. package/lib/opencode-telemetry.mjs +4 -5
  55. package/lib/orchestration-policy.mjs +9 -0
  56. package/lib/parity.mjs +124 -21
  57. package/lib/project-profile.mjs +1 -1
  58. package/lib/prompt-composer.js +106 -29
  59. package/lib/read-tracker-store.mjs +149 -0
  60. package/lib/roles/catalog.mjs +133 -0
  61. package/lib/roles/preference.mjs +74 -0
  62. package/lib/server/index.mjs +109 -47
  63. package/lib/server/insights.mjs +1 -1
  64. package/lib/server/telemetry-login.mjs +17 -25
  65. package/lib/service-manager.mjs +32 -24
  66. package/lib/services/local-postgres.mjs +15 -0
  67. package/lib/services/telemetry-backend.mjs +2 -3
  68. package/lib/setup-prompts.mjs +2 -2
  69. package/lib/setup.mjs +55 -46
  70. package/lib/status.mjs +56 -8
  71. package/lib/storage/backend.mjs +12 -2
  72. package/lib/storage/postgres-backup.mjs +1 -1
  73. package/lib/strategy-store.mjs +371 -0
  74. package/lib/telemetry/backends/local.mjs +6 -4
  75. package/lib/telemetry/client.mjs +185 -0
  76. package/lib/telemetry/ingest.mjs +13 -5
  77. package/lib/telemetry/team-rollup.mjs +9 -2
  78. package/lib/uninstall/uninstall.mjs +1 -1
  79. package/lib/worker/trace.mjs +17 -27
  80. package/package.json +5 -2
  81. package/rules/common/research.md +44 -12
  82. package/skills/docs/backlog-proposal-workflow.md +2 -2
  83. package/skills/docs/customer-profile-workflow.md +1 -1
  84. package/skills/docs/evidence-ingest-workflow.md +5 -5
  85. package/skills/docs/prfaq-workflow.md +1 -1
  86. package/skills/docs/product-intelligence-review.md +1 -1
  87. package/skills/docs/product-intelligence-workflow.md +3 -3
  88. package/skills/docs/product-signal-workflow.md +48 -18
  89. package/skills/docs/research-workflow.md +26 -14
  90. package/skills/docs/strategy-workflow.md +36 -0
  91. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  92. package/skills/roles/researcher.md +28 -15
  93. package/skills/routing.md +8 -1
  94. package/templates/docs/construct_guide.md +2 -2
  95. package/templates/docs/research-brief.md +63 -9
  96. package/templates/docs/strategy.md +36 -0
  97. package/templates/homebrew/construct.rb +7 -7
@@ -1,168 +1,283 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * lib/init-update.mjs — update existing projects with latest AGENTS.md template.
4
- *
5
- * For projects that already have AGENTS.md but need to sync with the latest
6
- * Construct operating rules and CI enforcement.
7
- *
8
- * Usage:
9
- * node lib/init-update.mjs [--dry-run] [--cwd=path]
10
- * construct init:update [--dry-run]
3
+ * lib/init-update.mjs — non-destructive project-standard updates and template conflict resolution.
4
+ *
5
+ * `construct init:update` helps projects adopt current Construct standards without replacing
6
+ * user-managed instruction files. Proposed updates are written under .cx/proposals/ for
7
+ * manual review and merge.
8
+ *
9
+ * checkTemplateConflicts(targetDir) compares project templates against the construct install's
10
+ * templates/docs/ directory. resolveTemplateConflict(conflict, resolution, targetDir) applies
11
+ * one of three resolutions: keep-project, use-construct, or move-to-cx-override.
11
12
  */
12
13
 
13
14
  import fs from "node:fs";
14
15
  import path from "node:path";
15
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
16
+ import crypto from "node:crypto";
17
+ import { constructDir } from "./paths.mjs";
16
18
 
17
19
  const args = process.argv.slice(2);
18
20
  const dryRun = args.includes("--dry-run");
19
- const cwdArg = args.find(arg => arg.startsWith("--cwd="));
21
+ const cwdArg = args.find((arg) => arg.startsWith("--cwd="));
20
22
  const cwd = cwdArg ? path.resolve(cwdArg.split("=")[1]) : process.cwd();
21
23
 
22
- console.log(`📝 Updating project documentation in ${cwd}`);
24
+ const REQUIRED_AGENT_SECTIONS = [
25
+ "Operating hierarchy",
26
+ "Start-of-session rules",
27
+ "Maintenance rules",
28
+ "End-of-session rules",
29
+ "Verification rules",
30
+ ];
23
31
 
24
- // Load the AGENTS.md template from project-init-shared
25
32
  async function loadAgentsTemplate(projectName) {
26
- const modulePath = path.join(import.meta.dirname, 'project-init-shared.mjs');
27
- try {
28
- // Import the module and call the buildAgentsGuide function
29
- const module = await import(modulePath);
30
- if (module.buildAgentsGuide) {
31
- return module.buildAgentsGuide(projectName);
32
- }
33
- throw new Error('Could not find buildAgentsGuide function in project-init-shared.mjs');
34
- } catch (error) {
35
- console.error(`❌ Failed to load AGENTS.md template: ${error.message}`);
36
- process.exit(1);
33
+ const modulePath = path.join(import.meta.dirname, "project-init-shared.mjs");
34
+ const module = await import(modulePath);
35
+ if (!module.buildAgentsGuide) {
36
+ throw new Error("buildAgentsGuide not available from project-init-shared.mjs");
37
37
  }
38
+ return module.buildAgentsGuide(projectName);
38
39
  }
39
40
 
40
- function updateAgentsMd(existingContent, newTemplate) {
41
- // We'll do a simple replacement for now
42
- // In a real implementation, we'd parse the existing file and merge sections
43
-
44
- console.log(`🔄 Updating AGENTS.md`);
45
-
46
- if (dryRun) {
47
- console.log(`📋 Dry run - would update AGENTS.md`);
48
- console.log(`--- Current AGENTS.md (first 200 chars) ---`);
49
- console.log(existingContent.substring(0, 200) + '...');
50
- console.log(`--- New AGENTS.md (first 200 chars) ---`);
51
- console.log(newTemplate.substring(0, 200) + '...');
52
- return null;
41
+ function inferProjectName(targetDir) {
42
+ const packagePath = path.join(targetDir, "package.json");
43
+ if (fs.existsSync(packagePath)) {
44
+ try {
45
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
46
+ if (pkg.name) return pkg.name;
47
+ } catch { /* fall through */ }
53
48
  }
54
-
55
- // Backup existing file
56
- const backupPath = path.join(cwd, 'AGENTS.md.backup');
57
- writeFileSync(backupPath, existingContent, 'utf8');
58
- console.log(`📦 Created backup at ${backupPath}`);
59
-
60
- // Write new template
61
- const agentsPath = path.join(cwd, 'AGENTS.md');
62
- writeFileSync(agentsPath, newTemplate, 'utf8');
63
- console.log(`✅ Updated AGENTS.md`);
64
-
65
- return newTemplate;
49
+ return path.basename(targetDir);
66
50
  }
67
51
 
68
- function updateCiWorkflow(cwd) {
69
- const workflowPath = path.join(cwd, '.github', 'workflows', 'ci.yml');
70
- if (!existsSync(workflowPath)) {
71
- console.log(`⚠️ No CI workflow found at ${workflowPath}`);
72
- return false;
73
- }
74
-
75
- let content = readFileSync(workflowPath, 'utf8');
76
-
77
- // Check if docs:verify is already in the workflow
78
- if (content.includes('construct docs:verify')) {
79
- console.log(`✅ CI workflow already has docs:verify check`);
80
- return false;
52
+ function findMissingAgentsSections(content) {
53
+ return REQUIRED_AGENT_SECTIONS.filter((section) => !content.toLowerCase().includes(section.toLowerCase()));
54
+ }
55
+
56
+ function ensureProposalDir(targetDir) {
57
+ const dir = path.join(targetDir, ".cx", "proposals");
58
+ fs.mkdirSync(dir, { recursive: true });
59
+ return dir;
60
+ }
61
+
62
+ function writeProposal(targetDir, fileName, content) {
63
+ const proposalDir = ensureProposalDir(targetDir);
64
+ const proposalPath = path.join(proposalDir, fileName);
65
+ fs.writeFileSync(proposalPath, content, "utf8");
66
+ return proposalPath;
67
+ }
68
+
69
+ function buildAgentsProposal(existingContent, template, missingSections) {
70
+ const missingList = missingSections.map((section) => `- ${section}`).join("\n");
71
+ return [
72
+ "<!--",
73
+ "Construct init:update proposal",
74
+ "Review this file and merge the needed sections into AGENTS.md manually.",
75
+ "The original AGENTS.md was not modified.",
76
+ "-->",
77
+ "",
78
+ "# Proposed AGENTS.md Update",
79
+ "",
80
+ "## Why this proposal exists",
81
+ "",
82
+ "Your current `AGENTS.md` is missing these required sections:",
83
+ missingList,
84
+ "",
85
+ "## Current AGENTS.md",
86
+ "",
87
+ "```md",
88
+ existingContent.trimEnd(),
89
+ "```",
90
+ "",
91
+ "## Current Construct template",
92
+ "",
93
+ "```md",
94
+ template.trimEnd(),
95
+ "```",
96
+ "",
97
+ ].join("\n");
98
+ }
99
+
100
+ function buildWorkflowProposal(existingContent) {
101
+ const updatedContent = existingContent.replace(
102
+ /(\s+- run: node bin\/construct doctor\s*\n)/,
103
+ "$1 - run: node bin/construct docs:verify\n",
104
+ );
105
+ if (updatedContent === existingContent) return null;
106
+ return [
107
+ "# Proposed CI Workflow Update",
108
+ "",
109
+ "Add `construct docs:verify` next to the existing doctor check.",
110
+ "",
111
+ "```yaml",
112
+ updatedContent.trimEnd(),
113
+ "```",
114
+ "",
115
+ ].join("\n");
116
+ }
117
+
118
+ // ── Template conflict detection ───────────────────────────────────────────────
119
+
120
+ function sha256File(filePath) {
121
+ const buf = fs.readFileSync(filePath);
122
+ return crypto.createHash('sha256').update(buf).digest('hex');
123
+ }
124
+
125
+ /**
126
+ * Compare project templates against the construct install's templates/docs/ directory.
127
+ *
128
+ * @param {string} targetDir — project root to inspect
129
+ * @returns {{ conflicts: Array<{ name, constructPath, projectPath, constructSha, projectSha }>, identical: string[], missing: string[] }}
130
+ */
131
+ export function checkTemplateConflicts(targetDir) {
132
+ const constructTemplatesDir = path.join(constructDir(), 'templates', 'docs');
133
+ const projectTemplatesDir = path.join(targetDir, 'templates', 'docs');
134
+
135
+ const conflicts = [];
136
+ const identical = [];
137
+ const missing = [];
138
+
139
+ if (!fs.existsSync(constructTemplatesDir)) {
140
+ return { conflicts, identical, missing };
81
141
  }
82
-
83
- // Find the test job and add docs:verify after doctor
84
- if (content.includes('node bin/construct doctor')) {
85
- const updatedContent = content.replace(
86
- /(\s+- run: node bin\/construct doctor\s*)/,
87
- '$1 - run: node bin/construct docs:verify\n'
88
- );
89
-
90
- if (dryRun) {
91
- console.log(`📋 Dry run - would update CI workflow to include docs:verify`);
92
- return true;
142
+
143
+ const constructFiles = fs.readdirSync(constructTemplatesDir).filter((f) => f.endsWith('.md'));
144
+
145
+ for (const name of constructFiles) {
146
+ const constructPath = path.join(constructTemplatesDir, name);
147
+ const projectPath = path.join(projectTemplatesDir, name);
148
+
149
+ if (!fs.existsSync(projectPath)) {
150
+ missing.push(name);
151
+ continue;
93
152
  }
94
-
95
- const backupPath = workflowPath + '.backup';
96
- writeFileSync(backupPath, content, 'utf8');
97
- writeFileSync(workflowPath, updatedContent, 'utf8');
98
- console.log(`✅ Updated CI workflow to include docs:verify check`);
99
- return true;
153
+
154
+ const constructSha = sha256File(constructPath);
155
+ const projectSha = sha256File(projectPath);
156
+
157
+ if (constructSha === projectSha) {
158
+ identical.push(name);
159
+ } else {
160
+ conflicts.push({ name, constructPath, projectPath, constructSha, projectSha });
161
+ }
162
+ }
163
+
164
+ return { conflicts, identical, missing };
165
+ }
166
+
167
+ /**
168
+ * Apply a resolution to a single template conflict.
169
+ *
170
+ * Resolutions:
171
+ * 'keep-project' — do nothing; leave the project template as-is
172
+ * 'use-construct' — overwrite project template with construct's version; backup original to <name>.bak
173
+ * 'move-to-cx-override' — copy project template to .cx/templates/docs/<name>,
174
+ * then write construct's template to templates/docs/<name>
175
+ *
176
+ * @param {{ name, constructPath, projectPath, constructSha, projectSha }} conflict
177
+ * @param {'keep-project'|'use-construct'|'move-to-cx-override'} resolution
178
+ * @param {string} targetDir
179
+ */
180
+ export function resolveTemplateConflict(conflict, resolution, targetDir) {
181
+ const { name, constructPath, projectPath } = conflict;
182
+
183
+ if (resolution === 'keep-project') {
184
+ return;
185
+ }
186
+
187
+ if (resolution === 'use-construct') {
188
+ const backupPath = `${projectPath}.bak`;
189
+ fs.copyFileSync(projectPath, backupPath);
190
+ fs.copyFileSync(constructPath, projectPath);
191
+ return;
100
192
  }
101
-
102
- console.log(`⚠️ Could not find 'construct doctor' in CI workflow to add docs:verify`);
103
- return false;
193
+
194
+ if (resolution === 'move-to-cx-override') {
195
+ const cxOverrideDir = path.join(targetDir, '.cx', 'templates', 'docs');
196
+ fs.mkdirSync(cxOverrideDir, { recursive: true });
197
+ fs.copyFileSync(projectPath, path.join(cxOverrideDir, name));
198
+ fs.copyFileSync(constructPath, projectPath);
199
+ return;
200
+ }
201
+
202
+ throw new Error(`Unknown resolution: ${resolution}. Expected keep-project, use-construct, or move-to-cx-override.`);
104
203
  }
105
204
 
205
+ // ── Init update workflow ──────────────────────────────────────────────────────
206
+
106
207
  async function main() {
107
- console.log(`\n🔍 Checking project structure...`);
108
-
109
- // Check for AGENTS.md
110
- const agentsPath = path.join(cwd, 'AGENTS.md');
111
- if (!existsSync(agentsPath)) {
112
- console.log(`❌ AGENTS.md not found. Run 'construct init' first.`);
208
+ console.log(`Preparing Construct standards update for ${cwd}`);
209
+
210
+ const agentsPath = path.join(cwd, "AGENTS.md");
211
+ if (!fs.existsSync(agentsPath)) {
212
+ console.error("AGENTS.md not found. Run `construct init` first.");
113
213
  process.exit(1);
114
214
  }
115
-
116
- const existingAgents = readFileSync(agentsPath, 'utf8');
117
-
118
- // Get project name from package.json or directory name
119
- let projectName = path.basename(cwd);
120
- const packagePath = path.join(cwd, 'package.json');
121
- if (existsSync(packagePath)) {
122
- try {
123
- const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
124
- if (pkg.name) projectName = pkg.name;
125
- } catch (error) {
126
- console.log(`⚠️ Could not read package.json: ${error.message}`);
215
+
216
+ const projectName = inferProjectName(cwd);
217
+ const existingAgents = fs.readFileSync(agentsPath, "utf8");
218
+ const template = await loadAgentsTemplate(projectName);
219
+ const missingSections = findMissingAgentsSections(existingAgents);
220
+
221
+ const planned = [];
222
+
223
+ if (missingSections.length > 0) {
224
+ const proposalPath = path.join(".cx", "proposals", "AGENTS.md.construct-update.md");
225
+ planned.push({
226
+ label: `AGENTS.md proposal (${missingSections.length} missing section${missingSections.length === 1 ? "" : "s"})`,
227
+ write() {
228
+ return writeProposal(cwd, "AGENTS.md.construct-update.md", buildAgentsProposal(existingAgents, template, missingSections));
229
+ },
230
+ relativePath: proposalPath,
231
+ });
232
+ }
233
+
234
+ const workflowPath = path.join(cwd, ".github", "workflows", "ci.yml");
235
+ if (fs.existsSync(workflowPath)) {
236
+ const workflowContent = fs.readFileSync(workflowPath, "utf8");
237
+ if (!workflowContent.includes("node bin/construct docs:verify")) {
238
+ const proposal = buildWorkflowProposal(workflowContent);
239
+ if (proposal) {
240
+ planned.push({
241
+ label: "CI workflow proposal (add docs:verify)",
242
+ write() {
243
+ return writeProposal(cwd, "ci.yml.construct-update.md", proposal);
244
+ },
245
+ relativePath: path.join(".cx", "proposals", "ci.yml.construct-update.md"),
246
+ });
247
+ }
127
248
  }
128
249
  }
129
-
130
- const template = await loadAgentsTemplate(projectName);
131
-
132
- console.log(`\n📊 Analysis:`);
133
- console.log(` • AGENTS.md found (${existingAgents.length} chars)`);
134
- console.log(` • Latest template loaded (${template.length} chars)`);
135
- console.log(` • Project name: ${projectName}`);
136
-
137
- // Check if AGENTS.md has documentation enforcement section
138
- const hasDocEnforcement = existingAgents.includes('Documentation Rules');
139
- console.log(` • Has documentation enforcement: ${hasDocEnforcement ? '✅' : '❌'}`);
140
-
141
- // Check for CI workflow
142
- const ciUpdated = updateCiWorkflow(cwd);
143
-
144
- // Update AGENTS.md if it doesn't have the latest sections
145
- if (!hasDocEnforcement) {
146
- updateAgentsMd(existingAgents, template);
147
- } else {
148
- console.log(`✅ AGENTS.md already has documentation enforcement rules`);
250
+
251
+ if (planned.length === 0) {
252
+ console.log("No proposals needed. The project already satisfies the current update checks.");
253
+ return;
149
254
  }
150
-
151
- console.log(`\n🎉 Update complete!`);
152
- console.log(`\nNext steps:`);
153
- console.log(` 1. Review the updated AGENTS.md`);
154
- console.log(` 2. Run 'construct docs:verify' to test documentation checks`);
155
- console.log(` 3. Commit changes: git add AGENTS.md .github/workflows/ci.yml`);
156
- console.log(` 4. Push and verify CI passes`);
157
-
255
+
256
+ console.log("");
257
+ console.log("Planned proposals:");
258
+ for (const item of planned) {
259
+ console.log(` - ${item.label} -> ${item.relativePath}`);
260
+ }
261
+
158
262
  if (dryRun) {
159
- console.log(`\n⚠️ This was a dry run. Use 'construct init:update' without --dry-run to apply changes.`);
263
+ console.log("");
264
+ console.log("Dry run only. No files were written.");
265
+ return;
160
266
  }
267
+
268
+ console.log("");
269
+ for (const item of planned) {
270
+ const proposalPath = item.write();
271
+ console.log(`Wrote proposal: ${proposalPath}`);
272
+ }
273
+
274
+ console.log("");
275
+ console.log("Review the proposal files under .cx/proposals/ and merge the needed changes manually.");
161
276
  }
162
277
 
163
278
  if (import.meta.url === `file://${process.argv[1]}`) {
164
- main().catch(error => {
165
- console.error(`❌ Error: ${error.message}`);
279
+ main().catch((error) => {
280
+ console.error(`Error: ${error.message}`);
166
281
  process.exit(1);
167
282
  });
168
- }
283
+ }
@@ -10,7 +10,7 @@
10
10
  * 2. Otherwise, run `probeAll()` from lib/bootstrap/resources.mjs.
11
11
  * 3. If everything's healthy → silent, set BOOTSTRAP_CHECKED=1.
12
12
  * 4. If anything's missing → print the status table to stderr; on TTY,
13
- * prompt to run `construct setup` now. Always set BOOTSTRAP_CHECKED=1
13
+ * prompt to run `construct init` now. Always set BOOTSTRAP_CHECKED=1
14
14
  * so we don't re-prompt on subsequent commands.
15
15
  *
16
16
  * Skipped entirely for hook invocations (would corrupt hook output), for
@@ -71,14 +71,14 @@ export async function maybeFirstInvocationProbe({
71
71
  for (const probe of probes) stdout.write(formatProbeFn(probe) + '\n');
72
72
 
73
73
  if (missingRequired.length > 0) {
74
- stdout.write(`\n[construct] ${missingRequired.length} required resource(s) missing. Run \`construct setup\` to install.\n\n`);
74
+ stdout.write(`\n[construct] ${missingRequired.length} required resource(s) missing. Run \`construct init\` to install.\n\n`);
75
75
  } else {
76
- stdout.write(`\n[construct] ${missingOptional.length} optional resource(s) missing. Run \`construct setup\` to install (Postgres, telemetry, embedding model), or continue in degraded mode.\n\n`);
76
+ stdout.write(`\n[construct] ${missingOptional.length} optional resource(s) missing. Run \`construct init\` to install (Postgres, telemetry, embedding model), or continue in degraded mode.\n\n`);
77
77
  }
78
78
 
79
79
  if (stdin.isTTY && stdout.isTTY !== false) {
80
80
  const wants = await promptYesNo({
81
- question: 'Run `construct setup` now?',
81
+ question: 'Run `construct init` now?',
82
82
  defaultYes: missingRequired.length > 0,
83
83
  readlineModule,
84
84
  stdin,
@@ -73,7 +73,7 @@ export function createIntakeQueue(rootDir, env = process.env, opts = {}) {
73
73
  if (backend === 'postgres') {
74
74
  const sql = opts.sql ?? createSqlClient(env);
75
75
  if (!sql) {
76
- throw new Error('Postgres intake queue requires a configured DATABASE_URL (or sql client). Run `construct setup` or set CONSTRUCT_INTAKE_QUEUE_BACKEND=filesystem.');
76
+ throw new Error('Postgres intake queue requires a configured DATABASE_URL (or sql client). Run `construct init` or set CONSTRUCT_INTAKE_QUEUE_BACKEND=filesystem.');
77
77
  }
78
78
  const project = opts.project ?? resolveProject(rootDir, env);
79
79
  const tenantId = opts.tenantId ?? env?.[INTAKE_TENANT_ENV_KEY] ?? null;
@@ -222,7 +222,7 @@ function buildLabels(t) {
222
222
  // ── Jira ─────────────────────────────────────────────────────────────────
223
223
 
224
224
  /**
225
- * Create a Jira ticket from an intake packet.
225
+ * Create a Jira work item from an intake packet.
226
226
  *
227
227
  * @param {object} packet - Intake packet with triage, excerpt
228
228
  * @param {object} [opts]
@@ -276,8 +276,7 @@ export async function createJiraTicket(packet, { host, email, token, project, is
276
276
 
277
277
  const json = await res.json();
278
278
 
279
- // We don't have the browse URL directly from the create response,
280
- // so construct it from the host + issue key
279
+ // Browse URL is absent from the create response construct from host + issue key
281
280
  const issueKey = json.key;
282
281
  const browseUrl = `${resolvedHost.replace(/\/$/, '')}/browse/${issueKey}`;
283
282
 
@@ -474,8 +473,8 @@ async function updateConfluencePage(artifact, { host, email, token, space, fetch
474
473
  }
475
474
 
476
475
  /**
477
- * Minimal markdown to Confluence storage format conversion.
478
- * Handles the subset of markdown used by Construct artifacts.
476
+ * Converts the subset of markdown common to Construct artifacts into
477
+ * Confluence storage format.
479
478
  */
480
479
  function markdownToConfluenceStorage(md) {
481
480
  if (!md) return '';
@@ -209,6 +209,7 @@ export async function verifyIntent({ request, specialist, flavor, matchedKeyword
209
209
  // Kept here (not in orchestration-policy) so the classifier module stays
210
210
  // the canonical source of "what does verified mean for this role."
211
211
  const ROLE_PROMPT_KEYWORDS = {
212
+ engineer: ['ai', 'platform', 'data', 'prompt', 'pipeline', 'deploy'],
212
213
  productManager: ['platform', 'enterprise', 'ai-product', 'growth', 'go-to-market'],
213
214
  architect: ['ai-systems', 'integration', 'data model', 'enterprise', 'platform'],
214
215
  qa: ['ai-eval', 'api-contract', 'data-pipeline', 'web-ui'],
@@ -19,6 +19,8 @@
19
19
  */
20
20
 
21
21
  export const KNOWLEDGE_ROOT = '.cx/knowledge';
22
+ export const KNOWLEDGE_INTERNAL_ROOT = `${KNOWLEDGE_ROOT}/internal`;
23
+ export const KNOWLEDGE_REFERENCE_ROOT = `${KNOWLEDGE_ROOT}/reference`;
22
24
 
23
25
  /**
24
26
  * All valid knowledge subdirectory names.
@@ -70,3 +72,11 @@ export function inferKnowledgeTarget(filePath) {
70
72
  if (/\bpost.?mortem\b|\bincident\b|\brca\b/.test(name)) return 'internal';
71
73
  return 'internal';
72
74
  }
75
+
76
+ export function knowledgeInternalStore(...segments) {
77
+ return ['knowledge', 'internal', ...segments].join('/');
78
+ }
79
+
80
+ export function knowledgeReferenceStore(...segments) {
81
+ return ['knowledge', 'reference', ...segments].join('/');
82
+ }
@@ -155,6 +155,21 @@ function loadSnapshotChunks(rootDir) {
155
155
  return chunks;
156
156
  }
157
157
 
158
+ /**
159
+ * Load ingested knowledge files from .cx/knowledge/ subdirectories.
160
+ */
161
+ function loadKnowledgeChunks(rootDir) {
162
+ const knowledgeRoot = path.resolve(rootDir, '.cx/knowledge');
163
+ const subdirs = ['internal', 'external', 'decisions', 'how-tos', 'reference'];
164
+ const chunks = [];
165
+ for (const subdir of subdirs) {
166
+ const full = path.join(knowledgeRoot, subdir);
167
+ if (!fs.existsSync(full)) continue;
168
+ chunks.push(...loadMarkdownChunks(full, 'knowledge'));
169
+ }
170
+ return chunks;
171
+ }
172
+
158
173
  // ── Index builder ──────────────────────────────────────────────────────────
159
174
 
160
175
  /**
@@ -166,6 +181,7 @@ export function buildCorpus(rootDir = process.cwd()) {
166
181
  ...loadObservationChunks(rootDir),
167
182
  ...loadArtifactChunks(rootDir),
168
183
  ...loadSnapshotChunks(rootDir),
184
+ ...loadKnowledgeChunks(rootDir),
169
185
  ];
170
186
 
171
187
  // Embed each chunk