@geraldmaron/construct 1.0.14 → 1.0.16

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 (275) hide show
  1. package/README.md +21 -2
  2. package/bin/construct +146 -83
  3. package/commands/build/feature.md +0 -6
  4. package/commands/build/fix.md +0 -6
  5. package/commands/design/access.md +0 -6
  6. package/commands/design/flow.md +0 -6
  7. package/commands/design/ui.md +0 -6
  8. package/commands/measure/experiment.md +0 -6
  9. package/commands/measure/metrics.md +0 -6
  10. package/commands/measure/results.md +0 -6
  11. package/commands/plan/api.md +0 -6
  12. package/commands/plan/challenge.md +0 -6
  13. package/commands/plan/decide.md +0 -6
  14. package/commands/plan/feature.md +0 -7
  15. package/commands/plan/requirements.md +0 -6
  16. package/commands/remember/context.md +0 -6
  17. package/commands/remember/handoff.md +0 -6
  18. package/commands/remember/runbook.md +0 -6
  19. package/commands/review/code.md +0 -6
  20. package/commands/review/quality.md +0 -6
  21. package/commands/review/security.md +0 -6
  22. package/commands/ship/ready.md +0 -6
  23. package/commands/ship/release.md +0 -6
  24. package/commands/ship/status.md +0 -6
  25. package/commands/understand/docs.md +0 -6
  26. package/commands/understand/research.md +0 -6
  27. package/commands/understand/this.md +0 -6
  28. package/commands/understand/why.md +0 -6
  29. package/commands/work/clean.md +0 -6
  30. package/commands/work/drive.md +0 -6
  31. package/commands/work/optimize-prompts.md +0 -6
  32. package/commands/work/parallel-review.md +0 -6
  33. package/db/schema/010_cx_scores.sql +51 -0
  34. package/lib/beads-client.mjs +19 -1
  35. package/lib/cli-commands.mjs +280 -146
  36. package/lib/comment-lint.mjs +5 -1
  37. package/lib/context-state.mjs +15 -3
  38. package/lib/contracts/validate.mjs +72 -18
  39. package/lib/contracts/violation-log.mjs +169 -0
  40. package/lib/document-extract/docling-client.mjs +114 -0
  41. package/lib/document-extract/docling-sidecar.py +122 -0
  42. package/lib/document-extract/whisper-client.mjs +79 -0
  43. package/lib/document-extract.mjs +73 -8
  44. package/lib/document-ingest.mjs +26 -4
  45. package/lib/embed/daemon.mjs +6 -1
  46. package/lib/flavors/loader.mjs +1 -1
  47. package/lib/handoffs/contract.mjs +2 -2
  48. package/lib/hooks/session-start.mjs +1 -1
  49. package/lib/hooks/stop-notify.mjs +7 -0
  50. package/lib/ingest/chunker.mjs +94 -0
  51. package/lib/ingest/pipeline.mjs +53 -0
  52. package/lib/ingest/store.mjs +82 -0
  53. package/lib/install/first-invocation.mjs +5 -1
  54. package/lib/intake/prepare.mjs +22 -4
  55. package/lib/integrations/intake-integrations.mjs +28 -2
  56. package/lib/knowledge/search.mjs +12 -0
  57. package/lib/mcp/server.mjs +142 -1
  58. package/lib/mcp/tools/skills.mjs +6 -3
  59. package/lib/mcp/tools/telemetry.mjs +30 -0
  60. package/lib/mcp/tools/workflow.mjs +6 -2
  61. package/lib/observation-store.mjs +14 -5
  62. package/lib/ollama-manager.mjs +32 -28
  63. package/lib/orchestration-policy.mjs +15 -0
  64. package/lib/prompt-composer.js +11 -1
  65. package/lib/roles/gateway.mjs +30 -1
  66. package/lib/runtime/uv-bootstrap.mjs +129 -0
  67. package/lib/runtime/whisper-bootstrap.mjs +102 -0
  68. package/lib/scheduler/index.mjs +24 -4
  69. package/lib/server/index.mjs +22 -1
  70. package/lib/server/insights.mjs +12 -0
  71. package/lib/server/static/index.html +1 -1
  72. package/lib/setup.mjs +54 -9
  73. package/lib/specialists/postconditions.mjs +1 -1
  74. package/lib/sync/skill-frontmatter.mjs +113 -21
  75. package/lib/tracking-surfaces.mjs +2 -0
  76. package/lib/update.mjs +31 -3
  77. package/lib/upgrade.mjs +31 -4
  78. package/lib/validators/skills.mjs +86 -54
  79. package/package.json +5 -5
  80. package/personas/construct.md +6 -7
  81. package/platforms/claude/CLAUDE.md +43 -15
  82. package/platforms/claude/settings.template.json +1 -1
  83. package/rules/common/beads-hygiene.md +3 -9
  84. package/rules/common/code-review.md +3 -6
  85. package/rules/common/coding-style.md +3 -6
  86. package/rules/common/comments.md +3 -7
  87. package/rules/common/commit-approval.md +3 -6
  88. package/rules/common/cx-agent-routing.md +3 -7
  89. package/rules/common/cx-skill-routing.md +3 -3
  90. package/rules/common/doc-ownership.md +3 -8
  91. package/rules/common/efficiency.md +3 -8
  92. package/rules/common/framing.md +3 -8
  93. package/rules/common/git-workflow.md +3 -5
  94. package/rules/common/no-fabrication.md +4 -12
  95. package/rules/common/patterns.md +3 -5
  96. package/rules/common/release-gates.md +3 -8
  97. package/rules/common/research.md +3 -8
  98. package/rules/common/review-before-change.md +3 -9
  99. package/rules/common/security.md +3 -6
  100. package/rules/common/skill-composition.md +3 -7
  101. package/rules/common/testing.md +3 -6
  102. package/rules/golang/coding-style.md +1 -5
  103. package/rules/golang/hooks.md +1 -5
  104. package/rules/golang/patterns.md +1 -5
  105. package/rules/golang/security.md +1 -5
  106. package/rules/golang/testing.md +1 -5
  107. package/rules/python/coding-style.md +1 -5
  108. package/rules/python/hooks.md +1 -5
  109. package/rules/python/patterns.md +1 -5
  110. package/rules/python/security.md +1 -5
  111. package/rules/python/testing.md +1 -5
  112. package/rules/swift/coding-style.md +1 -5
  113. package/rules/swift/hooks.md +1 -5
  114. package/rules/swift/patterns.md +1 -5
  115. package/rules/swift/security.md +1 -5
  116. package/rules/swift/testing.md +1 -5
  117. package/rules/typescript/coding-style.md +1 -5
  118. package/rules/typescript/hooks.md +1 -5
  119. package/rules/typescript/patterns.md +1 -5
  120. package/rules/typescript/security.md +1 -5
  121. package/rules/typescript/testing.md +1 -5
  122. package/rules/web/coding-style.md +3 -5
  123. package/rules/web/design-quality.md +3 -5
  124. package/rules/web/hooks.md +3 -5
  125. package/rules/web/patterns.md +3 -5
  126. package/rules/web/performance.md +3 -5
  127. package/rules/web/security.md +3 -5
  128. package/rules/web/testing.md +3 -5
  129. package/scripts/sync-specialists.mjs +20 -2
  130. package/skills/ai/agent-dev.md +4 -5
  131. package/skills/ai/llm-security.md +4 -5
  132. package/skills/ai/ml-ops.md +4 -5
  133. package/skills/ai/orchestration-workflow.md +4 -5
  134. package/skills/ai/prompt-and-eval.md +4 -5
  135. package/skills/ai/prompt-optimizer.md +4 -6
  136. package/skills/ai/rag-system.md +4 -5
  137. package/skills/architecture/api-design.md +4 -5
  138. package/skills/architecture/caching.md +4 -5
  139. package/skills/architecture/cloud-native.md +4 -5
  140. package/skills/architecture/message-queue.md +4 -5
  141. package/skills/architecture/security-arch.md +4 -5
  142. package/skills/compliance/ai-disclosure.md +4 -5
  143. package/skills/compliance/data-privacy.md +4 -5
  144. package/skills/compliance/license-audit.md +4 -5
  145. package/skills/compliance/regulatory-review.md +4 -5
  146. package/skills/development/cpp.md +4 -5
  147. package/skills/development/go.md +4 -5
  148. package/skills/development/java.md +4 -5
  149. package/skills/development/kotlin.md +4 -5
  150. package/skills/development/mobile-crossplatform.md +4 -5
  151. package/skills/development/python.md +4 -5
  152. package/skills/development/rust.md +4 -5
  153. package/skills/development/shell.md +4 -5
  154. package/skills/development/swift.md +4 -5
  155. package/skills/development/typescript.md +4 -5
  156. package/skills/devops/ci-cd.md +4 -5
  157. package/skills/devops/containerization.md +4 -5
  158. package/skills/devops/cost-optimization.md +4 -5
  159. package/skills/devops/data-engineering.md +4 -5
  160. package/skills/devops/database.md +4 -5
  161. package/skills/devops/dependency-management.md +4 -5
  162. package/skills/devops/devsecops.md +4 -5
  163. package/skills/devops/git-workflow.md +4 -5
  164. package/skills/devops/incident-response.md +4 -5
  165. package/skills/devops/monorepo.md +4 -5
  166. package/skills/devops/observability.md +4 -5
  167. package/skills/devops/performance.md +4 -5
  168. package/skills/devops/testing.md +4 -5
  169. package/skills/docs/adr-workflow.md +4 -7
  170. package/skills/docs/backlog-proposal-workflow.md +4 -3
  171. package/skills/docs/customer-profile-workflow.md +4 -3
  172. package/skills/docs/document-ingest-workflow.md +4 -3
  173. package/skills/docs/evidence-ingest-workflow.md +4 -3
  174. package/skills/docs/init-docs.md +4 -5
  175. package/skills/docs/init-project.md +4 -5
  176. package/skills/docs/prd-workflow.md +4 -7
  177. package/skills/docs/prfaq-workflow.md +4 -3
  178. package/skills/docs/product-intelligence-review.md +4 -3
  179. package/skills/docs/product-intelligence-workflow.md +4 -6
  180. package/skills/docs/product-signal-workflow.md +4 -5
  181. package/skills/docs/research-workflow.md +4 -5
  182. package/skills/docs/runbook-workflow.md +4 -5
  183. package/skills/docs/strategy-workflow.md +4 -5
  184. package/skills/exploration/dependency-graph-reading.md +4 -7
  185. package/skills/exploration/repo-map.md +4 -5
  186. package/skills/exploration/tracer-bullet-method.md +4 -7
  187. package/skills/exploration/unknown-codebase-onboarding.md +4 -7
  188. package/skills/frameworks/django.md +4 -5
  189. package/skills/frameworks/nextjs.md +4 -5
  190. package/skills/frameworks/react.md +4 -5
  191. package/skills/frameworks/spring-boot.md +4 -5
  192. package/skills/frontend-design/accessibility.md +4 -5
  193. package/skills/frontend-design/component-patterns.md +4 -5
  194. package/skills/frontend-design/engineering.md +4 -5
  195. package/skills/frontend-design/state-management.md +4 -5
  196. package/skills/frontend-design/ui-aesthetics.md +4 -5
  197. package/skills/frontend-design/ux-principles.md +4 -5
  198. package/skills/operating/change-management.md +4 -8
  199. package/skills/operating/incident-response.md +4 -8
  200. package/skills/operating/oncall-rotation.md +4 -7
  201. package/skills/operating/orchestration-reference.md +4 -10
  202. package/skills/quality-gates/review-work.md +4 -5
  203. package/skills/quality-gates/verify-change.md +4 -5
  204. package/skills/quality-gates/verify-module.md +4 -5
  205. package/skills/quality-gates/verify-quality.md +4 -5
  206. package/skills/quality-gates/verify-security.md +4 -5
  207. package/skills/roles/architect.ai-systems.md +6 -9
  208. package/skills/roles/architect.data.md +6 -9
  209. package/skills/roles/architect.enterprise.md +6 -9
  210. package/skills/roles/architect.integration.md +6 -9
  211. package/skills/roles/architect.md +7 -14
  212. package/skills/roles/architect.platform.md +6 -9
  213. package/skills/roles/data-analyst.experiment.md +6 -9
  214. package/skills/roles/data-analyst.md +6 -9
  215. package/skills/roles/data-analyst.product-intelligence.md +7 -9
  216. package/skills/roles/data-analyst.product.md +6 -9
  217. package/skills/roles/data-analyst.telemetry.md +7 -9
  218. package/skills/roles/data-engineer.pipeline.md +6 -9
  219. package/skills/roles/data-engineer.vector-retrieval.md +7 -9
  220. package/skills/roles/data-engineer.warehouse.md +6 -9
  221. package/skills/roles/debugger.md +6 -9
  222. package/skills/roles/designer.accessibility.md +6 -9
  223. package/skills/roles/designer.md +7 -9
  224. package/skills/roles/engineer.ai.md +6 -9
  225. package/skills/roles/engineer.data.md +6 -9
  226. package/skills/roles/engineer.md +9 -9
  227. package/skills/roles/engineer.platform.md +6 -9
  228. package/skills/roles/operator.docs.md +6 -9
  229. package/skills/roles/operator.md +9 -9
  230. package/skills/roles/operator.release.md +6 -9
  231. package/skills/roles/operator.sre.md +6 -9
  232. package/skills/roles/orchestrator.md +6 -9
  233. package/skills/roles/product-manager.ai-product.md +6 -9
  234. package/skills/roles/product-manager.business-strategy.md +6 -9
  235. package/skills/roles/product-manager.enterprise.md +6 -9
  236. package/skills/roles/product-manager.growth.md +7 -9
  237. package/skills/roles/product-manager.md +7 -9
  238. package/skills/roles/product-manager.platform.md +6 -9
  239. package/skills/roles/product-manager.product.md +6 -9
  240. package/skills/roles/qa.ai-eval.md +8 -9
  241. package/skills/roles/qa.api-contract.md +7 -9
  242. package/skills/roles/qa.data-pipeline.md +7 -9
  243. package/skills/roles/qa.md +7 -9
  244. package/skills/roles/qa.test-automation.md +6 -9
  245. package/skills/roles/qa.web-ui.md +7 -9
  246. package/skills/roles/researcher.explorer.md +6 -9
  247. package/skills/roles/researcher.md +8 -9
  248. package/skills/roles/researcher.ux.md +6 -9
  249. package/skills/roles/reviewer.devil-advocate.md +6 -9
  250. package/skills/roles/reviewer.evaluator.md +6 -9
  251. package/skills/roles/reviewer.md +9 -9
  252. package/skills/roles/reviewer.trace.md +6 -9
  253. package/skills/roles/security.ai.md +7 -9
  254. package/skills/roles/security.appsec.md +6 -9
  255. package/skills/roles/security.cloud.md +6 -9
  256. package/skills/roles/security.legal-compliance.md +6 -9
  257. package/skills/roles/security.md +7 -9
  258. package/skills/roles/security.privacy.md +7 -9
  259. package/skills/roles/security.supply-chain.md +7 -9
  260. package/skills/security/blue-team.md +4 -5
  261. package/skills/security/code-audit.md +4 -5
  262. package/skills/security/pentest.md +4 -5
  263. package/skills/security/red-team.md +4 -5
  264. package/skills/security/threat-intel.md +4 -5
  265. package/skills/security/vuln-research.md +4 -5
  266. package/skills/strategy/competitive-landscape.md +4 -8
  267. package/skills/strategy/market-research-methods.md +4 -8
  268. package/skills/strategy/narrative-arc.md +4 -8
  269. package/skills/strategy/pricing-positioning.md +4 -8
  270. package/skills/utility/clean-code.md +4 -5
  271. package/specialists/policy-inventory.json +14 -0
  272. package/lib/specialist-contracts-enforce.mjs +0 -172
  273. package/rules/common/agents.md +0 -28
  274. package/rules/common/development-workflow.md +0 -32
  275. package/rules/common/performance.md +0 -55
