@geraldmaron/construct 1.0.5 → 1.0.7

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 (268) hide show
  1. package/README.md +24 -18
  2. package/agents/contracts.json +617 -2
  3. package/agents/contracts.schema.json +83 -0
  4. package/agents/prompts/cx-accessibility.md +5 -3
  5. package/agents/prompts/cx-ai-engineer.md +9 -7
  6. package/agents/prompts/cx-architect.md +8 -6
  7. package/agents/prompts/cx-business-strategist.md +8 -6
  8. package/agents/prompts/cx-data-analyst.md +9 -7
  9. package/agents/prompts/cx-data-engineer.md +5 -3
  10. package/agents/prompts/cx-debugger.md +8 -6
  11. package/agents/prompts/cx-designer.md +7 -5
  12. package/agents/prompts/cx-devil-advocate.md +6 -4
  13. package/agents/prompts/cx-docs-keeper.md +7 -5
  14. package/agents/prompts/cx-engineer.md +7 -5
  15. package/agents/prompts/cx-evaluator.md +4 -2
  16. package/agents/prompts/cx-explorer.md +10 -8
  17. package/agents/prompts/cx-legal-compliance.md +4 -2
  18. package/agents/prompts/cx-operations.md +7 -5
  19. package/agents/prompts/cx-orchestrator.md +12 -10
  20. package/agents/prompts/cx-platform-engineer.md +7 -5
  21. package/agents/prompts/cx-product-manager.md +6 -4
  22. package/agents/prompts/cx-qa.md +10 -8
  23. package/agents/prompts/cx-rd-lead.md +11 -9
  24. package/agents/prompts/cx-release-manager.md +7 -5
  25. package/agents/prompts/cx-researcher.md +24 -22
  26. package/agents/prompts/cx-reviewer.md +9 -7
  27. package/agents/prompts/cx-security.md +12 -10
  28. package/agents/prompts/cx-sre.md +9 -7
  29. package/agents/prompts/cx-test-automation.md +5 -3
  30. package/agents/prompts/cx-trace-reviewer.md +10 -8
  31. package/agents/prompts/cx-ux-researcher.md +5 -3
  32. package/agents/registry.json +8 -3
  33. package/bin/construct +444 -53
  34. package/commands/build/feature.md +4 -4
  35. package/commands/build/fix.md +8 -8
  36. package/commands/design/access.md +3 -3
  37. package/commands/design/flow.md +3 -3
  38. package/commands/design/ui.md +4 -4
  39. package/commands/measure/experiment.md +5 -5
  40. package/commands/measure/metrics.md +3 -3
  41. package/commands/measure/results.md +4 -4
  42. package/commands/plan/api.md +3 -3
  43. package/commands/plan/challenge.md +3 -3
  44. package/commands/plan/decide.md +3 -3
  45. package/commands/plan/feature.md +7 -7
  46. package/commands/plan/requirements.md +3 -3
  47. package/commands/remember/context.md +5 -5
  48. package/commands/remember/handoff.md +3 -3
  49. package/commands/remember/runbook.md +3 -3
  50. package/commands/review/code.md +8 -8
  51. package/commands/review/quality.md +4 -4
  52. package/commands/review/security.md +3 -3
  53. package/commands/ship/ready.md +3 -3
  54. package/commands/ship/release.md +3 -3
  55. package/commands/ship/status.md +4 -4
  56. package/commands/understand/docs.md +3 -3
  57. package/commands/understand/this.md +4 -4
  58. package/commands/understand/why.md +8 -8
  59. package/commands/work/clean.md +14 -14
  60. package/commands/work/drive.md +10 -10
  61. package/commands/work/optimize-prompts.md +9 -9
  62. package/commands/work/parallel-review.md +8 -8
  63. package/db/schema/006_graph.sql +24 -0
  64. package/examples/provider-plugin/README.md +7 -7
  65. package/examples/seed-observations/README.md +6 -6
  66. package/examples/seed-observations/anti-patterns.md +14 -14
  67. package/examples/seed-observations/decisions.md +4 -4
  68. package/examples/seed-observations/patterns.md +14 -14
  69. package/lib/auto-docs.mjs +13 -8
  70. package/lib/boundary.mjs +126 -0
  71. package/lib/cache-strategy-google.js +26 -31
  72. package/lib/cli-commands.mjs +2 -2
  73. package/lib/comment-lint.mjs +134 -0
  74. package/lib/contracts/validate.mjs +323 -0
  75. package/lib/daemons/contract.mjs +210 -0
  76. package/lib/docs-verify.mjs +59 -6
  77. package/lib/doctor/cli.mjs +16 -1
  78. package/lib/doctor/index.mjs +3 -1
  79. package/lib/doctor/watchers/consistency.mjs +310 -0
  80. package/lib/doctor/watchers/mcp-protocol.mjs +232 -0
  81. package/lib/document-extract.mjs +211 -1
  82. package/lib/embed/cli.mjs +124 -3
  83. package/lib/embed/daemon.mjs +43 -4
  84. package/lib/embed/docs-lifecycle.mjs +1 -1
  85. package/lib/embed/inbox.mjs +2 -0
  86. package/lib/embed/scheduler.mjs +33 -5
  87. package/lib/evaluator-optimizer.mjs +2 -3
  88. package/lib/flavors/loader.mjs +1 -1
  89. package/lib/hooks/comment-lint.mjs +16 -0
  90. package/lib/hooks/mcp-audit.mjs +2 -1
  91. package/lib/hooks/proactive-activation.mjs +0 -14
  92. package/lib/hooks/rule-verifier.mjs +217 -0
  93. package/lib/hooks/session-optimize.mjs +2 -1
  94. package/lib/init-unified.mjs +55 -65
  95. package/lib/intake/classify.mjs +108 -24
  96. package/lib/intake/daemon.mjs +121 -0
  97. package/lib/intake/filesystem-queue.mjs +6 -1
  98. package/lib/intake/intake-config.mjs +2 -1
  99. package/lib/intake/prepare.mjs +0 -1
  100. package/lib/intake/session-prelude.mjs +7 -1
  101. package/lib/intake/traceability.mjs +90 -0
  102. package/lib/knowledge/graph.mjs +213 -0
  103. package/lib/knowledge/research-store.mjs +2 -0
  104. package/lib/maintenance/cleanup.mjs +315 -0
  105. package/lib/mcp/memory-bridge.mjs +276 -0
  106. package/lib/mcp/server.mjs +189 -1
  107. package/lib/mcp/tools/profile.mjs +270 -0
  108. package/lib/mcp/tools/workflow.mjs +25 -0
  109. package/lib/mcp-catalog.json +12 -8
  110. package/lib/mcp-platform-config.mjs +16 -8
  111. package/lib/migrations/index.mjs +106 -0
  112. package/lib/migrations/v1-baseline.mjs +33 -0
  113. package/lib/observation-store.mjs +9 -4
  114. package/lib/outcomes/record.mjs +2 -0
  115. package/lib/profiles/rebrand.mjs +46 -0
  116. package/lib/project-init-shared.mjs +12 -0
  117. package/lib/provider-capabilities.js +20 -7
  118. package/lib/providers/auth-manager.mjs +58 -17
  119. package/lib/reflect.mjs +49 -12
  120. package/lib/server/index.mjs +22 -28
  121. package/lib/session-store.mjs +6 -4
  122. package/lib/setup.mjs +14 -3
  123. package/lib/telemetry/client.mjs +5 -1
  124. package/lib/version.mjs +51 -0
  125. package/lib/worker/trace.mjs +5 -1
  126. package/package.json +4 -1
  127. package/personas/construct.md +3 -1
  128. package/platforms/claude/CLAUDE.md +6 -6
  129. package/rules/common/agents.md +2 -2
  130. package/rules/common/beads-hygiene.md +11 -11
  131. package/rules/common/code-review.md +1 -1
  132. package/rules/common/coding-style.md +1 -1
  133. package/rules/common/comments.md +8 -8
  134. package/rules/common/commit-approval.md +4 -4
  135. package/rules/common/cx-agent-routing.md +2 -2
  136. package/rules/common/cx-skill-routing.md +2 -2
  137. package/rules/common/development-workflow.md +3 -2
  138. package/rules/common/doc-ownership.md +2 -2
  139. package/rules/common/efficiency.md +3 -3
  140. package/rules/common/framing.md +1 -1
  141. package/rules/common/git-workflow.md +1 -1
  142. package/rules/common/no-fabrication.md +69 -0
  143. package/rules/common/patterns.md +1 -1
  144. package/rules/common/performance.md +1 -1
  145. package/rules/common/release-gates.md +7 -7
  146. package/rules/common/research.md +4 -4
  147. package/rules/common/review-before-change.md +58 -0
  148. package/rules/common/security.md +1 -1
  149. package/rules/common/skill-composition.md +8 -8
  150. package/rules/common/testing.md +1 -1
  151. package/rules/golang/coding-style.md +2 -2
  152. package/rules/golang/hooks.md +1 -1
  153. package/rules/golang/patterns.md +1 -1
  154. package/rules/golang/security.md +1 -1
  155. package/rules/golang/testing.md +1 -1
  156. package/rules/python/coding-style.md +1 -1
  157. package/rules/python/hooks.md +1 -1
  158. package/rules/python/patterns.md +1 -1
  159. package/rules/python/security.md +1 -1
  160. package/rules/python/testing.md +1 -1
  161. package/rules/swift/coding-style.md +3 -3
  162. package/rules/swift/hooks.md +2 -2
  163. package/rules/swift/patterns.md +2 -2
  164. package/rules/swift/security.md +4 -4
  165. package/rules/swift/testing.md +2 -2
  166. package/rules/typescript/coding-style.md +1 -1
  167. package/rules/typescript/hooks.md +1 -1
  168. package/rules/typescript/patterns.md +1 -1
  169. package/rules/typescript/security.md +1 -1
  170. package/rules/typescript/testing.md +1 -1
  171. package/rules/web/coding-style.md +1 -1
  172. package/rules/web/design-quality.md +1 -1
  173. package/rules/web/hooks.md +1 -1
  174. package/rules/web/patterns.md +1 -1
  175. package/rules/web/performance.md +1 -1
  176. package/rules/web/security.md +1 -1
  177. package/rules/web/testing.md +1 -1
  178. package/scripts/sync-agents.mjs +45 -14
  179. package/skills/ai/agent-dev.md +1 -1
  180. package/skills/ai/llm-security.md +1 -1
  181. package/skills/ai/ml-ops.md +6 -6
  182. package/skills/ai/orchestration-workflow.md +1 -1
  183. package/skills/ai/prompt-and-eval.md +1 -1
  184. package/skills/ai/prompt-optimizer.md +13 -13
  185. package/skills/ai/rag-system.md +1 -1
  186. package/skills/architecture/api-design.md +1 -1
  187. package/skills/architecture/caching.md +1 -1
  188. package/skills/architecture/cloud-native.md +1 -1
  189. package/skills/architecture/message-queue.md +1 -1
  190. package/skills/architecture/security-arch.md +1 -1
  191. package/skills/compliance/ai-disclosure.md +1 -1
  192. package/skills/compliance/data-privacy.md +1 -1
  193. package/skills/compliance/license-audit.md +2 -2
  194. package/skills/compliance/regulatory-review.md +1 -1
  195. package/skills/development/cpp.md +1 -1
  196. package/skills/development/go.md +1 -1
  197. package/skills/development/java.md +1 -1
  198. package/skills/development/kotlin.md +9 -9
  199. package/skills/development/mobile-crossplatform.md +13 -13
  200. package/skills/development/python.md +1 -1
  201. package/skills/development/rust.md +1 -1
  202. package/skills/development/shell.md +1 -1
  203. package/skills/development/swift.md +6 -6
  204. package/skills/development/typescript.md +1 -1
  205. package/skills/devops/ci-cd.md +5 -5
  206. package/skills/devops/containerization.md +9 -9
  207. package/skills/devops/cost-optimization.md +1 -1
  208. package/skills/devops/data-engineering.md +2 -2
  209. package/skills/devops/database.md +1 -1
  210. package/skills/devops/dependency-management.md +3 -3
  211. package/skills/devops/devsecops.md +1 -1
  212. package/skills/devops/git-workflow.md +1 -1
  213. package/skills/devops/incident-response.md +18 -18
  214. package/skills/devops/monorepo.md +5 -5
  215. package/skills/devops/observability.md +1 -1
  216. package/skills/devops/performance.md +1 -1
  217. package/skills/devops/testing.md +1 -1
  218. package/skills/docs/adr-workflow.md +2 -2
  219. package/skills/docs/backlog-proposal-workflow.md +1 -1
  220. package/skills/docs/customer-profile-workflow.md +1 -1
  221. package/skills/docs/document-ingest-workflow.md +1 -1
  222. package/skills/docs/evidence-ingest-workflow.md +1 -1
  223. package/skills/docs/init-docs.md +15 -15
  224. package/skills/docs/init-project.md +1 -1
  225. package/skills/docs/prd-workflow.md +3 -3
  226. package/skills/docs/prfaq-workflow.md +1 -1
  227. package/skills/docs/product-intelligence-review.md +1 -1
  228. package/skills/docs/product-intelligence-workflow.md +1 -1
  229. package/skills/docs/product-signal-workflow.md +9 -9
  230. package/skills/docs/research-workflow.md +10 -10
  231. package/skills/docs/runbook-workflow.md +2 -2
  232. package/skills/docs/strategy-workflow.md +3 -3
  233. package/skills/exploration/repo-map.md +11 -11
  234. package/skills/frameworks/django.md +15 -15
  235. package/skills/frameworks/nextjs.md +16 -16
  236. package/skills/frameworks/react.md +12 -12
  237. package/skills/frameworks/spring-boot.md +12 -12
  238. package/skills/frontend-design/accessibility.md +6 -6
  239. package/skills/frontend-design/component-patterns.md +1 -1
  240. package/skills/frontend-design/engineering.md +1 -1
  241. package/skills/frontend-design/state-management.md +1 -1
  242. package/skills/frontend-design/ui-aesthetics.md +1 -1
  243. package/skills/frontend-design/ux-principles.md +1 -1
  244. package/skills/operating/orchestration-reference.md +27 -27
  245. package/skills/quality-gates/review-work.md +3 -3
  246. package/skills/quality-gates/verify-change.md +1 -1
  247. package/skills/quality-gates/verify-module.md +1 -1
  248. package/skills/quality-gates/verify-quality.md +1 -1
  249. package/skills/quality-gates/verify-security.md +1 -1
  250. package/skills/routing.md +14 -14
  251. package/skills/security/blue-team.md +1 -1
  252. package/skills/security/code-audit.md +1 -1
  253. package/skills/security/pentest.md +1 -1
  254. package/skills/security/red-team.md +1 -1
  255. package/skills/security/threat-intel.md +1 -1
  256. package/skills/security/vuln-research.md +1 -1
  257. package/skills/utility/clean-code.md +2 -2
  258. package/templates/docs/changelog-entry.md +1 -1
  259. package/templates/docs/construct_guide.md +18 -18
  260. package/templates/docs/meta-prd.md +16 -16
  261. package/templates/docs/one-pager.md +1 -1
  262. package/templates/docs/prd-business.md +1 -1
  263. package/templates/docs/prd-platform.md +1 -1
  264. package/templates/docs/prd.md +17 -17
  265. package/templates/docs/research-brief.md +8 -8
  266. package/templates/docs/rfc.md +1 -1
  267. package/templates/docs/skill-artifact.md +1 -1
  268. package/templates/docs/strategy.md +1 -1
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lib/mcp/memory-bridge.mjs — stdio MCP bridge for cass-memory (cm).
4
+ *
5
+ * Editor MCP clients (OpenCode, Claude Code) expect a host to satisfy the MCP
6
+ * HTTP+SSE handshake — GET with `Accept: text/event-stream`, POST `initialize`,
7
+ * `notifications/initialized`, `ping`. cass-memory v0.2.x ships an HTTP server
8
+ * that answers `tools/list`, `tools/call`, `resources/list`, `resources/read`
9
+ * over POST but rejects the handshake calls (405 on GET, "Unsupported method"
10
+ * on POST initialize/ping). That single gap surfaces in editors as
11
+ * "memory SSE error: Non-200 status code (405)" and the memory tools never
12
+ * register.
13
+ *
14
+ * Sits between editor and cm. Speaks the canonical 2026 MCP stdio transport
15
+ * (newline-delimited JSON-RPC on stdin/stdout, logs on stderr), answers the
16
+ * handshake natively, and forwards real work (tools/resources) to cm over its
17
+ * existing HTTP API. Stays up across cm restarts; surfaces backend outages as
18
+ * JSON-RPC `-32603` errors rather than crashing, so the editor can retry
19
+ * without losing the channel.
20
+ *
21
+ * Env:
22
+ * CONSTRUCT_MEMORY_BRIDGE_URL cm base URL (default http://127.0.0.1:8765/)
23
+ * CONSTRUCT_MEMORY_BRIDGE_AUTOSTART set to 1 to spawn `cm serve` if unreachable
24
+ * CONSTRUCT_MEMORY_BRIDGE_TIMEOUT_MS per-request timeout (default 5000)
25
+ *
26
+ * Stdout is reserved for protocol frames. All diagnostic output goes to stderr.
27
+ */
28
+
29
+ import { createInterface } from 'node:readline';
30
+ import { spawn } from 'node:child_process';
31
+ import { readFileSync } from 'node:fs';
32
+ import { dirname, join } from 'node:path';
33
+ import { fileURLToPath } from 'node:url';
34
+
35
+ const PROTOCOL_VERSION = '2024-11-05';
36
+ const SERVER_NAME = 'construct-memory-bridge';
37
+
38
+ const __filename = fileURLToPath(import.meta.url);
39
+ const __dirname = dirname(__filename);
40
+
41
+ function readPackageVersion() {
42
+ try {
43
+ const pkgPath = join(__dirname, '..', '..', 'package.json');
44
+ return JSON.parse(readFileSync(pkgPath, 'utf8')).version || '0.0.0';
45
+ } catch {
46
+ return '0.0.0';
47
+ }
48
+ }
49
+
50
+ const SERVER_VERSION = readPackageVersion();
51
+
52
+ function normalizeBackendUrl(raw) {
53
+ const value = (raw && String(raw).trim()) || 'http://127.0.0.1:8765/';
54
+ return value.endsWith('/') ? value : `${value}/`;
55
+ }
56
+
57
+ const BACKEND_URL = normalizeBackendUrl(process.env.CONSTRUCT_MEMORY_BRIDGE_URL);
58
+ const REQUEST_TIMEOUT_MS = (() => {
59
+ const raw = Number(process.env.CONSTRUCT_MEMORY_BRIDGE_TIMEOUT_MS);
60
+ return Number.isFinite(raw) && raw > 0 ? raw : 5000;
61
+ })();
62
+ const AUTOSTART = process.env.CONSTRUCT_MEMORY_BRIDGE_AUTOSTART === '1';
63
+
64
+ const FORWARDED_METHODS = new Set([
65
+ 'tools/list',
66
+ 'tools/call',
67
+ 'resources/list',
68
+ 'resources/read',
69
+ 'prompts/list',
70
+ 'prompts/get',
71
+ ]);
72
+
73
+ function log(...parts) {
74
+ process.stderr.write(`[memory-bridge] ${parts.join(' ')}\n`);
75
+ }
76
+
77
+ function writeFrame(frame) {
78
+ process.stdout.write(`${JSON.stringify(frame)}\n`);
79
+ }
80
+
81
+ function jsonRpcResult(id, result) {
82
+ return { jsonrpc: '2.0', id, result };
83
+ }
84
+
85
+ function jsonRpcError(id, code, message, data) {
86
+ const err = { code, message };
87
+ if (data !== undefined) err.data = data;
88
+ return { jsonrpc: '2.0', id, error: err };
89
+ }
90
+
91
+ // cm forwarding uses fetch with an AbortController so a stuck backend never
92
+ // strands the editor. Failed call returns a JSON-RPC -32603 error frame, not a
93
+ // thrown exception, so the bridge process stays available for retries.
94
+
95
+ async function forwardToBackend(method, params, id) {
96
+ const controller = new AbortController();
97
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
98
+ try {
99
+ const body = { jsonrpc: '2.0', id: id ?? 0, method, params: params ?? {} };
100
+ const res = await fetch(BACKEND_URL, {
101
+ method: 'POST',
102
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
103
+ body: JSON.stringify(body),
104
+ signal: controller.signal,
105
+ });
106
+ if (!res.ok) {
107
+ return jsonRpcError(
108
+ id,
109
+ -32603,
110
+ `memory backend returned HTTP ${res.status} for ${method}`,
111
+ { backend: BACKEND_URL, status: res.status },
112
+ );
113
+ }
114
+ const text = await res.text();
115
+ let parsed;
116
+ try {
117
+ parsed = JSON.parse(text);
118
+ } catch {
119
+ return jsonRpcError(id, -32603, `memory backend returned non-JSON for ${method}`, { backend: BACKEND_URL });
120
+ }
121
+ if (parsed && Object.prototype.hasOwnProperty.call(parsed, 'error')) {
122
+ return { jsonrpc: '2.0', id, error: parsed.error };
123
+ }
124
+ return jsonRpcResult(id, parsed && parsed.result !== undefined ? parsed.result : parsed);
125
+ } catch (err) {
126
+ const reason = err?.name === 'AbortError' ? 'timeout' : (err?.message || String(err));
127
+ return jsonRpcError(id, -32603, `memory backend unreachable at ${BACKEND_URL} (${reason})`, { backend: BACKEND_URL });
128
+ } finally {
129
+ clearTimeout(timer);
130
+ }
131
+ }
132
+
133
+ // Autostart only fires when CONSTRUCT_MEMORY_BRIDGE_AUTOSTART=1. Detached
134
+ // spawn keeps cm alive after the editor disconnects.
135
+
136
+ let autostartAttempted = false;
137
+
138
+ function maybeAutostartCm() {
139
+ if (!AUTOSTART || autostartAttempted) return;
140
+ autostartAttempted = true;
141
+ try {
142
+ const port = (() => {
143
+ try {
144
+ const u = new URL(BACKEND_URL);
145
+ return u.port || '8765';
146
+ } catch {
147
+ return '8765';
148
+ }
149
+ })();
150
+ const child = spawn('cm', ['serve', '--port', String(port)], {
151
+ detached: true,
152
+ stdio: 'ignore',
153
+ });
154
+ child.unref();
155
+ log(`autostart: spawned cm serve --port ${port}`);
156
+ } catch (err) {
157
+ log(`autostart failed: ${err?.message || err}`);
158
+ }
159
+ }
160
+
161
+ // Handshake methods resolve natively. cm cannot answer them; forwarding would
162
+ // return the same "Unsupported method" surface that triggered this bug.
163
+
164
+ function handleInitialize(id) {
165
+ return jsonRpcResult(id, {
166
+ protocolVersion: PROTOCOL_VERSION,
167
+ capabilities: { tools: {}, resources: {} },
168
+ serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
169
+ });
170
+ }
171
+
172
+ function handlePing(id) {
173
+ return jsonRpcResult(id, {});
174
+ }
175
+
176
+ async function handleShutdown(id) {
177
+ try { process.stderr.write(''); } catch { /* stderr already closed */ }
178
+ if (id !== undefined && id !== null) writeFrame(jsonRpcResult(id, null));
179
+ setImmediate(() => process.exit(0));
180
+ }
181
+
182
+ async function dispatch(message) {
183
+ if (!message || typeof message !== 'object') return null;
184
+ if (message.jsonrpc !== '2.0') return null;
185
+
186
+ const { id, method, params } = message;
187
+ const isNotification = id === undefined || id === null;
188
+
189
+ if (method === 'notifications/initialized' || method === 'notifications/cancelled') {
190
+ return null;
191
+ }
192
+ if (method === 'initialize') return handleInitialize(id);
193
+ if (method === 'ping') return handlePing(id);
194
+ if (method === 'shutdown') return handleShutdown(id);
195
+
196
+ if (FORWARDED_METHODS.has(method)) {
197
+ maybeAutostartCm();
198
+ return forwardToBackend(method, params, id);
199
+ }
200
+
201
+ if (isNotification) return null;
202
+ return jsonRpcError(id, -32601, `Method not found: ${method}`);
203
+ }
204
+
205
+ async function handleLine(line) {
206
+ const trimmed = line.trim();
207
+ if (!trimmed) return;
208
+ let message;
209
+ try {
210
+ message = JSON.parse(trimmed);
211
+ } catch (err) {
212
+ writeFrame(jsonRpcError(null, -32700, `Parse error: ${err?.message || err}`));
213
+ return;
214
+ }
215
+ try {
216
+ const response = await dispatch(message);
217
+ if (response) writeFrame(response);
218
+ } catch (err) {
219
+ if (message && message.id !== undefined && message.id !== null) {
220
+ writeFrame(jsonRpcError(message.id, -32603, `Internal bridge error: ${err?.message || err}`));
221
+ } else {
222
+ log(`unhandled error on notification: ${err?.message || err}`);
223
+ }
224
+ }
225
+ }
226
+
227
+ export function startBridge({ input = process.stdin, output = process.stdout } = {}) {
228
+ if (output !== process.stdout) {
229
+ const writer = (frame) => output.write(`${JSON.stringify(frame)}\n`);
230
+ return runLoop(input, writer);
231
+ }
232
+ return runLoop(input, (frame) => process.stdout.write(`${JSON.stringify(frame)}\n`));
233
+ }
234
+
235
+ function runLoop(input, writer) {
236
+ const rl = createInterface({ input, crlfDelay: Infinity });
237
+ rl.on('line', async (line) => {
238
+ const trimmed = line.trim();
239
+ if (!trimmed) return;
240
+ let message;
241
+ try {
242
+ message = JSON.parse(trimmed);
243
+ } catch (err) {
244
+ writer(jsonRpcError(null, -32700, `Parse error: ${err?.message || err}`));
245
+ return;
246
+ }
247
+ try {
248
+ const response = await dispatch(message);
249
+ if (response) writer(response);
250
+ } catch (err) {
251
+ if (message && message.id !== undefined && message.id !== null) {
252
+ writer(jsonRpcError(message.id, -32603, `Internal bridge error: ${err?.message || err}`));
253
+ } else {
254
+ log(`unhandled error on notification: ${err?.message || err}`);
255
+ }
256
+ }
257
+ });
258
+ rl.on('close', () => process.exit(0));
259
+ return rl;
260
+ }
261
+
262
+ const isMain = (() => {
263
+ try { return import.meta.url === `file://${process.argv[1]}`; } catch { return false; }
264
+ })();
265
+
266
+ if (isMain) {
267
+ log(`starting (backend=${BACKEND_URL}, autostart=${AUTOSTART ? 'on' : 'off'})`);
268
+ startBridge();
269
+ }
270
+
271
+ export const _testing = {
272
+ dispatch,
273
+ handleLine,
274
+ BACKEND_URL,
275
+ SERVER_VERSION,
276
+ };
@@ -3,7 +3,7 @@
3
3
  * lib/mcp/server.mjs — Construct MCP server: tool registry and request dispatcher.
4
4
  *
5
5
  * Thin dispatcher only — all tool implementations live in lib/mcp/tools/*.mjs.
6
- * Registers 40 tools across 7 modules: project, document, storage, skills, workflow, telemetry, memory.
6
+ * Registers 52 tools across 8 modules: project, document, storage, skills, workflow, telemetry, memory, profile.
7
7
  * Consumed by Claude Code, OpenCode, and any MCP-compatible host.
8
8
  */
9
9
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -40,6 +40,7 @@ import {
40
40
  import {
41
41
  workflowInit, workflowAddTask, workflowUpdateTask,
42
42
  workflowNeedsMainInput, workflowValidate, workflowImportPlan,
43
+ workflowContractValidate,
43
44
  } from './tools/workflow.mjs';
44
45
  import {
45
46
  cxTrace, cxTraceUpdate, cxScore, sessionUsage, efficiencySnapshot,
@@ -48,6 +49,12 @@ import {
48
49
  memorySearch, memoryAddObservations, memoryCreateEntities, memoryRecent,
49
50
  sessionList, sessionLoad, sessionSearch, sessionSave, rovoSearch,
50
51
  } from './tools/memory.mjs';
52
+ import {
53
+ profileShow, profileList, profileDrafts, profileHealthTool,
54
+ outcomesSummary, outcomesRecord, knowledgeAdd,
55
+ profileCreate, profileArchive, sandboxList, learningStatus,
56
+ knowledgeGraphAsk,
57
+ } from './tools/profile.mjs';
51
58
 
52
59
  const DEFAULT_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
53
60
  const ROOT_DIR = resolve(process.env.CX_TOOLKIT_DIR || DEFAULT_ROOT_DIR);
@@ -711,6 +718,174 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
711
718
  },
712
719
  },
713
720
  },
