@geraldmaron/construct 1.0.18 → 1.0.20

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 (93) hide show
  1. package/README.md +8 -4
  2. package/bin/construct +105 -3
  3. package/db/schema/003_observation_reconciliation.sql +14 -0
  4. package/lib/bootstrap/resources.mjs +0 -1
  5. package/lib/cli-commands.mjs +57 -6
  6. package/lib/comment-lint.mjs +44 -0
  7. package/lib/config/schema.mjs +31 -1
  8. package/lib/contracts/validate.mjs +106 -0
  9. package/lib/decisions/enforced-baseline.json +25 -0
  10. package/lib/decisions/golden.mjs +87 -0
  11. package/lib/decisions/precedence.mjs +46 -0
  12. package/lib/decisions/registry.mjs +469 -0
  13. package/lib/deployment/parity-contract.mjs +148 -0
  14. package/lib/document-ingest.mjs +71 -6
  15. package/lib/embed/cli.mjs +11 -0
  16. package/lib/embed/conflict-detection.mjs +4 -4
  17. package/lib/embed/customer-profiles.mjs +1 -1
  18. package/lib/embed/reconcile.mjs +60 -0
  19. package/lib/embedded-contract/capability.mjs +3 -3
  20. package/lib/embedded-contract/contract-version.mjs +1 -1
  21. package/lib/embedded-contract/execution.mjs +184 -0
  22. package/lib/embedded-contract/index.mjs +16 -0
  23. package/lib/embedded-contract/ingest.mjs +61 -7
  24. package/lib/gates-audit.mjs +2 -2
  25. package/lib/hooks/_lib/output-mode.mjs +101 -0
  26. package/lib/hooks/config-protection.mjs +22 -3
  27. package/lib/hooks/guard-bash.mjs +1 -1
  28. package/lib/hooks/session-start.mjs +13 -1
  29. package/lib/ingest/provider-extract.mjs +200 -0
  30. package/lib/ingest/strategy.mjs +95 -0
  31. package/lib/init-docs.mjs +1 -0
  32. package/lib/mcp/server.mjs +72 -4
  33. package/lib/mcp/tools/embedded-contract.mjs +17 -1
  34. package/lib/mode-commands.mjs +6 -8
  35. package/lib/observation-store.mjs +16 -2
  36. package/lib/opencode-telemetry.mjs +1 -1
  37. package/lib/orchestration/run-store.mjs +82 -0
  38. package/lib/orchestration/runtime.mjs +240 -0
  39. package/lib/roles/cli.mjs +10 -2
  40. package/lib/roles/gateway.mjs +50 -1
  41. package/lib/scheduler/index.mjs +31 -0
  42. package/lib/server/index.mjs +13 -3
  43. package/lib/server/static/index.html +1 -1
  44. package/lib/setup.mjs +6 -0
  45. package/lib/storage/hybrid-query.mjs +49 -38
  46. package/lib/storage/rrf.mjs +42 -0
  47. package/lib/storage/vector-client.mjs +18 -3
  48. package/lib/telemetry/backends/local.mjs +1 -1
  49. package/lib/telemetry/skill-calls.mjs +1 -1
  50. package/lib/templates/visual-requirements.mjs +84 -0
  51. package/package.json +10 -1
  52. package/rules/common/comments.md +3 -0
  53. package/rules/common/no-fabrication.md +3 -0
  54. package/rules/common/precedence.md +19 -0
  55. package/rules/common/research-sources.md +32 -0
  56. package/rules/common/research.md +59 -2
  57. package/skills/roles/data-engineer.pipeline.md +13 -1
  58. package/skills/roles/debugger.md +9 -0
  59. package/skills/roles/designer.accessibility.md +13 -3
  60. package/skills/roles/designer.md +10 -0
  61. package/skills/roles/engineer.platform.md +8 -0
  62. package/skills/roles/operator.md +10 -1
  63. package/skills/roles/operator.release.md +8 -0
  64. package/skills/roles/operator.sre.md +10 -1
  65. package/skills/roles/orchestrator.md +9 -2
  66. package/skills/roles/product-manager.business-strategy.md +10 -1
  67. package/skills/roles/researcher.explorer.md +12 -1
  68. package/skills/roles/researcher.ux.md +13 -1
  69. package/skills/roles/reviewer.devil-advocate.md +14 -2
  70. package/skills/roles/reviewer.evaluator.md +17 -4
  71. package/skills/roles/reviewer.trace.md +12 -1
  72. package/skills/roles/security.legal-compliance.md +8 -0
  73. package/skills/roles/security.md +11 -0
  74. package/specialists/contracts.json +18 -0
  75. package/specialists/prompts/cx-researcher.md +4 -2
  76. package/templates/docs/backlog-proposal.md +1 -1
  77. package/templates/docs/customer-profile.md +1 -1
  78. package/templates/docs/evidence-brief.md +5 -1
  79. package/templates/docs/incident-report.md +37 -21
  80. package/templates/docs/prfaq.md +2 -2
  81. package/templates/docs/product-intelligence-report.md +1 -1
  82. package/templates/docs/research-brief.md +8 -6
  83. package/templates/docs/research-finding.md +32 -7
  84. package/templates/docs/rfc.md +13 -1
  85. package/templates/docs/runbook.md +20 -1
  86. package/templates/docs/signal-brief.md +4 -1
  87. package/templates/docs/skill-artifact.md +27 -7
  88. package/templates/docs/strategy.md +23 -2
  89. package/lib/bootstrap/lazy-install.mjs +0 -161
  90. package/lib/embed/jobs/vector-sync.mjs +0 -198
  91. package/lib/knowledge/postgres-search.mjs +0 -132
  92. package/lib/services/pattern-promotion-service.mjs +0 -167
  93. package/lib/storage/unified-storage.mjs +0 -550