@@ -0,0 +1,102 @@
1
+ /**
2
+ * lib/runtime/whisper-bootstrap.mjs — provision whisper.cpp binary + ASR model.
3
+ *
4
+ * Strategy (2026-06):
5
+ * 1. Prefer system-installed whisper-cli (or legacy `main`) on PATH —
6
+ * `brew install whisper-cpp` (macOS, Metal-accelerated) or distro
7
+ * package on Linux.
8
+ * 2. Fall back to bundled `whisper-cli` cached at <runtimeDir>/bin/ if a
9
+ * previous bootstrap fetched one.
10
+ * 3. If neither, return a structured "not-available" descriptor — caller
11
+ * surfaces a user-actionable install hint rather than crashing.
12
+ *
13
+ * Model files are downloaded directly from Hugging Face the first time the
14
+ * default `base.en` (or env-overridden) model is requested. Models cache at
15
+ * <runtimeDir>/models/. base.en is ~150MB; smaller than the small.en or
16
+ * medium.en model that callers can opt into via CONSTRUCT_WHISPER_MODEL.
17
+ *
18
+ * No bundled distribution: whisper.cpp upstream does not publish stable
19
+ * pre-built CLI binaries across platforms, and building from source via
20
+ * cmake adds a heavyweight first-run path. Defer that to a future opt-in.
21
+ */
22
+ import { existsSync, mkdirSync, statSync, createWriteStream } from 'node:fs';
23
+ import { spawnSync } from 'node:child_process';
24
+ import { pipeline } from 'node:stream/promises';
25
+ import path from 'node:path';
26
+ import os from 'node:os';
27
+
28
+ const HF_MODEL_BASE_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main';
29
+ const DEFAULT_MODEL = process.env.CONSTRUCT_WHISPER_MODEL || 'base.en';
30
+ const KNOWN_MODELS = new Set(['tiny', 'tiny.en', 'base', 'base.en', 'small', 'small.en', 'medium', 'medium.en', 'large-v3', 'large-v3-turbo']);
31
+
32
+ export function defaultRuntimeDir(cwd = process.cwd()) {
33
+ return path.join(cwd, '.cx', 'runtime', 'whisper');
34
+ }
35
+
36
+ function which(bin) {
37
+ const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [bin], { encoding: 'utf8' });
38
+ if (result.status !== 0) return null;
39
+ return result.stdout.trim().split('\n')[0] || null;
40
+ }
41
+
42
+ export function locateWhisperBinary({ runtimeDir = defaultRuntimeDir() } = {}) {
43
+ for (const candidate of ['whisper-cli', 'whisper-cpp', 'main']) {
44
+ const found = which(candidate);
45
+ if (found) return { binary: found, source: 'system', name: candidate };
46
+ }
47
+ const cached = path.join(runtimeDir, 'bin', 'whisper-cli');
48
+ if (existsSync(cached)) return { binary: cached, source: 'cached', name: 'whisper-cli' };
49
+ return null;
50
+ }
51
+
52
+ export function describeWhisperRuntime({ runtimeDir = defaultRuntimeDir() } = {}) {
53
+ const located = locateWhisperBinary({ runtimeDir });
54
+ const modelPath = modelPathFor(DEFAULT_MODEL, runtimeDir);
55
+ return {
56
+ runtimeDir,
57
+ binary: located,
58
+ defaultModel: DEFAULT_MODEL,
59
+ modelPath,
60
+ modelInstalled: existsSync(modelPath),
61
+ available: !!located,
62
+ };
63
+ }
64
+
65
+ export function installHint() {
66
+ if (process.platform === 'darwin') return 'brew install whisper-cpp';
67
+ if (process.platform === 'linux') return 'See https://github.com/ggml-org/whisper.cpp#quick-start (build from source via cmake or install via your distro package manager).';
68
+ return 'See https://github.com/ggml-org/whisper.cpp#quick-start';
69
+ }
70
+
71
+ function modelPathFor(modelName, runtimeDir) {
72
+ return path.join(runtimeDir, 'models', `ggml-${modelName}.bin`);
73
+ }
74
+
75
+ export async function ensureWhisperModel({ runtimeDir = defaultRuntimeDir(), model = DEFAULT_MODEL } = {}) {
76
+ if (!KNOWN_MODELS.has(model)) {
77
+ throw new Error(`unknown whisper model: ${model} (allowed: ${[...KNOWN_MODELS].join(', ')})`);
78
+ }
79
+ const target = modelPathFor(model, runtimeDir);
80
+ if (existsSync(target) && statSync(target).size > 1024) return target;
81
+ mkdirSync(path.dirname(target), { recursive: true });
82
+ const url = `${HF_MODEL_BASE_URL}/ggml-${model}.bin`;
83
+ const response = await fetch(url, { redirect: 'follow' });
84
+ if (!response.ok) {
85
+ throw new Error(`failed to download whisper model ${model}: ${response.status} ${response.statusText} (${url})`);
86
+ }
87
+ await pipeline(response.body, createWriteStream(target));
88
+ return target;
89
+ }
90
+
91
+ export async function ensureWhisperRuntime({ runtimeDir = defaultRuntimeDir(), model = DEFAULT_MODEL } = {}) {
92
+ mkdirSync(runtimeDir, { recursive: true });
93
+ const located = locateWhisperBinary({ runtimeDir });
94
+ if (!located) {
95
+ const err = new Error(`whisper-cli not found; install via: ${installHint()}`);
96
+ err.code = 'WHISPER_BINARY_MISSING';
97
+ err.installHint = installHint();
98
+ throw err;
99
+ }
100
+ const modelPath = await ensureWhisperModel({ runtimeDir, model });
101
+ return { binary: located.binary, source: located.source, modelPath, model };
102
+ }
@@ -94,14 +94,34 @@ registerJob({
94
94
  handler: async () => ({ status: 'noop', reason: 'not yet implemented' }),
95
95
  });