721
+ {
722
+ name: 'profile_show',
723
+ description: 'Return the active Construct org profile (id, displayName, roles, departments, intake taxonomy, doc templates). Use when a specialist needs to know which role set, classification taxonomy, or doc templates apply before drafting work.',
724
+ inputSchema: {
725
+ type: 'object',
726
+ properties: {
727
+ cwd: { type: 'string', description: 'Project root (default: server cwd).' },
728
+ id: { type: 'string', description: 'Force a specific profile id instead of resolving from config.' },
729
+ },
730
+ },
731
+ },
732
+ {
733
+ name: 'profile_list',
734
+ description: 'List the curated org profile catalog (rnd, operations, creative, research) with role/department counts. Use to discover which profiles are available before suggesting `construct profile set`.',
735
+ inputSchema: { type: 'object', properties: {} },
736
+ },
737
+ {
738
+ name: 'profile_drafts',
739
+ description: 'List in-progress draft profiles under `.cx/profiles/draft-*` and any user-defined custom profile at `.cx/profile.json`. Use to see what profile work is pending before scaffolding another draft.',
740
+ inputSchema: {
741
+ type: 'object',
742
+ properties: { cwd: { type: 'string', description: 'Project root (default: server cwd).' } },
743
+ },
744
+ },
745
+ {
746
+ name: 'profile_health',
747
+ description: 'Per-profile health rollup over a window: observation count, per-role outcome runs and success rates. Use to check whether a profile is producing data before recommending changes or archive.',
748
+ inputSchema: {
749
+ type: 'object',
750
+ properties: {
751
+ cwd: { type: 'string', description: 'Project root (default: server cwd).' },
752
+ id: { type: 'string', description: 'Profile id (default: active profile).' },
753
+ window_days: { type: 'number', description: 'Window in days (default 30).' },
754
+ },
755
+ },
756
+ },
757
+ {
758
+ name: 'outcomes_summary',
759
+ description: 'Read `.cx/outcomes/_summary.json` (per-role success rate, 30-day trend). Pass `aggregate=true` to rebuild the summary from JSONL outcome files first. Use to ground tiebreakers and improvement suggestions in real specialist performance.',
760
+ inputSchema: {
761
+ type: 'object',
762
+ properties: {
763
+ cwd: { type: 'string', description: 'Project root (default: server cwd).' },
764
+ aggregate: { type: 'boolean', description: 'Rebuild `_summary.json` before reading (default false).' },
765
+ },
766
+ },
767
+ },
768
+ {
769
+ name: 'outcomes_record',
770
+ description: 'Append a specialist outcome line to `.cx/outcomes/<role>.jsonl` (writes durable state — requires `confirm=true`). Use when a specialist wants to self-report success/failure outside the automatic agent-tracker path.',
771
+ inputSchema: {
772
+ type: 'object',
773
+ required: ['confirm', 'role', 'success'],
774
+ properties: {
775
+ confirm: { type: 'boolean', description: 'Must be true.' },
776
+ cwd: { type: 'string' },
777
+ role: { type: 'string', description: 'Specialist id (e.g. cx-engineer, product-manager).' },
778
+ success: { type: 'boolean' },
779
+ intake_id: { type: 'string' },
780
+ profile: { type: 'string', description: 'Override active profile id stamp.' },
781
+ escalated: { type: 'boolean' },
782
+ duration_ms: { type: 'number' },
783
+ notes: { type: 'string', description: 'Trimmed to 500 chars.' },
784
+ source: { type: 'string', description: 'Origin tag (default: "mcp").' },
785
+ },
786
+ },
787
+ },
788
+ {
789
+ name: 'knowledge_add',
790
+ description: 'Persist a research finding as `.cx/knowledge/external/research/<slug>.md` with research-specific frontmatter (topic, confidence, sources, expiresAt, profile). Writes durable state — requires `confirm=true`. `confidence=confirmed` requires at least one source.',
791
+ inputSchema: {
792
+ type: 'object',
793
+ required: ['confirm', 'slug', 'topic', 'body'],
794
+ properties: {
795
+ confirm: { type: 'boolean', description: 'Must be true.' },
796
+ cwd: { type: 'string' },
797
+ slug: { type: 'string', description: 'Lowercase hyphenated, max 60 chars.' },
798
+ topic: { type: 'string' },
799
+ body: { type: 'string', description: 'Findings / inferences / gaps / recommendation block. Capped at 50KB total file.' },
800
+ confidence: { type: 'string', enum: ['confirmed', 'inferred', 'weak'], description: 'Default: inferred.' },
801
+ sources: {
802
+ type: 'array',
803
+ description: 'Required when confidence=confirmed.',
804
+ items: {
805
+ type: 'object',
806
+ required: ['url'],
807
+ properties: {
808
+ url: { type: 'string' },
809
+ accessedAt: { type: 'string', description: 'ISO timestamp; default now.' },
810
+ span: { type: 'string', description: 'Citation locator; trimmed to 200 chars.' },
811
+ },
812
+ },
813
+ },
814
+ ttl_days: { type: 'number', description: 'Default 90.' },
815
+ },
816
+ },
817
+ },
818
+ {
819
+ name: 'profile_create',
820
+ description: 'Scaffold a draft org profile under `.cx/profiles/draft-<id>/` (requirements.md + profile.json + persona stubs + department charters). Writes durable state — requires `confirm=true`. For curated catalog work, follow `docs/concepts/profile-lifecycle.md` after creation.',
821
+ inputSchema: {
822
+ type: 'object',
823
+ required: ['confirm', 'id'],
824
+ properties: {
825
+ confirm: { type: 'boolean', description: 'Must be true.' },
826
+ cwd: { type: 'string' },
827
+ id: { type: 'string', description: 'Profile id (^[a-z][a-z0-9-]{1,30}$).' },
828
+ display_name: { type: 'string' },
829
+ seed_roles: {
830
+ type: 'array',
831
+ items: { type: 'string' },
832
+ description: 'Role ids to scaffold persona files for (cap 80).',
833
+ },
834
+ seed_departments: {
835
+ type: 'array',
836
+ items: {
837
+ type: 'object',
838
+ required: ['id'],
839
+ properties: {
840
+ id: { type: 'string' },
841
+ displayName: { type: 'string' },
842
+ },
843
+ },
844
+ description: 'Departments to scaffold charters for (cap 12).',
845
+ },
846
+ },
847
+ },
848
+ },
849
+ {
850
+ name: 'profile_archive',
851
+ description: 'Archive a curated profile: moves `profiles/<id>.json` and its intake table into `archive/profiles/<id>/` with an archive note. Destructive — requires `confirm=true` and a substantive `reason` (>=8 chars). Observations and outcomes are preserved.',
852
+ inputSchema: {
853
+ type: 'object',
854
+ required: ['confirm', 'id', 'reason'],
855
+ properties: {
856
+ confirm: { type: 'boolean', description: 'Must be true.' },
857
+ id: { type: 'string' },
858
+ reason: { type: 'string', description: 'Substantive reason (>= 8 chars).' },
859
+ },
860
+ },
861
+ },
862
+ {
863
+ name: 'sandbox_list',
864
+ description: 'List Construct sandboxes under `~/.cx/sandboxes/` (id, path, createdAt). Use to find an isolated environment for QA or dry-runs without polluting the active project.',
865
+ inputSchema: { type: 'object', properties: {} },
866
+ },
867
+ {
868
+ name: 'knowledge_graph_ask',
869
+ description: 'GraphRAG-style global query over the entity graph in `.cx/observations/`. Detects communities via label propagation, ranks them by BM25 against the query, and returns each top community with its central members and extractive summary. Use for "tell me about how X relates across the project" questions that pure semantic retrieval handles poorly.',
870
+ inputSchema: {
871
+ type: 'object',
872
+ required: ['query'],
873
+ properties: {
874
+ query: { type: 'string', description: 'Natural-language question.' },
875
+ cwd: { type: 'string', description: 'Project root (default: server cwd).' },
876
+ top_k: { type: 'number', description: 'Max communities to return (default 5).' },
877
+ min_size: { type: 'number', description: 'Skip communities smaller than this (default 2).' },
878
+ },
879
+ },
880
+ },
881
+ {
882
+ name: 'learning_status',
883
+ description: 'One-shot mirror of `npm run learning:status`: active profile, observation counts (last 24h + total), research finding count, per-role outcome rollup. Use to answer "is Construct learning?" without spawning a shell.',
884
+ inputSchema: {
885
+ type: 'object',
886
+ properties: { cwd: { type: 'string', description: 'Project root (default: server cwd).' } },
887
+ },
888
+ },
714
889
  {
715
890
  name: 'knowledge_search',
716
891
  description: 'Search Construct\'s own documentation, knowledge base, and distilled embed observations. Call this immediately — no approval needed — when the user asks what Construct is, how a feature works, what commands exist, or anything about Construct\'s architecture or configuration. Also searches embed observations from GitHub, Jira, and other configured sources. Returns relevant excerpts with source file and heading.',
@@ -760,6 +935,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
760
935
  else if (name === 'workflow_update_task') result = workflowUpdateTask(args);
761
936
  else if (name === 'workflow_needs_main_input') result = workflowNeedsMainInput(args);
762
937
  else if (name === 'workflow_validate') result = workflowValidate(args);
938
+ else if (name === 'workflow_contract_validate') result = await workflowContractValidate(args);
763
939
  else if (name === 'workflow_import_plan') result = workflowImportPlan(args);
764
940
  else if (name === 'list_teams') result = listTeams(opts);
765
941
  else if (name === 'get_team') result = getTeam(args, opts);
@@ -786,6 +962,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
786
962
  const { knowledgeSearch } = await import('../knowledge/search.mjs');
787
963
  result = knowledgeSearch({ query: args.query, topK: args.top_k, repoRoot: args.repo_root, rootDir: args.root_dir });
788
964
  }
965
+ else if (name === 'profile_show') result = profileShow(args);
966
+ else if (name === 'profile_list') result = profileList();
967
+ else if (name === 'profile_drafts') result = profileDrafts(args);
968
+ else if (name === 'profile_health') result = profileHealthTool(args);
969
+ else if (name === 'outcomes_summary') result = outcomesSummary(args);
970
+ else if (name === 'outcomes_record') result = outcomesRecord(args);
971
+ else if (name === 'knowledge_add') result = await knowledgeAdd(args);
972
+ else if (name === 'profile_create') result = profileCreate(args);
973
+ else if (name === 'profile_archive') result = profileArchive(args);
974
+ else if (name === 'sandbox_list') result = sandboxList();
975
+ else if (name === 'learning_status') result = learningStatus(args);
976
+ else if (name === 'knowledge_graph_ask') result = knowledgeGraphAsk(args);
789
977
  else result = { error: `Unknown tool: ${name}` };
790
978
  } catch (err) {
791
979
  result = { error: err.message ?? String(err) };