@@ -130,3 +130,34 @@ registerJob({
130
130
  };
131
131
  },
132
132
  });
133
+
134
+ // The prompt-optimize loop reads telemetry traces and proposes prompt patches.
135
+ // Scheduled runs are advisory by construction: the argv never carries --apply, so
136
+ // a cadence surfaces proposals for review and can never auto-mutate a prompt. The
137
+ // existing --apply gate in scripts/optimize.mjs remains the only path that writes.
138
+
139
+ export const OPTIMIZE_JOB_ID = 'optimize-loop';
140
+
141
+ export function optimizeJobArgv() {
142
+ return [];
143
+ }
144
+
145
+ registerJob({
146
+ id: OPTIMIZE_JOB_ID,
147
+ schedule: '0 5 * * 1',
148
+ mode: 'solo',
149
+ handler: async ({ cwd, env }) => {
150
+ const argv = optimizeJobArgv();
151
+ if (argv.includes('--apply')) throw new Error('scheduled optimize must never auto-apply');
152
+ const { execFileSync } = await import('node:child_process');
153
+ const { fileURLToPath } = await import('node:url');
154
+ const { resolve, dirname } = await import('node:path');
155
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
156
+ try {
157
+ const output = execFileSync('node', [resolve(root, 'scripts', 'optimize.mjs'), ...argv], { cwd, env, encoding: 'utf8' });
158
+ return { status: 'ok', applied: false, output: output.slice(0, 500) };
159
+ } catch (err) {
160
+ return { status: 'error', applied: false, error: err.message };
161
+ }
162
+ },
163
+ });
@@ -713,9 +713,19 @@ const server = createServer(async (req, res) => {
713
713
  return;
714
714
  }
715
715
 
716
- if (url.pathname === '/api/services/langfuse/login' && req.method === 'GET') {
717
- const { handleLangfuseLogin } = await import('./langfuse-login.mjs');
718
- await handleLangfuseLogin(req, res);
716
+ // Provider-agnostic telemetry-UI sign-in. An external backend configured via
717
+ // CONSTRUCT_TELEMETRY_URL uses the vendor-neutral bridge; with none set, fall
718
+ // back to the zero-touch local Langfuse stack. `/langfuse/login` stays as a
719
+ // back-compat alias of the canonical `/telemetry/login`.
720
+
721
+ if ((url.pathname === '/api/services/telemetry/login' || url.pathname === '/api/services/langfuse/login') && req.method === 'GET') {
722
+ if (process.env.CONSTRUCT_TELEMETRY_URL) {
723
+ const { handleTelemetryLogin } = await import('./telemetry-login.mjs');
724
+ await handleTelemetryLogin(req, res, {});
725
+ } else {
726
+ const { handleLangfuseLogin } = await import('./langfuse-login.mjs');
727
+ await handleLangfuseLogin(req, res);
728
+ }
719
729
  return;
720
730
  }
721
731
 
@@ -1 +1 @@
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>
1
+ <!DOCTYPE html><html lang="en" data-theme="dark" data-density="comfortable" data-motion="normal" class="geist_478ec610-module__Maz4kG__variable geist_d633ac5f-module__BboVrW__variable jetbrains_mono_dc177b0e-module__78ed6G__variable"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/70bc3e132a0a741e-s.p.3t6q91iet4nsy.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/chunks/07baqf-hmk6oo.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/0i88v4ql6s7aw.js"/><script src="/_next/static/chunks/26rq-xwqn81a7.js" async=""></script><script src="/_next/static/chunks/1q2ld_2skrq5q.js" async=""></script><script src="/_next/static/chunks/41yml7ep3qrnx.js" async=""></script><script src="/_next/static/chunks/turbopack-1wenwimq-yd36.js" async=""></script><script src="/_next/static/chunks/39l2haooi0nh6.js" async=""></script><script src="/_next/static/chunks/1-8s9_t85wwr4.js" async=""></script><script src="/_next/static/chunks/0cangwgy_395t.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/0cz1d0mv5g_q7.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/0i88v4ql6s7aw.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[95327,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"AppShellClient\"]\n3:I[39756,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"default\"]\n4:I[37457,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"default\"]\n5:I[47257,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"ClientPageRoot\"]\n6:I[62453,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\",\"/_next/static/chunks/0cangwgy_395t.js\"],\"default\"]\n9:I[97367,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"OutletBoundary\"]\na:\"$Sreact.suspense\"\nc:I[97367,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"ViewportBoundary\"]\ne:I[97367,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"MetadataBoundary\"]\n10:I[68027,[\"/_next/static/chunks/39l2haooi0nh6.js\",\"/_next/static/chunks/1-8s9_t85wwr4.js\"],\"default\",1]\n:HL[\"/_next/static/chunks/07baqf-hmk6oo.css\",\"style\"]\n:HL[\"/_next/static/media/70bc3e132a0a741e-s.p.3t6q91iet4nsy.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",16],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/07baqf-hmk6oo.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/39l2haooi0nh6.js\",\"async\":true,\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-1\",{\"src\":\"/_next/static/chunks/1-8s9_t85wwr4.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"data-theme\":\"dark\",\"data-density\":\"comfortable\",\"data-motion\":\"normal\",\"suppressHydrationWarning\":true,\"className\":\"geist_478ec610-module__Maz4kG__variable geist_d633ac5f-module__BboVrW__variable jetbrains_mono_dc177b0e-module__78ed6G__variable\",\"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\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L5\",null,{\"Component\":\"$6\",\"serverProvidedParams\":{\"searchParams\":{},\"params\":{},\"promises\":[\"$@7\",\"$@8\"]}}],[[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/0cangwgy_395t.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"$L9\",null,{\"children\":[\"$\",\"$a\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@b\"}]}]]}],{},null,false,null]},null,false,null],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$Lc\",null,{\"children\":\"$Ld\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$Le\",null,{\"children\":[\"$\",\"$a\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lf\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$10\",[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/07baqf-hmk6oo.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"piZVVlR5yulp6NxkELzTZ\"}\n"])</script><script>self.__next_f.push([1,"7:{}\n8:\"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params\"\n"])</script><script>self.__next_f.push([1,"d:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"b:null\nf:[[\"$\",\"title\",\"0\",{\"children\":\"Construct — Dashboard\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Local operations dashboard for Construct — approvals, health, knowledge, models, providers.\"}]]\n"])</script></body></html>
package/lib/setup.mjs CHANGED
@@ -794,7 +794,13 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
794
794
  console.log(' construct install --yes');
795
795
  }
796
796
 
797
+ // Install is machine setup: the global front-door agent must land on every
798
+ // user-scope surface (opencode/claude/codex/copilot) or a fresh machine fails
799
+ // cross-surface parity. Plain sync writes global hooks + project tier but not
800
+ // the front-door agent — `--global` is what populates it, so run both.
801
+
797
802
  runConstruct(['sync']);
803
+ runConstruct(['sync', '--global']);
798
804
  runConstruct(['doctor']);
799
805
 
800
806
  // Profile the CWD so per-host skill filtering layers downstream have
@@ -6,8 +6,24 @@ import { loadStateSnapshot, summarizeStateSnapshot } from './state-source.mjs';
6
6
  import { describeSqlStore } from './sql-store.mjs';
7
7
  import { describeVectorStore, searchLocalVectorIndex, vectorSearchLocal } from './vector-store.mjs';
8
8
  import { createSqlClient, closeSqlClient, readVectorConfig } from './backend.mjs';
9
- import { scoreByEmbedding } from './embeddings.mjs';
10
9
  import { embedText, getEmbeddingModelInfo } from './embeddings-engine.mjs';
10
+ import { floatArrayToPgVector } from './vector-client.mjs';
11
+ import { reciprocalRankFusion } from './rrf.mjs';
12
+
13
+ // iterative_scan (pgvector >= 0.8.0) keeps a filtered ANN query from
14
+ // under-returning; harmless for the current unfiltered query, and the correct
15
+ // default once tag/metadata filters are added to the vector search.
16
+
17
+ async function supportsIterativeScan(client) {
18
+ try {
19
+ const [row] = await client`SELECT extversion AS v FROM pg_extension WHERE extname = 'vector'`;
20
+ if (!row?.v) return false;
21
+ const [major, minor] = String(row.v).split('.').map((n) => parseInt(n, 10));
22
+ return major > 0 || (major === 0 && minor >= 8);
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
11
27
 
12
28
  function collectFileCandidates(snapshot) {
13
29
  const docs = [];
@@ -104,33 +120,38 @@ export function buildHybridSearchResults(rootDir, query, { limit = 10, env = pro
104
120
  };
105
121
  }
106
122
 
107
- export async function buildHybridSearchResultsAsync(rootDir, query, { limit = 10, env = process.env } = {}) {
123
+ export async function buildHybridSearchResultsAsync(rootDir, query, { limit = 10, env = process.env, embed = embedText, embeddingModel: modelOverride = null } = {}) {
108
124
  const base = buildHybridSearchResults(rootDir, query, { limit, env });
109
125
  const client = createSqlClient(env);
110
126
  if (!client) return base;
111
127
 
112
128
  // Resolve the active embedding model so the SQL filter and the query
113
129
  // embedding agree on dimensionality and identity.
114
- const modelInfo = await getEmbeddingModelInfo({ env });
115
- const embeddingModel = modelInfo.model;
130
+ const embeddingModel = modelOverride || (await getEmbeddingModelInfo({ env })).model;
116
131
 
117
132
  try {
118
- const embeddingRows = await client`
119
- select d.id, d.kind, d.title, d.summary, d.body, d.source_path, d.tags, e.embedding
133
+ const queryVec = floatArrayToPgVector((await embed(query, { env })).embedding);
134
+ const iterative = await supportsIterativeScan(client);
135
+
136
+ // Native pgvector ANN over the HNSW index (not a JS full scan). When the
137
+ // extension supports it, relaxed_order iterative scan runs inside the txn.
138
+ const runVector = (sql) => sql`
139
+ select d.id, d.kind, d.title, d.summary, d.source_path
120
140
  from construct_documents d
121
141
  join construct_embeddings e on e.document_id = d.id
122
142
  where d.project = 'construct' and e.model = ${embeddingModel}
143
+ order by e.embedding <=> ${queryVec}
144
+ limit ${limit}
123
145
  `;
146
+ const vectorHits = iterative
147
+ ? await client.begin(async (sql) => {
148
+ await sql`SET LOCAL hnsw.iterative_scan = relaxed_order`;
149
+ return runVector(sql);
150
+ })
151
+ : await runVector(client);
124
152
 
125
- const queryEmbedding = (await embedText(query, { env })).embedding;
126
- const embeddedHits = scoreByEmbedding(
127
- embeddingRows.map((row) => ({ ...row, embedding: row.embedding })),
128
- Array.from(queryEmbedding),
129
- { limit },
130
- );
131
-
132
- const sqlHits = await client`
133
- select id, kind, title, summary, body, source_path
153
+ const keywordHits = await client`
154
+ select id, kind, title, summary, source_path
134
155
  from construct_documents
135
156
  where project = 'construct'
136
157
  and (title ilike ${`%${query}%`} or coalesce(summary, '') ilike ${`%${query}%`} or body ilike ${`%${query}%`})
@@ -138,39 +159,29 @@ export async function buildHybridSearchResultsAsync(rootDir, query, { limit = 10
138
159
  limit ${limit}
139
160
  `;
140
161
 
141
- const merged = [...base.results];
142
- for (const hit of embeddedHits) {
143
- if (merged.some((entry) => entry.id === hit.id)) continue;
144
- merged.push({
145
- id: hit.id,
146
- kind: hit.kind,
147
- title: hit.title,
148
- summary: hit.summary,
149
- score: hit.score,
150
- source_path: hit.source_path,
151
- });
152
- }
153
- for (const hit of sqlHits) {
154
- if (merged.some((entry) => entry.id === hit.id)) continue;
155
- merged.push({
156
- id: hit.id,
157
- kind: hit.kind,
158
- title: hit.title,
159
- summary: hit.summary,
160
- score: 1,
161
- source_path: hit.source_path,
162
- });
162
+ // One consolidated ranking: file BM25 (base) + neural ANN + keyword, fused
163
+ // by Reciprocal Rank Fusion so three incompatible score scales merge by
164
+ // rank rather than magnitude.
165
+ const lists = [base.results, vectorHits, keywordHits];
166
+ const byId = new Map();
167
+ for (const list of lists) {
168
+ for (const it of list) if (it && !byId.has(it.id)) byId.set(it.id, it);
163
169
  }
170
+ const results = reciprocalRankFusion(lists, { idOf: (x) => x.id, limit }).map(({ id, score }) => {
171
+ const it = byId.get(id);
172
+ return { id, kind: it.kind, title: it.title, summary: it.summary, score, source_path: it.source_path ?? null };
173
+ });
164
174
 
165
175
  return {
166
176
  ...base,
167
- results: merged.slice(0, limit),
177
+ results,
168
178
  stores: {
169
179
  ...base.stores,
170
180
  vector: {
171
181
  ...base.stores.vector,
172
182
  ...readVectorConfig(env),
173
183
  model: embeddingModel,
184
+ iterativeScan: iterative,
174
185
  },
175
186
  sql: {
176
187
  ...base.stores.sql,
@@ -0,0 +1,42 @@
1
+ /**
2
+ * lib/storage/rrf.mjs — Reciprocal Rank Fusion for hybrid retrieval.
3
+ *
4
+ * Combines N independently-ranked result lists (e.g. BM25 keyword and cosine
5
+ * vector) into one ranking by summing 1/(k + rank) across the lists a document
6
+ * appears in. Fusion is by RANK, not raw score, so it merges lists whose scores
7
+ * live on incompatible scales — BM25 is unbounded-positive, cosine is [-1,1] —
8
+ * without any normalization or hand-tuned weighting. k smooths how much top
9
+ * ranks dominate; 60 is the field default (Cormack, Clarke & Büttcher,
10
+ * "Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning
11
+ * Methods", SIGIR 2009) and the dominant fusion method across search engines.
12
+ */
13
+
14
+ // Each ranked list is ordered best-first. A document's RRF score is the sum,
15
+ // over every list it appears in, of 1/(k + rank) where rank is 1-based.
16
+
17
+ export function reciprocalRankFusion(rankedLists, { k = 60, idOf = (x) => x.id, limit = null } = {}) {
18
+ if (!Array.isArray(rankedLists) || rankedLists.length === 0) return [];
19
+ if (!Number.isFinite(k) || k <= 0) throw new Error('reciprocalRankFusion: k must be a positive number');
20
+
21
+ const scores = new Map();
22
+ const items = new Map();
23
+
24
+ for (const list of rankedLists) {
25
+ if (!Array.isArray(list)) continue;
26
+ for (let rank = 1; rank <= list.length; rank += 1) {
27
+ const item = list[rank - 1];
28
+ if (item == null) continue;
29
+ const id = idOf(item);
30
+ if (id == null) continue;
31
+ scores.set(id, (scores.get(id) || 0) + 1 / (k + rank));
32
+ if (!items.has(id)) items.set(id, item);
33
+ }
34
+ }
35
+
36
+ // Deterministic order: by fused score desc, then id asc to break ties stably.
37
+ const fused = [...scores.entries()]
38
+ .sort((a, b) => (b[1] - a[1]) || String(a[0]).localeCompare(String(b[0])))
39
+ .map(([id, score]) => ({ item: items.get(id), id, score }));
40
+
41
+ return limit != null ? fused.slice(0, limit) : fused;
42
+ }
@@ -161,7 +161,7 @@ export class VectorClient {
161
161
  /**
162
162
  * Store an observation with its embedding.
163
163
  */
164
- async storeObservation({ id, project, role, category, summary, content, tags, confidence, source, embedding, gitSha }) {
164
+ async storeObservation({ id, project, role, category, summary, content, tags, confidence, source, embedding, gitSha, contentHash = null, model = null }) {
165
165
  const sql = await this._getSql();
166
166
  if (!sql) return { mode: 'file', reason: 'no_sql' };
167
167
  const expected = await this.getEngineDimensions();
@@ -169,8 +169,8 @@ export class VectorClient {
169
169
 
170
170
  const embeddingVec = floatArrayToPgVector(embedding);
171
171
  await sql`
172
- INSERT INTO construct_observations (id, project, role, category, summary, content, tags, confidence, source, git_sha, embedding)
173
- VALUES (${id}, ${project}, ${role}, ${category}, ${summary}, ${content}, ${JSON.stringify(tags || [])}, ${confidence || 0.8}, ${source || null}, ${gitSha || null}, ${embeddingVec})
172
+ INSERT INTO construct_observations (id, project, role, category, summary, content, tags, confidence, source, git_sha, embedding, content_hash, model)
173
+ VALUES (${id}, ${project}, ${role}, ${category}, ${summary}, ${content}, ${JSON.stringify(tags || [])}, ${confidence || 0.8}, ${source || null}, ${gitSha || null}, ${embeddingVec}, ${contentHash}, ${model})
174
174
  ON CONFLICT (id) DO UPDATE SET
175
175
  summary = EXCLUDED.summary,
176
176
  content = EXCLUDED.content,
@@ -179,11 +179,26 @@ export class VectorClient {
179
179
  source = EXCLUDED.source,
180
180
  git_sha = EXCLUDED.git_sha,
181
181
  embedding = EXCLUDED.embedding,
182
+ content_hash = EXCLUDED.content_hash,
183
+ model = EXCLUDED.model,
182
184
  updated_at = now()
183
185
  `;
184
186
  return { mode: 'sql', id };
185
187
  }
186
188
 
189
+ // Fingerprint = the (content_hash, model) a stored observation was embedded
190
+ // with. Reconciliation compares this against the live content + current model
191
+ // to find rows needing re-embedding without re-reading every vector.
192
+
193
+ async getObservationFingerprints(ids = []) {
194
+ const sql = await this._getSql();
195
+ if (!sql || !Array.isArray(ids) || ids.length === 0) return new Map();
196
+ const rows = await sql`
197
+ SELECT id, content_hash, model FROM construct_observations WHERE id = ANY(${ids})
198
+ `;
199
+ return new Map(rows.map((r) => [r.id, { contentHash: r.content_hash, model: r.model }]));
200
+ }
201
+
187
202
  /**
188
203
  * Search observations by vector similarity.
189
204
  */
@@ -30,7 +30,7 @@ export async function listTraces(teamId, windowMs) {
30
30
  if (!existsSync(dir)) return [];
31
31
 
32
32
  const since = Date.now() - windowMs;
33
- const files = readdirSync(TRACES_DIR)
33
+ const files = readdirSync(dir)
34
34
  .filter((f) => f.endsWith('.jsonl'))
35
35
  .sort()
36
36
  .reverse()
@@ -32,7 +32,7 @@ export const DEFAULT_LOG_PATH = path.join(os.homedir(), '.cx', 'skill-calls.json
32
32
  *
33
33
  * @param {object} event
34
34
  * @param {string} event.skillId — path-relative-to-skills/ without the .md, e.g. "roles/engineer.platform"
35
- * @param {'mcp'|'prompt-composer'|'role-preload'|'pattern-promotion'|'validation'|'other'} event.source
35
+ * @param {'mcp'|'prompt-composer'|'role-preload'|'validation'|'other'} event.source
36
36
  * @param {string} [event.callerContext] — optional free-form context (agent name, MCP client id, etc.)
37
37
  * @param {object} [opts]
38
38
  * @param {string} [opts.logPath] — override the default log path (tests pass a tmpdir)
@@ -0,0 +1,84 @@
1
+ /**
2
+ * lib/templates/visual-requirements.mjs — required visuals per document type.
3
+ *
4
+ * The machine-readable form of docs/concepts/doc-visual-matrix.md: which doc
5
+ * types must carry which visual (a diagram of a given kind, or a table with given
6
+ * columns), expressed as postcondition checks so the existing validate.mjs engine
7
+ * enforces them. The shipped template for each listed type must satisfy its own
8
+ * requirements — pinned by tests/template-visuals.test.mjs — so the templates can
9
+ * never drift out of step with the matrix (beads wvbf.10 / wvbf.11).
10
+ */
11
+
12
+ import { validateArtifactPostconditions } from '../contracts/validate.mjs';
13
+
14
+ export const VISUAL_REQUIREMENTS = {
15
+ runbook: [
16
+ { id: 'runbook-diagnostic-flowchart', check: 'artifact-has-mermaid', diagram: 'flowchart' },
17
+ ],
18
+ 'incident-report': [
19
+ { id: 'incident-timeline-table', check: 'artifact-table-has-columns', columns: ['Time (UTC)', 'Event'] },
20
+ ],
21
+ rfc: [
22
+ { id: 'rfc-sequence-diagram', check: 'artifact-has-mermaid', diagram: 'sequenceDiagram' },
23
+ ],
24
+ };
25
+
26
+ export function visualRequirementTypes() {
27
+ return Object.keys(VISUAL_REQUIREMENTS);
28
+ }
29
+
30
+ // The structural floor of the quality rubric (ADR-0018): the sections a domain
31
+ // expert expects for each doc type. Each shipped template must satisfy its own
32
+ // entry — pinned by tests/structure-requirements.test.mjs — so a template can
33
+ // never quietly drop a required section. Section requirements compose with the
34
+ // VISUAL_REQUIREMENTS above; lintDocStructure runs both.
35
+
36
+ export const STRUCTURE_REQUIREMENTS = {
37
+ adr: ['Problem', 'Decision', 'Rejected alternatives', 'Consequences', 'Reversibility'],
38
+ rfc: ['Summary', 'Motivation', 'Proposed design', 'Risks', 'Verification'],
39
+ prd: ['Problem', 'Goals', 'Success metrics', 'Risks and mitigations'],
40
+ 'research-brief': ['Sources', 'Findings', 'Confidence summary', 'Recommendation'],
41
+ 'incident-report': ['Summary', 'Severity rationale', 'Impact', 'Timeline', 'Trigger', 'Root cause', 'Contributing factors', 'Action items'],
42
+ runbook: ['Alert trigger', 'Symptoms', 'Impact', 'Severity and response', 'Diagnostic steps', 'Remediation', 'Rollback', 'Escalation'],
43
+ strategy: ['Vision', 'Bets', 'Non-bets', 'North Star Metric', 'Metrics', 'Milestones', 'Risks'],
44
+ 'signal-brief': ['Signal', 'Evidence', 'Counter-signal', 'What would make this actionable'],
45
+ prfaq: ['Problem statement', 'Press release', 'External FAQ', 'Internal FAQ', 'Evidence appendix'],
46
+ 'customer-profile': ['Snapshot', 'Active pain points', 'Open asks', 'Evidence links'],
47
+ 'one-pager': ['Problem', 'Proposal', 'Why now', 'Success measure', 'Cost', 'Asks'],
48
+ 'product-intelligence-report': ['Executive readout', 'Evidence base', 'Themes', 'Customer asks', 'Product implications', 'Recommended actions', 'Gaps and risks'],
49
+ 'backlog-proposal': ['Source evidence', 'Proposed changes', 'Duplicate and conflict check', 'Approval request'],
50
+ 'persona-artifact': ['Goals', 'Frustrations', 'Decision rights', 'Output contract', 'Failure modes', 'Evidence'],
51
+ 'skill-artifact': ['What this skill produces', 'When to invoke it', 'Competency rubric', 'Failure modes', 'Worked example'],
52
+ 'research-finding': ['SOURCES', 'FINDINGS', 'INFERENCES', 'CONFIDENCE', 'GAPS', 'RECOMMENDATION'],
53
+ };
54
+
55
+ export function structureRequirementTypes() {
56
+ return [...new Set([...Object.keys(STRUCTURE_REQUIREMENTS), ...Object.keys(VISUAL_REQUIREMENTS)])];
57
+ }
58
+
59
+ /**
60
+ * Lint one document against both the required sections and required visuals for
61
+ * its type. Returns an array of violation strings (empty when satisfied or when
62
+ * the type declares no requirements).
63
+ */
64
+ export function lintDocStructure(filePath, type) {
65
+ const sectionChecks = (STRUCTURE_REQUIREMENTS[type] || []).map((section) => ({
66
+ id: `${type}-section-${section}`,
67
+ check: 'artifact-has-section',
68
+ section,
69
+ }));
70
+ const postconditions = [...sectionChecks, ...(VISUAL_REQUIREMENTS[type] || [])];
71
+ if (postconditions.length === 0) return [];
72
+ return validateArtifactPostconditions({ contract: { postconditions }, artifactPath: filePath });
73
+ }
74
+
75
+ /**
76
+ * Lint one document against the visual requirements for its type. Returns an
77
+ * array of violation strings (empty when satisfied, or when the type has no
78
+ * declared visual requirements).
79
+ */
80
+ export function lintDocVisuals(filePath, type) {
81
+ const postconditions = VISUAL_REQUIREMENTS[type];
82
+ if (!postconditions) return [];
83
+ return validateArtifactPostconditions({ contract: { postconditions }, artifactPath: filePath });
84
+ }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@geraldmaron/construct",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "type": "module",
5
+ "packageManager": "npm@11.5.1",
5
6
  "description": "Construct — agent orchestration layer for OpenCode, Claude Code, and other coding surfaces",
6
7
  "bin": {
7
8
  "construct": "bin/construct"
@@ -54,6 +55,8 @@
54
55
  "test": "node scripts/run-tests.mjs",
55
56
  "test:functional": "node --test --test-timeout=120000 --test-concurrency=1 tests/functional/*.functional.test.mjs",
56
57
  "test:unit": "node scripts/run-tests.mjs --exclude=tests/functional",
58
+ "lint:js": "eslint bin/construct \"lib/**/*.mjs\" \"scripts/**/*.mjs\" \"tests/**/*.mjs\"",
59
+ "coverage": "c8 --reporter=text-summary --reporter=lcov --src=lib --src=bin node scripts/run-tests.mjs --exclude=tests/functional",
57
60
  "docs:init": "node lib/init-docs.mjs --yes",
58
61
  "docs:update": "node ./bin/construct docs:update",
59
62
  "docs:site": "node ./bin/construct docs:site",
@@ -92,5 +95,11 @@
92
95
  },
93
96
  "overrides": {
94
97
  "express-rate-limit": "8.5.1"
98
+ },
99
+ "devDependencies": {
100
+ "@eslint/js": "^9.39.4",
101
+ "c8": "^11.0.0",
102
+ "eslint": "^9.39.4",
103
+ "globals": "^17.6.0"
95
104
  }
96
105
  }
@@ -1,5 +1,8 @@
1
1
  ---
2
2
  description: Construct comment convention for JS/TS/MJS source files.
3
+ enforced_by: lib/comment-lint.mjs
4
+ adr_reference: ADR-0015
5
+ precedence_tier: style
3
6
  ---
4
7
  # Comment Convention
5
8
 
@@ -1,5 +1,8 @@
1
1
  ---
2
2
  description: canonical anti-fabrication policy for Construct.
3
+ enforced_by: lib/comment-lint.mjs, lib/contracts/validate.mjs
4
+ adr_reference: ADR-0015
5
+ precedence_tier: correctness
3
6
  ---
4
7
  # No-Fabrication Policy
5
8
 
@@ -0,0 +1,19 @@
1
+ ---
2
+ description: Canonical precedence order for resolving conflicting guidance.
3
+ enforced_by: lib/decisions/precedence.mjs
4
+ adr_reference: ADR-0015
5
+ ---
6
+ # Rule precedence
7
+
8
+ When two rules give contradictory direction for the same situation, the conflict resolves by tier, not by recency or proximity in the prompt. The canonical order, highest priority first:
9
+
10
+ 1. **safety** — preventing destructive or irreversible harm (data loss, secret exposure, production damage).
11
+ 2. **security** — preventing unauthorized access, injection, or privilege escalation.
12
+ 3. **correctness** — producing truthful, accurate, non-fabricated output that does what it claims.
13
+ 4. **durability** — keeping decisions and state from silently drifting or being lost.
14
+ 5. **performance** — speed, cost, and resource efficiency.
15
+ 6. **style** — naming, formatting, comment convention, and other presentation choices.
16
+
17
+ A rule may declare its tier in frontmatter (`precedence_tier: <tier>`). A higher tier always governs: a style rule never overrides a correctness rule, and a performance optimization never overrides a safety constraint. The resolver lives in `lib/decisions/precedence.mjs`; `construct decisions check` fails if a rule declares a tier outside this list.
18
+
19
+ This sets the resolution order. It does not detect whether two rules contradict — that judgment stays with the author and reviewer.