96
96
 
97
+ // Doc hygiene scan cadence is deployment-aware. Solo runs nightly because
98
+ // a single contributor's doc drift accumulates slowly. Team and enterprise
99
+ // runs hourly because many writers can shift the surface within a workday,
100
+ // and the higher limit (50 vs 25) gives the reconcile worker enough headroom
101
+ // to keep up. Mode/schedule resolved at module-load so a single registry
102
+ // entry covers both topologies.
103
+
104
+ const DOC_HYGIENE_SOLO_CRON = '0 2 * * *';
105
+ const DOC_HYGIENE_TEAM_CRON = '0 * * * *';
106
+
107
+ export function resolveDocHygieneSchedule(env = process.env) {
108
+ const mode = getDeploymentMode(env);
109
+ const isTeamish = mode === 'team' || mode === 'enterprise';
110
+ return {
111
+ mode: isTeamish ? 'team' : 'solo',
112
+ schedule: isTeamish ? DOC_HYGIENE_TEAM_CRON : DOC_HYGIENE_SOLO_CRON,
113
+ limit: isTeamish ? 50 : 25,
114
+ };
115
+ }
116
+
117
+ const docHygiene = resolveDocHygieneSchedule();
97
118
  registerJob({
98
119
  id: 'doc-hygiene-scan',
99
- schedule: '0 2 * * *',
100
- mode: 'solo',
120
+ schedule: docHygiene.schedule,
121
+ mode: docHygiene.mode,
101
122
  handler: async ({ cwd, env }) => {
102
123
  const { findHygieneCandidates } = await import('../hygiene/scan.mjs');
103
- const mode = getDeploymentMode(env);
104
- const limit = mode === 'solo' ? 25 : 50;
124
+ const { limit } = resolveDocHygieneSchedule(env);
105
125
  const candidates = findHygieneCandidates({ cwd, limit });
106
126
  return {
107
127
  status: 'ok',
@@ -33,7 +33,27 @@ const __dirname = fileURLToPath(new URL('.', import.meta.url));
33
33
  const ROOT_DIR = join(__dirname, '..', '..');
34
34
  const HOME = homedir();
35
35
  const PORT = parseInt(process.env.PORT ?? '4242', 10);
36
- const BIND_HOST = process.env.BIND_HOST ?? (process.env.NODE_ENV === 'production' ? '0.0.0.0' : '127.0.0.1');
36
+
37
+ // Default bind is loopback regardless of NODE_ENV. Exposing the dashboard to
38
+ // non-loopback interfaces is opt-in via BIND_HOST and gated on configured
39
+ // auth (see assertSafeBind below).
40
+
41
+ const BIND_HOST = process.env.BIND_HOST ?? '127.0.0.1';
42
+
43
+ function isLoopbackHost(host) {
44
+ return host === '127.0.0.1' || host === 'localhost' || host === '::1';
45
+ }
46
+
47
+ function assertSafeBind() {
48
+ if (isLoopbackHost(BIND_HOST)) return;
49
+ if (!isAuthConfigured()) {
50
+ process.stderr.write(
51
+ `[dashboard] refuses to bind to non-loopback host ${BIND_HOST} without auth configured. ` +
52
+ `Set CONSTRUCT_DASHBOARD_TOKEN (or run \`construct dashboard auth init\`) before exposing the dashboard.\n`,
53
+ );
54
+ process.exit(1);
55
+ }
56
+ }
37
57
 
38
58
  const STATIC_DIR = join(__dirname, 'static');
39
59
  const REGISTRY_FILE = join(ROOT_DIR, 'specialists', 'registry.json');
@@ -2237,6 +2257,7 @@ if (process.env.CX_AUTO_EMBED === '1') {
2237
2257
  setInterval(runEmbedSync, intervalMs).unref();
2238
2258
  console.log(`Embed scheduler active — syncing every ${intervalMs / 60_000} min`);
2239
2259
  }
2260
+ assertSafeBind();
2240
2261
  server.listen(PORT, BIND_HOST, () => {
2241
2262
  console.log(`Construct dashboard running at http://${BIND_HOST}:${PORT}`);
2242
2263
  });
@@ -14,6 +14,7 @@ import os from 'node:os';
14
14
  import path from 'node:path';
15
15
  import { spawnSync } from 'node:child_process';
16
16
  import { readCostLog, summarizeCostData } from '../cost.mjs';
17
+ import { getRebrand } from '../profiles/rebrand.mjs';
17
18
 
18
19
  const TELEMETRY_TIMEOUT_MS = 3500;
19
20
 
@@ -309,12 +310,23 @@ function summarizeIntakeQueue(env, cwd = process.cwd()) {
309
310
  }
310
311
  } catch { /* skip */ }
311
312
 
313
+ // Profile-aware labels: the active profile's rebrand block (e.g.
314
+ // rnd → "Intake queue"/"signal"; sales-ops → "Lead inbox"/"lead")
315
+ // travels in the payload so the dashboard renders the right noun
316
+ // without hardcoding "intake" anywhere user-facing.
317
+
318
+ let rebrand;
319
+ try { rebrand = getRebrand(cwd); }
320
+ catch { rebrand = { intakeQueueLabel: 'Intake queue', signalNoun: 'signal' }; }
321
+
312
322
  return {
313
323
  state: pendingCount === 0 && processedCount === 0 && skippedCount === 0 ? 'empty' : 'ok',
314
324
  pending: pendingCount,
315
325
  processed: processedCount,
316
326
  skipped: skippedCount,
317
327
  customerLinked,
328
+ label: rebrand.intakeQueueLabel,
329
+ itemNoun: rebrand.signalNoun,
318
330
  };
319
331
  } catch (err) {
320
332
  return { state: 'unreachable', message: err.message };
@@ -1 +1 @@
1
- <!DOCTYPE html><!--02hC8wDNvTNKwi4iN6D_t--><html lang="en" data-theme="dark" data-density="comfortable" data-motion="normal" class="__variable_629dad __variable_246ccd __variable_3c557b"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/22a5144ee8d83bca-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/media/bb3ef058b751a6ad-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/47a506ef57811fb9.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-8e0503f8a36190b3.js"/><script src="/_next/static/chunks/87c73c54-24122e7b92478d00.js" async=""></script><script src="/_next/static/chunks/18-1e25b5692ec1d26d.js" async=""></script><script src="/_next/static/chunks/main-app-07d82ec6c92d7402.js" async=""></script><script src="/_next/static/chunks/9664-af80478aa73ba424.js" async=""></script><script src="/_next/static/chunks/8144-da56a4a291c0b21a.js" async=""></script><script src="/_next/static/chunks/app/layout-1a2d2674aa220229.js" async=""></script><script src="/_next/static/chunks/app/page-2568fbdd2b0e1eca.js" async=""></script><meta name="next-size-adjust" content=""/><title>Construct — Dashboard</title><meta name="description" content="Local operations dashboard for Construct — approvals, health, knowledge, models, providers."/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div class="shell"><header class="topbar"><a class="brand" href="/"><div class="mark"></div><div class="name">Construct<em>dashboard</em></div></a><div class="search-wrap"><label class="search"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"></circle><line x1="21" y1="21" x2="16.5" y2="16.5"></line></svg><input readOnly="" placeholder="Search dashboard…"/><span class="kbd">⌘K</span></label></div><div class="top-actions"><button class="icon-btn" title="Toggle theme" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"></circle><line x1="12" y1="2" x2="12" y2="5"></line><line x1="12" y1="19" x2="12" y2="22"></line><line x1="2" y1="12" x2="5" y2="12"></line><line x1="19" y1="12" x2="22" y2="12"></line><line x1="4.5" y1="4.5" x2="6.5" y2="6.5"></line><line x1="17.5" y1="17.5" x2="19.5" y2="19.5"></line><line x1="4.5" y1="19.5" x2="6.5" y2="17.5"></line><line x1="17.5" y1="6.5" x2="19.5" y2="4.5"></line></svg></button><button class="icon-btn" title="Toggle density" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="7" x2="21" y2="7"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="17" x2="21" y2="17"></line></svg></button><a class="icon-btn outlined" href="https://github.com/geraldmaron/construct" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M12 .5C5.65.5.5 5.66.5 12.02c0 5.09 3.29 9.4 7.86 10.92.58.1.79-.25.79-.55v-2.13c-3.2.7-3.87-1.37-3.87-1.37-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.76 2.69 1.25 3.35.96.1-.74.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.7 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.16 1.18a10.9 10.9 0 0 1 5.74 0c2.2-1.49 3.16-1.18 3.16-1.18.62 1.58.23 2.75.12 3.04.74.81 1.18 1.84 1.18 3.1 0 4.43-2.69 5.41-5.25 5.69.41.36.78 1.05.78 2.13v3.16c0 .31.21.66.79.55A11.52 11.52 0 0 0 23.5 12.02C23.5 5.66 18.35.5 12 .5Z"></path></svg> <span style="font-size:11px">repo</span></a></div><div class="progress"></div></header><div class="body-grid"><aside class="sidebar"><div class="side-group"><div class="side-label">Overview</div><a class="side-link active" href="/"><span class="num">01</span><span>Home</span></a><a class="side-link" href="/doctor/"><span class="num">02</span><span>Doctor</span></a><a class="side-link" href="/resources/"><span class="num">03</span><span>Services</span></a></div><div class="side-group"><div class="side-label">Activity</div><a class="side-link" href="/audit/"><span class="num">01</span><span>Audit trail</span></a><a class="side-link" href="/snapshots/"><span class="num">02</span><span>Snapshots</span></a><a class="side-link" href="/performance/"><span class="num">03</span><span>Performance</span></a></div><div class="side-group"><div class="side-label">Work</div><a class="side-link" href="/approvals/"><span class="num">01</span><span>Approvals</span></a><a class="side-link" href="/workflow/"><span class="num">02</span><span>Workflow</span></a><a class="side-link" href="/beads/"><span class="num">03</span><span>Beads</span></a><a class="side-link" href="/artifacts/"><span class="num">04</span><span>Artifacts</span></a><a class="side-link" href="/intake/"><span class="num">05</span><span>Intake</span></a></div><div class="side-group"><div class="side-label">Specialists</div><a class="side-link" href="/agents/"><span class="num">01</span><span>Specialists</span></a><a class="side-link" href="/skills/"><span class="num">02</span><span>Skills</span></a><a class="side-link" href="/commands/"><span class="num">03</span><span>Slash commands</span></a><a class="side-link" href="/hooks/"><span class="num">04</span><span>Hooks</span></a><a class="side-link" href="/plugins/"><span class="num">05</span><span>Plugins</span></a></div><div class="side-group"><div class="side-label">Models &amp; Providers</div><a class="side-link" href="/models/"><span class="num">01</span><span>Models</span></a><a class="side-link" href="/providers/"><span class="num">02</span><span>Providers</span></a><a class="side-link" href="/mcp/"><span class="num">03</span><span>MCP servers</span></a></div><div class="side-group"><div class="side-label">Knowledge</div><a class="side-link" href="/knowledge/"><span class="num">01</span><span>Knowledge</span></a><a class="side-link" href="/editor/"><span class="num">02</span><span>Editor</span></a></div><div class="side-group"><div class="side-label">System</div><a class="side-link" href="/config/"><span class="num">01</span><span>Config</span></a><a class="side-link" href="/infrastructure/"><span class="num">02</span><span>Infrastructure</span></a></div><div class="side-foot">Local dashboard for Construct. Theme + density preferences persist.</div></aside><main class="main"><div class="page"><div class="eyebrow"><span class="dot"></span><span>overview · construct dashboard</span></div><h1 class="page-title">System state</h1><p class="page-lede">The runtime in one place. Approvals, services, model providers, knowledge corpus, and intake — each section deep-links to its dedicated surface.</p><div class="meta-strip"><span class="pill">mode: <!-- -->…</span><span>instance: <!-- -->…</span><span class="sep">·</span><span>embed: <!-- -->stopped</span></div><div class="body"><div class="hero-stats" style="margin-top:0;margin-bottom:24px"><div class="hero-stat"><span class="k">Services</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">running</span></div><div class="hero-stat"><span class="k">Approvals</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">pending</span></div><div class="hero-stat"><span class="k">Skills</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">registered</span></div><div class="hero-stat"><span class="k">Commands</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">slash</span></div></div><section class="section open"><button class="section-head" aria-expanded="true" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">01</span><span class="section-title">Pending approvals</span></span><span class="section-aside"><span>empty</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Items waiting on a human gate. High-risk mutations (work item creation, merge, doc publish, config changes) land here.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">02</span><span class="section-title">Services</span></span><span class="section-aside"><span>0/0 running</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Local processes that back the runtime: Postgres, memory bridge, OpenCode bridge, dashboard, embed daemon.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div><p style="margin-top:12px"><a class="link" href="/resources/">Open services dashboard →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">03</span><span class="section-title">Credentials</span></span><span class="section-aside"><span>0 tracked</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Provider API keys, billing posture, and 1Password references. Set them in /providers.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><p style="margin-top:12px"><a class="link" href="/providers/">Manage providers →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">04</span><span class="section-title">Quick links</span></span><span class="section-aside"></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Deep links into every dashboard surface.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div class="callout"><span class="clt-label">Where to next</span><div><p><a class="link" href="/doctor/">Doctor</a>,<!-- --> <a class="link" href="/knowledge/">Knowledge</a>,<!-- --> <a class="link" href="/intake/">Intake</a>,<!-- --> <a class="link" href="/workflow/">Workflow</a>,<!-- --> <a class="link" href="/agents/">Specialists</a>,<!-- --> <a class="link" href="/models/">Models</a>,<!-- --> <a class="link" href="/audit/">Audit</a>.</p></div></div></div></div></div></div></section></div></div><!--$--><!--/$--></main></div></div><script src="/_next/static/chunks/webpack-8e0503f8a36190b3.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[105,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"7177\",\"static/chunks/app/layout-1a2d2674aa220229.js\"],\"AppShellClient\"]\n3:I[5341,[],\"\"]\n4:I[25,[],\"\"]\n5:I[1012,[],\"ClientPageRoot\"]\n6:I[9008,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"8974\",\"static/chunks/app/page-2568fbdd2b0e1eca.js\"],\"default\"]\n9:I[5104,[],\"OutletBoundary\"]\nb:I[7158,[],\"AsyncMetadataOutlet\"]\nd:I[5104,[],\"ViewportBoundary\"]\nf:I[5104,[],\"MetadataBoundary\"]\n10:\"$Sreact.suspense\"\n12:I[4431,[],\"\"]\n:HL[\"/_next/static/media/22a5144ee8d83bca-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/bb3ef058b751a6ad-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/47a506ef57811fb9.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"02hC8wDNvTNKwi4iN6D_t\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/47a506ef57811fb9.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"data-theme\":\"dark\",\"data-density\":\"comfortable\",\"data-motion\":\"normal\",\"suppressHydrationWarning\":true,\"className\":\"__variable_629dad __variable_246ccd __variable_3c557b\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L2\",null,{\"nav\":[{\"label\":\"Overview\",\"items\":[{\"id\":\"home\",\"title\":\"Home\",\"href\":\"/\"},{\"id\":\"doctor\",\"title\":\"Doctor\",\"href\":\"/doctor\"},{\"id\":\"resources\",\"title\":\"Services\",\"href\":\"/resources\"}]},{\"label\":\"Activity\",\"items\":[{\"id\":\"audit\",\"title\":\"Audit trail\",\"href\":\"/audit\"},{\"id\":\"snapshots\",\"title\":\"Snapshots\",\"href\":\"/snapshots\"},{\"id\":\"performance\",\"title\":\"Performance\",\"href\":\"/performance\"}]},{\"label\":\"Work\",\"items\":[{\"id\":\"approvals\",\"title\":\"Approvals\",\"href\":\"/approvals\"},{\"id\":\"workflow\",\"title\":\"Workflow\",\"href\":\"/workflow\"},{\"id\":\"beads\",\"title\":\"Beads\",\"href\":\"/beads\"},{\"id\":\"artifacts\",\"title\":\"Artifacts\",\"href\":\"/artifacts\"},{\"id\":\"intake\",\"title\":\"Intake\",\"href\":\"/intake\"}]},{\"label\":\"Specialists\",\"items\":[{\"id\":\"agents\",\"title\":\"Specialists\",\"href\":\"/agents\"},{\"id\":\"skills\",\"title\":\"Skills\",\"href\":\"/skills\"},{\"id\":\"commands\",\"title\":\"Slash commands\",\"href\":\"/commands\"},{\"id\":\"hooks\",\"title\":\"Hooks\",\"href\":\"/hooks\"},{\"id\":\"plugins\",\"title\":\"Plugins\",\"href\":\"/plugins\"}]},{\"label\":\"Models \u0026 Providers\",\"items\":[{\"id\":\"models\",\"title\":\"Models\",\"href\":\"/models\"},{\"id\":\"providers\",\"title\":\"Providers\",\"href\":\"/providers\"},{\"id\":\"mcp\",\"title\":\"MCP servers\",\"href\":\"/mcp\"}]},{\"label\":\"Knowledge\",\"items\":[{\"id\":\"knowledge\",\"title\":\"Knowledge\",\"href\":\"/knowledge\"},{\"id\":\"editor\",\"title\":\"Editor\",\"href\":\"/editor\"}]},{\"label\":\"System\",\"items\":[{\"id\":\"config\",\"title\":\"Config\",\"href\":\"/config\"},{\"id\":\"infrastructure\",\"title\":\"Infrastructure\",\"href\":\"/infrastructure\"}]}],\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L5\",null,{\"Component\":\"$6\",\"searchParams\":{},\"params\":{},\"promises\":[\"$@7\",\"$@8\"]}],null,[\"$\",\"$L9\",null,{\"children\":[\"$La\",[\"$\",\"$Lb\",null,{\"promise\":\"$@c\"}]]}]]}],{},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[[\"$\",\"$Ld\",null,{\"children\":\"$Le\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$Lf\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$10\",null,{\"fallback\":null,\"children\":\"$L11\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$12\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"7:{}\n8:\"$0:f:0:1:2:children:1:props:children:0:props:params\"\n"])</script><script>self.__next_f.push([1,"e:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\na:null\n"])</script><script>self.__next_f.push([1,"c:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Construct — Dashboard\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Local operations dashboard for Construct — approvals, health, knowledge, models, providers.\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"11:\"$c:metadata\"\n"])</script></body></html>
1
+ <!DOCTYPE html><!--qA_CTDVxjnG_0_bArvNYn--><html lang="en" data-theme="dark" data-density="comfortable" data-motion="normal" class="__variable_629dad __variable_246ccd __variable_3c557b"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/22a5144ee8d83bca-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/media/bb3ef058b751a6ad-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/47a506ef57811fb9.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-8e0503f8a36190b3.js"/><script src="/_next/static/chunks/87c73c54-24122e7b92478d00.js" async=""></script><script src="/_next/static/chunks/18-1e25b5692ec1d26d.js" async=""></script><script src="/_next/static/chunks/main-app-07d82ec6c92d7402.js" async=""></script><script src="/_next/static/chunks/9664-af80478aa73ba424.js" async=""></script><script src="/_next/static/chunks/8144-da56a4a291c0b21a.js" async=""></script><script src="/_next/static/chunks/app/layout-1a2d2674aa220229.js" async=""></script><script src="/_next/static/chunks/app/page-2568fbdd2b0e1eca.js" async=""></script><meta name="next-size-adjust" content=""/><title>Construct — Dashboard</title><meta name="description" content="Local operations dashboard for Construct — approvals, health, knowledge, models, providers."/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div class="shell"><header class="topbar"><a class="brand" href="/"><div class="mark"></div><div class="name">Construct<em>dashboard</em></div></a><div class="search-wrap"><label class="search"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"></circle><line x1="21" y1="21" x2="16.5" y2="16.5"></line></svg><input readOnly="" placeholder="Search dashboard…"/><span class="kbd">⌘K</span></label></div><div class="top-actions"><button class="icon-btn" title="Toggle theme" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"></circle><line x1="12" y1="2" x2="12" y2="5"></line><line x1="12" y1="19" x2="12" y2="22"></line><line x1="2" y1="12" x2="5" y2="12"></line><line x1="19" y1="12" x2="22" y2="12"></line><line x1="4.5" y1="4.5" x2="6.5" y2="6.5"></line><line x1="17.5" y1="17.5" x2="19.5" y2="19.5"></line><line x1="4.5" y1="19.5" x2="6.5" y2="17.5"></line><line x1="17.5" y1="6.5" x2="19.5" y2="4.5"></line></svg></button><button class="icon-btn" title="Toggle density" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="7" x2="21" y2="7"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="17" x2="21" y2="17"></line></svg></button><a class="icon-btn outlined" href="https://github.com/geraldmaron/construct" target="_blank" rel="noreferrer"><svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M12 .5C5.65.5.5 5.66.5 12.02c0 5.09 3.29 9.4 7.86 10.92.58.1.79-.25.79-.55v-2.13c-3.2.7-3.87-1.37-3.87-1.37-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.76 2.69 1.25 3.35.96.1-.74.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.7 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.16 1.18a10.9 10.9 0 0 1 5.74 0c2.2-1.49 3.16-1.18 3.16-1.18.62 1.58.23 2.75.12 3.04.74.81 1.18 1.84 1.18 3.1 0 4.43-2.69 5.41-5.25 5.69.41.36.78 1.05.78 2.13v3.16c0 .31.21.66.79.55A11.52 11.52 0 0 0 23.5 12.02C23.5 5.66 18.35.5 12 .5Z"></path></svg> <span style="font-size:11px">repo</span></a></div><div class="progress"></div></header><div class="body-grid"><aside class="sidebar"><div class="side-group"><div class="side-label">Overview</div><a class="side-link active" href="/"><span class="num">01</span><span>Home</span></a><a class="side-link" href="/doctor/"><span class="num">02</span><span>Doctor</span></a><a class="side-link" href="/resources/"><span class="num">03</span><span>Services</span></a></div><div class="side-group"><div class="side-label">Activity</div><a class="side-link" href="/audit/"><span class="num">01</span><span>Audit trail</span></a><a class="side-link" href="/snapshots/"><span class="num">02</span><span>Snapshots</span></a><a class="side-link" href="/performance/"><span class="num">03</span><span>Performance</span></a></div><div class="side-group"><div class="side-label">Work</div><a class="side-link" href="/approvals/"><span class="num">01</span><span>Approvals</span></a><a class="side-link" href="/workflow/"><span class="num">02</span><span>Workflow</span></a><a class="side-link" href="/beads/"><span class="num">03</span><span>Beads</span></a><a class="side-link" href="/artifacts/"><span class="num">04</span><span>Artifacts</span></a><a class="side-link" href="/intake/"><span class="num">05</span><span>Intake</span></a></div><div class="side-group"><div class="side-label">Specialists</div><a class="side-link" href="/agents/"><span class="num">01</span><span>Specialists</span></a><a class="side-link" href="/skills/"><span class="num">02</span><span>Skills</span></a><a class="side-link" href="/commands/"><span class="num">03</span><span>Slash commands</span></a><a class="side-link" href="/hooks/"><span class="num">04</span><span>Hooks</span></a><a class="side-link" href="/plugins/"><span class="num">05</span><span>Plugins</span></a></div><div class="side-group"><div class="side-label">Models &amp; Providers</div><a class="side-link" href="/models/"><span class="num">01</span><span>Models</span></a><a class="side-link" href="/providers/"><span class="num">02</span><span>Providers</span></a><a class="side-link" href="/mcp/"><span class="num">03</span><span>MCP servers</span></a></div><div class="side-group"><div class="side-label">Knowledge</div><a class="side-link" href="/knowledge/"><span class="num">01</span><span>Knowledge</span></a><a class="side-link" href="/editor/"><span class="num">02</span><span>Editor</span></a></div><div class="side-group"><div class="side-label">System</div><a class="side-link" href="/config/"><span class="num">01</span><span>Config</span></a><a class="side-link" href="/infrastructure/"><span class="num">02</span><span>Infrastructure</span></a></div><div class="side-foot">Local dashboard for Construct. Theme + density preferences persist.</div></aside><main class="main"><div class="page"><div class="eyebrow"><span class="dot"></span><span>overview · construct dashboard</span></div><h1 class="page-title">System state</h1><p class="page-lede">The runtime in one place. Approvals, services, model providers, knowledge corpus, and intake — each section deep-links to its dedicated surface.</p><div class="meta-strip"><span class="pill">mode: <!-- -->…</span><span>instance: <!-- -->…</span><span class="sep">·</span><span>embed: <!-- -->stopped</span></div><div class="body"><div class="hero-stats" style="margin-top:0;margin-bottom:24px"><div class="hero-stat"><span class="k">Services</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">running</span></div><div class="hero-stat"><span class="k">Approvals</span><span class="v">…</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">pending</span></div><div class="hero-stat"><span class="k">Skills</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">registered</span></div><div class="hero-stat"><span class="k">Commands</span><span class="v">0</span><span style="display:block;font-size:11.5px;color:var(--muted);margin-top:4px">slash</span></div></div><section class="section open"><button class="section-head" aria-expanded="true" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">01</span><span class="section-title">Pending approvals</span></span><span class="section-aside"><span>empty</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Items waiting on a human gate. High-risk mutations (work item creation, merge, doc publish, config changes) land here.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">02</span><span class="section-title">Services</span></span><span class="section-aside"><span>0/0 running</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Local processes that back the runtime: Postgres, memory bridge, OpenCode bridge, dashboard, embed daemon.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div style="padding:24px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">Loading…</div><p style="margin-top:12px"><a class="link" href="/resources/">Open services dashboard →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">03</span><span class="section-title">Credentials</span></span><span class="section-aside"><span>0 tracked</span></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Provider API keys, billing posture, and 1Password references. Set them in /providers.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><p style="margin-top:12px"><a class="link" href="/providers/">Manage providers →</a></p></div></div></div></div></section><section class="section"><button class="section-head" aria-expanded="false" type="button"><span class="chev"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 6 15 12 9 18"></polyline></svg></span><span style="display:flex;flex-direction:column;gap:6px"><span class="section-num">04</span><span class="section-title">Quick links</span></span><span class="section-aside"></span></button><div class="tldr-row"><div class="tldr"><b>TL;DR</b>Deep links into every dashboard surface.</div></div><div class="section-body-wrap"><div class="section-body"><div class="section-body-inner"><div class="body"><div class="callout"><span class="clt-label">Where to next</span><div><p><a class="link" href="/doctor/">Doctor</a>,<!-- --> <a class="link" href="/knowledge/">Knowledge</a>,<!-- --> <a class="link" href="/intake/">Intake</a>,<!-- --> <a class="link" href="/workflow/">Workflow</a>,<!-- --> <a class="link" href="/agents/">Specialists</a>,<!-- --> <a class="link" href="/models/">Models</a>,<!-- --> <a class="link" href="/audit/">Audit</a>.</p></div></div></div></div></div></div></section></div></div><!--$--><!--/$--></main></div></div><script src="/_next/static/chunks/webpack-8e0503f8a36190b3.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[105,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"7177\",\"static/chunks/app/layout-1a2d2674aa220229.js\"],\"AppShellClient\"]\n3:I[5341,[],\"\"]\n4:I[25,[],\"\"]\n5:I[1012,[],\"ClientPageRoot\"]\n6:I[9008,[\"9664\",\"static/chunks/9664-af80478aa73ba424.js\",\"8144\",\"static/chunks/8144-da56a4a291c0b21a.js\",\"8974\",\"static/chunks/app/page-2568fbdd2b0e1eca.js\"],\"default\"]\n9:I[5104,[],\"OutletBoundary\"]\nb:I[7158,[],\"AsyncMetadataOutlet\"]\nd:I[5104,[],\"ViewportBoundary\"]\nf:I[5104,[],\"MetadataBoundary\"]\n10:\"$Sreact.suspense\"\n12:I[4431,[],\"\"]\n:HL[\"/_next/static/media/22a5144ee8d83bca-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/bb3ef058b751a6ad-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/47a506ef57811fb9.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"qA_CTDVxjnG_0_bArvNYn\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\",[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/47a506ef57811fb9.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"data-theme\":\"dark\",\"data-density\":\"comfortable\",\"data-motion\":\"normal\",\"suppressHydrationWarning\":true,\"className\":\"__variable_629dad __variable_246ccd __variable_3c557b\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L2\",null,{\"nav\":[{\"label\":\"Overview\",\"items\":[{\"id\":\"home\",\"title\":\"Home\",\"href\":\"/\"},{\"id\":\"doctor\",\"title\":\"Doctor\",\"href\":\"/doctor\"},{\"id\":\"resources\",\"title\":\"Services\",\"href\":\"/resources\"}]},{\"label\":\"Activity\",\"items\":[{\"id\":\"audit\",\"title\":\"Audit trail\",\"href\":\"/audit\"},{\"id\":\"snapshots\",\"title\":\"Snapshots\",\"href\":\"/snapshots\"},{\"id\":\"performance\",\"title\":\"Performance\",\"href\":\"/performance\"}]},{\"label\":\"Work\",\"items\":[{\"id\":\"approvals\",\"title\":\"Approvals\",\"href\":\"/approvals\"},{\"id\":\"workflow\",\"title\":\"Workflow\",\"href\":\"/workflow\"},{\"id\":\"beads\",\"title\":\"Beads\",\"href\":\"/beads\"},{\"id\":\"artifacts\",\"title\":\"Artifacts\",\"href\":\"/artifacts\"},{\"id\":\"intake\",\"title\":\"Intake\",\"href\":\"/intake\"}]},{\"label\":\"Specialists\",\"items\":[{\"id\":\"agents\",\"title\":\"Specialists\",\"href\":\"/agents\"},{\"id\":\"skills\",\"title\":\"Skills\",\"href\":\"/skills\"},{\"id\":\"commands\",\"title\":\"Slash commands\",\"href\":\"/commands\"},{\"id\":\"hooks\",\"title\":\"Hooks\",\"href\":\"/hooks\"},{\"id\":\"plugins\",\"title\":\"Plugins\",\"href\":\"/plugins\"}]},{\"label\":\"Models \u0026 Providers\",\"items\":[{\"id\":\"models\",\"title\":\"Models\",\"href\":\"/models\"},{\"id\":\"providers\",\"title\":\"Providers\",\"href\":\"/providers\"},{\"id\":\"mcp\",\"title\":\"MCP servers\",\"href\":\"/mcp\"}]},{\"label\":\"Knowledge\",\"items\":[{\"id\":\"knowledge\",\"title\":\"Knowledge\",\"href\":\"/knowledge\"},{\"id\":\"editor\",\"title\":\"Editor\",\"href\":\"/editor\"}]},{\"label\":\"System\",\"items\":[{\"id\":\"config\",\"title\":\"Config\",\"href\":\"/config\"},{\"id\":\"infrastructure\",\"title\":\"Infrastructure\",\"href\":\"/infrastructure\"}]}],\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]}]]}],{\"children\":[\"__PAGE__\",[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L5\",null,{\"Component\":\"$6\",\"searchParams\":{},\"params\":{},\"promises\":[\"$@7\",\"$@8\"]}],null,[\"$\",\"$L9\",null,{\"children\":[\"$La\",[\"$\",\"$Lb\",null,{\"promise\":\"$@c\"}]]}]]}],{},null,false]},null,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[[\"$\",\"$Ld\",null,{\"children\":\"$Le\"}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]],[\"$\",\"$Lf\",null,{\"children\":[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$10\",null,{\"fallback\":null,\"children\":\"$L11\"}]}]}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$12\",[]],\"s\":false,\"S\":true}\n"])</script><script>self.__next_f.push([1,"7:{}\n8:\"$0:f:0:1:2:children:1:props:children:0:props:params\"\n"])</script><script>self.__next_f.push([1,"e:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\na:null\n"])</script><script>self.__next_f.push([1,"c:{\"metadata\":[[\"$\",\"title\",\"0\",{\"children\":\"Construct — Dashboard\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Local operations dashboard for Construct — approvals, health, knowledge, models, providers.\"}]],\"error\":null,\"digest\":\"$undefined\"}\n"])</script><script>self.__next_f.push([1,"11:\"$c:metadata\"\n"])</script></body></html>
package/lib/setup.mjs CHANGED
@@ -1,10 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * lib/setup.mjs — Interactive first-run setup wizard for Construct.
3
+ * lib/setup.mjs — Machine-scoped first-run setup wizard for Construct.
4
4
  *
5
- * Guides users through provider selection, API key entry, and model tier
6
- * assignment. Writes the resulting config to ~/.cx/env and optionally to
7
- * project-level .env. Invoked by `construct init` and on first init.
5
+ * Installs cm/cass, configures managed defaults, starts local Postgres
6
+ * (consent-driven), pre-warms the embedding model, wires MCP integrations,
7
+ * and writes ~/.construct/config.env. Invoked by `construct install` and
8
+ * by lib/install/first-invocation.mjs when a TTY user is missing resources.
9
+ *
10
+ * Project-scoped scaffolding (`.cx/`, AGENTS.md, plan.md, adapters) lives
11
+ * in lib/init-unified.mjs — invoked by `construct init`. These two flows
12
+ * are intentionally separate: install runs once per machine, init runs
13
+ * once per repo.
8
14
  */
9
15
 
10
16
  import fs from 'node:fs';
@@ -36,10 +42,10 @@ const LOCAL_POSTGRES_DB = 'construct';
36
42
  const LOCAL_DATABASE_URL = `postgresql://${LOCAL_POSTGRES_USER}:${LOCAL_POSTGRES_PASSWORD}@127.0.0.1:${LOCAL_POSTGRES_PORT}/${LOCAL_POSTGRES_DB}`;
37
43
 
38
44
  function printHelp() {
39
- console.log(`Construct setup
45
+ console.log(`Construct install — machine setup (once per machine)
40
46
 
41
47
  Usage:
42
- construct init [--yes] [--no-docker]
48
+ construct install [--yes] [--no-docker]
43
49
 
44
50
  What it does:
45
51
  - creates ~/.construct/config.env
@@ -52,6 +58,7 @@ What it does:
52
58
  - runs construct doctor
53
59
  - detects the project tech stack and writes .cx/project-profile.json
54
60
 
61
+ For project setup (once per repo): construct init
55
62
  Use --yes to run without prompts and accept detected environment defaults.`);
56
63
  }
57
64
 
@@ -465,6 +472,35 @@ export function ensureGitHooksPath({ cwd = process.cwd() } = {}) {
465
472
  };
466
473
  }
467
474
 
475
+ // $HOME/.construct/lib/hooks/* is the resolution path for Claude Code
476
+ // Stop-hook commands. Absent symlink → every Stop hook fails silently,
477
+ // including stop-notify (dashboard cost feed). Idempotent: matching
478
+ // symlink left alone, stale symlink replaced, real dir refused.
479
+
480
+ export function ensureLibSymlink({ homeDir = HOME, rootDir = ROOT_DIR } = {}) {
481
+ const target = path.join(homeDir, '.construct', 'lib');
482
+ const source = path.join(rootDir, 'lib');
483
+ fs.mkdirSync(path.dirname(target), { recursive: true });
484
+ let stat = null;
485
+ try { stat = fs.lstatSync(target); } catch { stat = null; }
486
+ if (!stat) {
487
+ fs.symlinkSync(source, target, 'dir');
488
+ return { status: 'created', target, source };
489
+ }
490
+ if (stat.isSymbolicLink()) {
491
+ const current = fs.readlinkSync(target);
492
+ if (current === source) return { status: 'kept', target, source };
493
+ fs.unlinkSync(target);
494
+ fs.symlinkSync(source, target, 'dir');
495
+ return { status: 'replaced', target, source, previous: current };
496
+ }
497
+ return {
498
+ status: 'conflict',
499
+ target,
500
+ message: `${target} exists and is not a symlink — leaving alone. Move or remove it, then re-run \`construct install\` to wire the hooks.`,
501
+ };
502
+ }
503
+
468
504
  export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME } = {}) {
469
505
  const argSet = new Set(args);
470
506
  const isYes = argSet.has('--yes');
@@ -480,9 +516,17 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
480
516
 
481
517
  const envPath = ensureUserConfig(homeDir);
482
518
  const opencodePath = ensureOpenCodeConfig();
519
+ const libLink = ensureLibSymlink({ homeDir, rootDir });
483
520
 
484
521
  console.log(`User config: ${envPath}`);
485
522
  console.log(`OpenCode config: ${opencodePath}`);
523
+ if (libLink.status === 'created' || libLink.status === 'replaced') {
524
+ console.log(`Hook lib link: ${libLink.target} → ${libLink.source} (${libLink.status})`);
525
+ } else if (libLink.status === 'kept') {
526
+ console.log(`Hook lib link: ${libLink.target} already in place`);
527
+ } else if (libLink.status === 'conflict') {
528
+ console.log(`Hook lib link: ${libLink.message}`);
529
+ }
486
530
  warnIfGlobalCommandIsUnavailable();
487
531
 
488
532
  const cmInstall = ensureCmInstalled({ env: process.env });
@@ -582,6 +626,7 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
582
626
  });
583
627
  if (cheapestConsent.decision) {
584
628
  try {
629
+ const { applyToEnv } = await import('./model-router.mjs');
585
630
  const selections = await selectCheapestForAllTiers({ env: process.env });
586
631
  const applied = {};
587
632
  for (const tier of ['reasoning', 'standard', 'fast']) {
@@ -637,7 +682,7 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
637
682
 
638
683
  if (isYes) {
639
684
  console.log('\nManaged setup:');
640
- console.log(` Deployment mode: ${getDeploymentMode(env) || DEFAULT_DEPLOYMENT_MODE} (set in construct.config.json — runtime env override available via ${DEPLOYMENT_MODE_ENV_KEY})`);
685
+ console.log(` Deployment mode: ${getDeploymentMode(process.env) || DEFAULT_DEPLOYMENT_MODE} (set in construct.config.json — runtime env override available via ${DEPLOYMENT_MODE_ENV_KEY})`);
641
686
  console.log(` Vector index: ${managedValues.CONSTRUCT_VECTOR_INDEX_PATH}`);
642
687
  console.log(` Vector model: ${managedValues.CONSTRUCT_VECTOR_MODEL}`);
643
688
  console.log(` Trace backend: ${managedValues.CONSTRUCT_TRACE_BACKEND}`);
@@ -723,12 +768,12 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
723
768
  runConstruct(['mcp', 'add', 'github', '--auto'], { optional: true });
724
769
  } else {
725
770
  console.log('\nManaged defaults written:');
726
- console.log(` Deployment mode: ${getDeploymentMode(env) || DEFAULT_DEPLOYMENT_MODE} (set in construct.config.json — runtime env override available via ${DEPLOYMENT_MODE_ENV_KEY})`);
771
+ console.log(` Deployment mode: ${getDeploymentMode(process.env) || DEFAULT_DEPLOYMENT_MODE} (set in construct.config.json — runtime env override available via ${DEPLOYMENT_MODE_ENV_KEY})`);
727
772
  console.log(` Vector index: ${managedValues.CONSTRUCT_VECTOR_INDEX_PATH}`);
728
773
  console.log(` Trace backend: ${managedValues.CONSTRUCT_TRACE_BACKEND}${managedValues.CONSTRUCT_TELEMETRY_URL ? ` (${managedValues.CONSTRUCT_TELEMETRY_URL})` : ''}`);
729
774
  console.log(` Pressure guard: swap ${managedValues.CONSTRUCT_PRESSURE_GUARD_SWAP_GB} GiB, opencode max ${managedValues.CONSTRUCT_PRESSURE_GUARD_MAX_OPENCODE}`);
730
775
  console.log('\nFor unattended setup, including local Postgres when Docker is running:');
731
- console.log(' construct init --yes');
776
+ console.log(' construct install --yes');
732
777
  }
733
778
 
734
779
  runConstruct(['sync']);
@@ -100,7 +100,7 @@ export const POSTCONDITIONS = {
100
100
  * @param {object} packet — the output packet about to hand off
101
101
  * @returns {{ ok: boolean, producer: string, failures: Array<{id, reason}> }}
102
102
  */
103
- export function validatePostconditions(producer, packet) {
103
+ export function validateBinaryPostconditions(producer, packet) {
104
104
  const rules = POSTCONDITIONS[producer] || [];
105
105
  if (rules.length === 0) return { ok: true, producer, failures: [] };
106
106
  const failures = [];