@neurcode-ai/cli 0.9.64 → 0.9.65

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 (252) hide show
  1. package/LICENSE +201 -0
  2. package/dist/commands/brain.d.ts.map +1 -1
  3. package/dist/commands/brain.js +273 -0
  4. package/dist/commands/brain.js.map +1 -1
  5. package/dist/commands/pilot-report.d.ts +9 -0
  6. package/dist/commands/pilot-report.d.ts.map +1 -0
  7. package/dist/commands/pilot-report.js +176 -0
  8. package/dist/commands/pilot-report.js.map +1 -0
  9. package/dist/commands/remediate-governance.d.ts +54 -0
  10. package/dist/commands/remediate-governance.d.ts.map +1 -0
  11. package/dist/commands/remediate-governance.js +375 -0
  12. package/dist/commands/remediate-governance.js.map +1 -0
  13. package/dist/commands/remediate.d.ts.map +1 -1
  14. package/dist/commands/remediate.js.map +1 -1
  15. package/dist/commands/replay.d.ts.map +1 -1
  16. package/dist/commands/replay.js +30 -0
  17. package/dist/commands/replay.js.map +1 -1
  18. package/dist/commands/verify.d.ts.map +1 -1
  19. package/dist/commands/verify.js +307 -24
  20. package/dist/commands/verify.js.map +1 -1
  21. package/dist/daemon/server.d.ts.map +1 -1
  22. package/dist/daemon/server.js +1078 -0
  23. package/dist/daemon/server.js.map +1 -1
  24. package/dist/explainability/DeterminismClassifier.d.ts +34 -0
  25. package/dist/explainability/DeterminismClassifier.d.ts.map +1 -0
  26. package/dist/explainability/DeterminismClassifier.js +104 -0
  27. package/dist/explainability/DeterminismClassifier.js.map +1 -0
  28. package/dist/explainability/ViolationFormatter.d.ts +32 -0
  29. package/dist/explainability/ViolationFormatter.d.ts.map +1 -0
  30. package/dist/explainability/ViolationFormatter.js +252 -0
  31. package/dist/explainability/ViolationFormatter.js.map +1 -0
  32. package/dist/explainability/index.d.ts +15 -0
  33. package/dist/explainability/index.d.ts.map +1 -0
  34. package/dist/explainability/index.js +94 -0
  35. package/dist/explainability/index.js.map +1 -0
  36. package/dist/explainability/types.d.ts +37 -0
  37. package/dist/explainability/types.d.ts.map +1 -0
  38. package/dist/explainability/types.js +3 -0
  39. package/dist/explainability/types.js.map +1 -0
  40. package/dist/governance/canonical-pipeline.d.ts +38 -0
  41. package/dist/governance/canonical-pipeline.d.ts.map +1 -0
  42. package/dist/governance/canonical-pipeline.js +448 -0
  43. package/dist/governance/canonical-pipeline.js.map +1 -0
  44. package/dist/governance/structural-on-diff.d.ts +13 -0
  45. package/dist/governance/structural-on-diff.d.ts.map +1 -0
  46. package/dist/governance/structural-on-diff.js +35 -0
  47. package/dist/governance/structural-on-diff.js.map +1 -0
  48. package/dist/governance/structural-policy-merge.d.ts +14 -0
  49. package/dist/governance/structural-policy-merge.d.ts.map +1 -0
  50. package/dist/governance/structural-policy-merge.js +25 -0
  51. package/dist/governance/structural-policy-merge.js.map +1 -0
  52. package/dist/index.js +71 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/integrations/review-compression/index.d.ts +50 -0
  55. package/dist/integrations/review-compression/index.d.ts.map +1 -0
  56. package/dist/integrations/review-compression/index.js +158 -0
  57. package/dist/integrations/review-compression/index.js.map +1 -0
  58. package/dist/intent-engine/domain-taxonomy.d.ts +42 -0
  59. package/dist/intent-engine/domain-taxonomy.d.ts.map +1 -0
  60. package/dist/intent-engine/domain-taxonomy.js +534 -0
  61. package/dist/intent-engine/domain-taxonomy.js.map +1 -0
  62. package/dist/intent-engine/index.d.ts +1 -0
  63. package/dist/intent-engine/index.d.ts.map +1 -1
  64. package/dist/intent-engine/index.js +6 -1
  65. package/dist/intent-engine/index.js.map +1 -1
  66. package/dist/intent-engine/parser.d.ts.map +1 -1
  67. package/dist/intent-engine/parser.js +47 -0
  68. package/dist/intent-engine/parser.js.map +1 -1
  69. package/dist/intent-engine/semantic-expander.d.ts +104 -0
  70. package/dist/intent-engine/semantic-expander.d.ts.map +1 -0
  71. package/dist/intent-engine/semantic-expander.js +480 -0
  72. package/dist/intent-engine/semantic-expander.js.map +1 -0
  73. package/dist/patch-engine/patterns.d.ts.map +1 -1
  74. package/dist/patch-engine/patterns.js +8 -4
  75. package/dist/patch-engine/patterns.js.map +1 -1
  76. package/dist/semantic/index.d.ts +14 -0
  77. package/dist/semantic/index.d.ts.map +1 -0
  78. package/dist/semantic/index.js +30 -0
  79. package/dist/semantic/index.js.map +1 -0
  80. package/dist/semantic/tfidf-engine.d.ts +81 -0
  81. package/dist/semantic/tfidf-engine.d.ts.map +1 -0
  82. package/dist/semantic/tfidf-engine.js +278 -0
  83. package/dist/semantic/tfidf-engine.js.map +1 -0
  84. package/dist/semantic/vector-store.d.ts +108 -0
  85. package/dist/semantic/vector-store.d.ts.map +1 -0
  86. package/dist/semantic/vector-store.js +321 -0
  87. package/dist/semantic/vector-store.js.map +1 -0
  88. package/dist/structural-rules/context-severity.d.ts +46 -0
  89. package/dist/structural-rules/context-severity.d.ts.map +1 -0
  90. package/dist/structural-rules/context-severity.js +115 -0
  91. package/dist/structural-rules/context-severity.js.map +1 -0
  92. package/dist/structural-rules/distributed/DS001-saga-rollback-absence.d.ts +11 -0
  93. package/dist/structural-rules/distributed/DS001-saga-rollback-absence.d.ts.map +1 -0
  94. package/dist/structural-rules/distributed/DS001-saga-rollback-absence.js +212 -0
  95. package/dist/structural-rules/distributed/DS001-saga-rollback-absence.js.map +1 -0
  96. package/dist/structural-rules/distributed/DS002-missing-correlation-id.d.ts +11 -0
  97. package/dist/structural-rules/distributed/DS002-missing-correlation-id.d.ts.map +1 -0
  98. package/dist/structural-rules/distributed/DS002-missing-correlation-id.js +213 -0
  99. package/dist/structural-rules/distributed/DS002-missing-correlation-id.js.map +1 -0
  100. package/dist/structural-rules/distributed/index.d.ts +3 -0
  101. package/dist/structural-rules/distributed/index.d.ts.map +1 -0
  102. package/dist/structural-rules/distributed/index.js +8 -0
  103. package/dist/structural-rules/distributed/index.js.map +1 -0
  104. package/dist/structural-rules/engine.d.ts +25 -0
  105. package/dist/structural-rules/engine.d.ts.map +1 -0
  106. package/dist/structural-rules/engine.js +90 -0
  107. package/dist/structural-rules/engine.js.map +1 -0
  108. package/dist/structural-rules/index.d.ts +41 -0
  109. package/dist/structural-rules/index.d.ts.map +1 -0
  110. package/dist/structural-rules/index.js +141 -0
  111. package/dist/structural-rules/index.js.map +1 -0
  112. package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.d.ts +11 -0
  113. package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.d.ts.map +1 -0
  114. package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.js +66 -0
  115. package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.js.map +1 -0
  116. package/dist/structural-rules/python/PY002-unbounded-dict-singleton.d.ts +11 -0
  117. package/dist/structural-rules/python/PY002-unbounded-dict-singleton.d.ts.map +1 -0
  118. package/dist/structural-rules/python/PY002-unbounded-dict-singleton.js +135 -0
  119. package/dist/structural-rules/python/PY002-unbounded-dict-singleton.js.map +1 -0
  120. package/dist/structural-rules/python/PY003-broad-except-clause.d.ts +11 -0
  121. package/dist/structural-rules/python/PY003-broad-except-clause.d.ts.map +1 -0
  122. package/dist/structural-rules/python/PY003-broad-except-clause.js +86 -0
  123. package/dist/structural-rules/python/PY003-broad-except-clause.js.map +1 -0
  124. package/dist/structural-rules/python/PY004-swallowed-async-exception.d.ts +11 -0
  125. package/dist/structural-rules/python/PY004-swallowed-async-exception.d.ts.map +1 -0
  126. package/dist/structural-rules/python/PY004-swallowed-async-exception.js +167 -0
  127. package/dist/structural-rules/python/PY004-swallowed-async-exception.js.map +1 -0
  128. package/dist/structural-rules/python/PY005-fastapi-without-pydantic.d.ts +11 -0
  129. package/dist/structural-rules/python/PY005-fastapi-without-pydantic.d.ts.map +1 -0
  130. package/dist/structural-rules/python/PY005-fastapi-without-pydantic.js +154 -0
  131. package/dist/structural-rules/python/PY005-fastapi-without-pydantic.js.map +1 -0
  132. package/dist/structural-rules/python/PY006-blocking-io-in-async.d.ts +11 -0
  133. package/dist/structural-rules/python/PY006-blocking-io-in-async.d.ts.map +1 -0
  134. package/dist/structural-rules/python/PY006-blocking-io-in-async.js +130 -0
  135. package/dist/structural-rules/python/PY006-blocking-io-in-async.js.map +1 -0
  136. package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.d.ts +11 -0
  137. package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.d.ts.map +1 -0
  138. package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.js +93 -0
  139. package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.js.map +1 -0
  140. package/dist/structural-rules/python/PY008-celery-task-without-retry.d.ts +11 -0
  141. package/dist/structural-rules/python/PY008-celery-task-without-retry.d.ts.map +1 -0
  142. package/dist/structural-rules/python/PY008-celery-task-without-retry.js +154 -0
  143. package/dist/structural-rules/python/PY008-celery-task-without-retry.js.map +1 -0
  144. package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.d.ts +11 -0
  145. package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.d.ts.map +1 -0
  146. package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.js +133 -0
  147. package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.js.map +1 -0
  148. package/dist/structural-rules/python/PY010-leaked-aiohttp-session.d.ts +11 -0
  149. package/dist/structural-rules/python/PY010-leaked-aiohttp-session.d.ts.map +1 -0
  150. package/dist/structural-rules/python/PY010-leaked-aiohttp-session.js +80 -0
  151. package/dist/structural-rules/python/PY010-leaked-aiohttp-session.js.map +1 -0
  152. package/dist/structural-rules/rules/SR001-swallowed-async-rejection.d.ts +11 -0
  153. package/dist/structural-rules/rules/SR001-swallowed-async-rejection.d.ts.map +1 -0
  154. package/dist/structural-rules/rules/SR001-swallowed-async-rejection.js +145 -0
  155. package/dist/structural-rules/rules/SR001-swallowed-async-rejection.js.map +1 -0
  156. package/dist/structural-rules/rules/SR002-unbounded-collection.d.ts +11 -0
  157. package/dist/structural-rules/rules/SR002-unbounded-collection.d.ts.map +1 -0
  158. package/dist/structural-rules/rules/SR002-unbounded-collection.js +196 -0
  159. package/dist/structural-rules/rules/SR002-unbounded-collection.js.map +1 -0
  160. package/dist/structural-rules/rules/SR003-timer-without-cleanup.d.ts +11 -0
  161. package/dist/structural-rules/rules/SR003-timer-without-cleanup.d.ts.map +1 -0
  162. package/dist/structural-rules/rules/SR003-timer-without-cleanup.js +148 -0
  163. package/dist/structural-rules/rules/SR003-timer-without-cleanup.js.map +1 -0
  164. package/dist/structural-rules/rules/SR004-request-boundary-no-validation.d.ts +11 -0
  165. package/dist/structural-rules/rules/SR004-request-boundary-no-validation.d.ts.map +1 -0
  166. package/dist/structural-rules/rules/SR004-request-boundary-no-validation.js +162 -0
  167. package/dist/structural-rules/rules/SR004-request-boundary-no-validation.js.map +1 -0
  168. package/dist/structural-rules/rules/SR005-halfopen-probe-gate.d.ts +11 -0
  169. package/dist/structural-rules/rules/SR005-halfopen-probe-gate.d.ts.map +1 -0
  170. package/dist/structural-rules/rules/SR005-halfopen-probe-gate.js +150 -0
  171. package/dist/structural-rules/rules/SR005-halfopen-probe-gate.js.map +1 -0
  172. package/dist/structural-rules/rules/SR006-fanout-error-sanitization.d.ts +11 -0
  173. package/dist/structural-rules/rules/SR006-fanout-error-sanitization.d.ts.map +1 -0
  174. package/dist/structural-rules/rules/SR006-fanout-error-sanitization.js +161 -0
  175. package/dist/structural-rules/rules/SR006-fanout-error-sanitization.js.map +1 -0
  176. package/dist/structural-rules/rules/SR007-cross-request-error.d.ts +11 -0
  177. package/dist/structural-rules/rules/SR007-cross-request-error.d.ts.map +1 -0
  178. package/dist/structural-rules/rules/SR007-cross-request-error.js +175 -0
  179. package/dist/structural-rules/rules/SR007-cross-request-error.js.map +1 -0
  180. package/dist/structural-rules/rules/SR008-background-task-orphan.d.ts +11 -0
  181. package/dist/structural-rules/rules/SR008-background-task-orphan.d.ts.map +1 -0
  182. package/dist/structural-rules/rules/SR008-background-task-orphan.js +176 -0
  183. package/dist/structural-rules/rules/SR008-background-task-orphan.js.map +1 -0
  184. package/dist/structural-rules/rules/SR009-missing-retry-backoff.d.ts +11 -0
  185. package/dist/structural-rules/rules/SR009-missing-retry-backoff.d.ts.map +1 -0
  186. package/dist/structural-rules/rules/SR009-missing-retry-backoff.js +168 -0
  187. package/dist/structural-rules/rules/SR009-missing-retry-backoff.js.map +1 -0
  188. package/dist/structural-rules/rules/SR010-retry-storm.d.ts +11 -0
  189. package/dist/structural-rules/rules/SR010-retry-storm.d.ts.map +1 -0
  190. package/dist/structural-rules/rules/SR010-retry-storm.js +181 -0
  191. package/dist/structural-rules/rules/SR010-retry-storm.js.map +1 -0
  192. package/dist/structural-rules/rules/SR011-event-listener-leak.d.ts +11 -0
  193. package/dist/structural-rules/rules/SR011-event-listener-leak.d.ts.map +1 -0
  194. package/dist/structural-rules/rules/SR011-event-listener-leak.js +208 -0
  195. package/dist/structural-rules/rules/SR011-event-listener-leak.js.map +1 -0
  196. package/dist/structural-rules/rules/SR012-promise-race-leak.d.ts +11 -0
  197. package/dist/structural-rules/rules/SR012-promise-race-leak.d.ts.map +1 -0
  198. package/dist/structural-rules/rules/SR012-promise-race-leak.js +191 -0
  199. package/dist/structural-rules/rules/SR012-promise-race-leak.js.map +1 -0
  200. package/dist/structural-rules/rules/SR013-missing-idempotency-key.d.ts +11 -0
  201. package/dist/structural-rules/rules/SR013-missing-idempotency-key.d.ts.map +1 -0
  202. package/dist/structural-rules/rules/SR013-missing-idempotency-key.js +219 -0
  203. package/dist/structural-rules/rules/SR013-missing-idempotency-key.js.map +1 -0
  204. package/dist/structural-rules/rules/SR014-mutable-closure-async.d.ts +11 -0
  205. package/dist/structural-rules/rules/SR014-mutable-closure-async.d.ts.map +1 -0
  206. package/dist/structural-rules/rules/SR014-mutable-closure-async.js +208 -0
  207. package/dist/structural-rules/rules/SR014-mutable-closure-async.js.map +1 -0
  208. package/dist/structural-rules/rules/SR015-dangling-abort-controller.d.ts +11 -0
  209. package/dist/structural-rules/rules/SR015-dangling-abort-controller.d.ts.map +1 -0
  210. package/dist/structural-rules/rules/SR015-dangling-abort-controller.js +190 -0
  211. package/dist/structural-rules/rules/SR015-dangling-abort-controller.js.map +1 -0
  212. package/dist/structural-rules/rules/SR016-unsafe-json-parse.d.ts +11 -0
  213. package/dist/structural-rules/rules/SR016-unsafe-json-parse.d.ts.map +1 -0
  214. package/dist/structural-rules/rules/SR016-unsafe-json-parse.js +187 -0
  215. package/dist/structural-rules/rules/SR016-unsafe-json-parse.js.map +1 -0
  216. package/dist/structural-rules/suppressions.d.ts +43 -0
  217. package/dist/structural-rules/suppressions.d.ts.map +1 -0
  218. package/dist/structural-rules/suppressions.js +115 -0
  219. package/dist/structural-rules/suppressions.js.map +1 -0
  220. package/dist/structural-rules/types.d.ts +43 -0
  221. package/dist/structural-rules/types.d.ts.map +1 -0
  222. package/dist/structural-rules/types.js +3 -0
  223. package/dist/structural-rules/types.js.map +1 -0
  224. package/dist/utils/brain-cache.d.ts +100 -0
  225. package/dist/utils/brain-cache.d.ts.map +1 -0
  226. package/dist/utils/brain-cache.js +346 -0
  227. package/dist/utils/brain-cache.js.map +1 -0
  228. package/dist/utils/governance-provenance.d.ts +95 -0
  229. package/dist/utils/governance-provenance.d.ts.map +1 -0
  230. package/dist/utils/governance-provenance.js +187 -0
  231. package/dist/utils/governance-provenance.js.map +1 -0
  232. package/dist/utils/pilot-metrics.d.ts +46 -0
  233. package/dist/utils/pilot-metrics.d.ts.map +1 -0
  234. package/dist/utils/pilot-metrics.js +240 -0
  235. package/dist/utils/pilot-metrics.js.map +1 -0
  236. package/dist/utils/replay-runtime.d.ts +34 -0
  237. package/dist/utils/replay-runtime.d.ts.map +1 -1
  238. package/dist/utils/replay-runtime.js +207 -0
  239. package/dist/utils/replay-runtime.js.map +1 -1
  240. package/dist/workspace/cross-repo-graph.d.ts +111 -0
  241. package/dist/workspace/cross-repo-graph.d.ts.map +1 -0
  242. package/dist/workspace/cross-repo-graph.js +450 -0
  243. package/dist/workspace/cross-repo-graph.js.map +1 -0
  244. package/dist/workspace/federated-context.d.ts +144 -0
  245. package/dist/workspace/federated-context.d.ts.map +1 -0
  246. package/dist/workspace/federated-context.js +347 -0
  247. package/dist/workspace/federated-context.js.map +1 -0
  248. package/dist/workspace/index.d.ts +38 -0
  249. package/dist/workspace/index.d.ts.map +1 -0
  250. package/dist/workspace/index.js +48 -0
  251. package/dist/workspace/index.js.map +1 -0
  252. package/package.json +9 -9
@@ -47,8 +47,14 @@ const execution_bus_1 = require("../utils/execution-bus");
47
47
  const runtime_events_1 = require("../utils/runtime-events");
48
48
  const control_plane_1 = require("../utils/control-plane");
49
49
  const workspace_runtime_1 = require("../utils/workspace-runtime");
50
+ const workspace_1 = require("../workspace");
51
+ const semantic_1 = require("../semantic");
52
+ const intent_engine_1 = require("../intent-engine");
50
53
  const replay_runtime_1 = require("../utils/replay-runtime");
51
54
  const contracts_1 = require("@neurcode-ai/contracts");
55
+ const telemetry_1 = require("@neurcode-ai/telemetry");
56
+ const governance_provenance_1 = require("../utils/governance-provenance");
57
+ const pilot_metrics_1 = require("../utils/pilot-metrics");
52
58
  // ── Configuration ──────────────────────────────────────────────────────────────
53
59
  exports.DAEMON_PORT = Number.parseInt(process.env.NEURCODE_DAEMON_PORT || '4321', 10) || 4321;
54
60
  exports.DAEMON_HOST = process.env.NEURCODE_DAEMON_HOST || '127.0.0.1';
@@ -1767,6 +1773,243 @@ async function handleExecuteWorkspace(req, res) {
1767
1773
  });
1768
1774
  success(res, result);
1769
1775
  }
1776
+ // ── Semantic Search Handlers ──────────────────────────────────────────────────
1777
+ /**
1778
+ * POST /workspaces/:id/semantic-search
1779
+ *
1780
+ * Body: { query: string, limit?: number, minScore?: number }
1781
+ * Returns: { results: SemanticSearchResult[], totalIndexed: number }
1782
+ *
1783
+ * Performs TF-IDF vector similarity search over the brain context index.
1784
+ * Fully deterministic — zero LLM calls, zero external dependencies.
1785
+ */
1786
+ async function handleSemanticSearch(req, res, workspaceId) {
1787
+ const workspace = (0, workspace_runtime_1.getWorkspaceById)(workspaceId, process.cwd());
1788
+ if (!workspace) {
1789
+ failure(res, `Workspace not found: ${workspaceId}`, 404, { code: 'workspace.not_found' });
1790
+ return;
1791
+ }
1792
+ let body;
1793
+ try {
1794
+ body = JSON.parse(await readBody(req));
1795
+ }
1796
+ catch {
1797
+ failure(res, 'Invalid JSON body', 400, { code: 'bad_request' });
1798
+ return;
1799
+ }
1800
+ const query = typeof body.query === 'string' ? body.query.trim() : '';
1801
+ if (!query) {
1802
+ failure(res, 'Missing required field: query', 400, { code: 'bad_request' });
1803
+ return;
1804
+ }
1805
+ const limit = typeof body.limit === 'number' ? Math.min(50, Math.max(1, body.limit)) : 10;
1806
+ const minScore = typeof body.minScore === 'number' ? Math.max(0, body.minScore) : 0.01;
1807
+ // Use the first enabled repo root, or fall back to process.cwd()
1808
+ const rootPath = workspace.repositories.find((r) => r.enabled)?.rootPath ?? process.cwd();
1809
+ const scope = { orgId: null, projectId: null };
1810
+ const stats = (0, semantic_1.getSemanticIndexStats)(rootPath, scope);
1811
+ // Auto-build index if it doesn't exist yet
1812
+ if (!stats.exists || stats.documentCount === 0) {
1813
+ (0, semantic_1.buildSemanticIndex)(rootPath, scope);
1814
+ }
1815
+ const results = (0, semantic_1.semanticSearch)(rootPath, scope, query, { limit, minScore });
1816
+ const updatedStats = (0, semantic_1.getSemanticIndexStats)(rootPath, scope);
1817
+ success(res, {
1818
+ schemaVersion: 'neurcode.semantic-search.v1',
1819
+ workspaceId,
1820
+ workspaceName: workspace.name,
1821
+ query,
1822
+ results,
1823
+ totalIndexed: updatedStats.documentCount,
1824
+ indexBuiltAt: updatedStats.builtAt,
1825
+ });
1826
+ }
1827
+ /**
1828
+ * POST /workspaces/:id/semantic-index/build
1829
+ *
1830
+ * Forces a full semantic index rebuild from the current brain context.
1831
+ * Returns: { documentsIndexed: number, builtAt: string }
1832
+ */
1833
+ async function handleBuildSemanticIndex(_req, res, workspaceId) {
1834
+ const workspace = (0, workspace_runtime_1.getWorkspaceById)(workspaceId, process.cwd());
1835
+ if (!workspace) {
1836
+ failure(res, `Workspace not found: ${workspaceId}`, 404, { code: 'workspace.not_found' });
1837
+ return;
1838
+ }
1839
+ const rootPath = workspace.repositories.find((r) => r.enabled)?.rootPath ?? process.cwd();
1840
+ const scope = { orgId: null, projectId: null };
1841
+ const count = (0, semantic_1.buildSemanticIndex)(rootPath, scope);
1842
+ const stats = (0, semantic_1.getSemanticIndexStats)(rootPath, scope);
1843
+ success(res, {
1844
+ schemaVersion: 'neurcode.semantic-index.v1',
1845
+ workspaceId,
1846
+ workspaceName: workspace.name,
1847
+ documentsIndexed: count,
1848
+ builtAt: stats.builtAt,
1849
+ sizeBytes: stats.sizeBytes,
1850
+ });
1851
+ }
1852
+ /**
1853
+ * POST /workspaces/:id/intent-expand
1854
+ *
1855
+ * Body: { intent: string, forceRefresh?: boolean }
1856
+ *
1857
+ * Returns a rich semantic governance artifact for the given intent.
1858
+ * The artifact is HMAC-signed and cached — subsequent calls return
1859
+ * the stored artifact without calling the LLM again.
1860
+ *
1861
+ * expansionMethod='llm' → LLM was called (first time only)
1862
+ * expansionMethod='keyword-fallback' → no LLM key configured
1863
+ * signature present → tamper-evident, auditable
1864
+ */
1865
+ async function handleIntentExpand(req, res, workspaceId) {
1866
+ const workspace = (0, workspace_runtime_1.getWorkspaceById)(workspaceId, process.cwd());
1867
+ if (!workspace) {
1868
+ failure(res, `Workspace not found: ${workspaceId}`, 404, { code: 'workspace.not_found' });
1869
+ return;
1870
+ }
1871
+ let body;
1872
+ try {
1873
+ body = JSON.parse(await readBody(req));
1874
+ }
1875
+ catch {
1876
+ failure(res, 'Invalid JSON body', 400, { code: 'bad_request' });
1877
+ return;
1878
+ }
1879
+ const intent = typeof body.intent === 'string' ? body.intent.trim() : '';
1880
+ if (!intent) {
1881
+ failure(res, 'Missing required field: intent', 400, { code: 'bad_request' });
1882
+ return;
1883
+ }
1884
+ const forceRefresh = body.forceRefresh === true;
1885
+ const rootPath = workspace.repositories.find((r) => r.enabled)?.rootPath ?? process.cwd();
1886
+ // Check cache first (synchronous — no LLM call)
1887
+ if (!forceRefresh) {
1888
+ const cached = (0, intent_engine_1.loadCachedExpansion)(rootPath, intent);
1889
+ if (cached) {
1890
+ success(res, {
1891
+ schemaVersion: 'neurcode.intent-expansion.v1',
1892
+ workspaceId,
1893
+ workspaceName: workspace.name,
1894
+ cacheHit: true,
1895
+ expansion: cached,
1896
+ summary: (0, intent_engine_1.formatExpansionSummary)(cached),
1897
+ });
1898
+ return;
1899
+ }
1900
+ }
1901
+ // Expand (may call LLM or use fallback)
1902
+ try {
1903
+ const expansion = await (0, intent_engine_1.expandIntent)(intent, { cwd: rootPath, forceRefresh });
1904
+ success(res, {
1905
+ schemaVersion: 'neurcode.intent-expansion.v1',
1906
+ workspaceId,
1907
+ workspaceName: workspace.name,
1908
+ cacheHit: false,
1909
+ expansion,
1910
+ summary: (0, intent_engine_1.formatExpansionSummary)(expansion),
1911
+ });
1912
+ }
1913
+ catch (err) {
1914
+ failure(res, `Intent expansion failed: ${err instanceof Error ? err.message : String(err)}`, 500, { code: 'intent_expansion.failed' });
1915
+ }
1916
+ }
1917
+ // ── Cross-Repo Context Handlers ──────────────────────────────────────────────
1918
+ /**
1919
+ * GET /workspaces/:id/cross-repo-graph
1920
+ *
1921
+ * Returns the detected cross-repo dependency graph for all repos in a workspace.
1922
+ * Used by Fix Center to show which services are coupled to the file being patched.
1923
+ *
1924
+ * Query params:
1925
+ * ?format=summary — human-readable summary (default: full JSON)
1926
+ */
1927
+ async function handleGetCrossRepoGraph(req, res, workspaceId) {
1928
+ const workspace = (0, workspace_runtime_1.getWorkspaceById)(workspaceId, process.cwd());
1929
+ if (!workspace) {
1930
+ failure(res, `Workspace not found: ${workspaceId}`, 404, { code: 'workspace.not_found' });
1931
+ return;
1932
+ }
1933
+ const repos = workspace.repositories.filter((r) => r.enabled);
1934
+ if (repos.length === 0) {
1935
+ success(res, {
1936
+ schemaVersion: 'neurcode.cross-repo-graph.v1',
1937
+ workspaceId,
1938
+ workspaceName: workspace.name,
1939
+ graph: { generatedAt: new Date().toISOString(), repos: [], edges: [], stats: { filesScanned: 0, edgesDetected: 0, byVia: {}, byConfidence: {} } },
1940
+ edgeCount: 0,
1941
+ });
1942
+ return;
1943
+ }
1944
+ const graph = (0, workspace_1.buildCrossRepoGraph)({ repos });
1945
+ success(res, {
1946
+ schemaVersion: 'neurcode.cross-repo-graph.v1',
1947
+ workspaceId,
1948
+ workspaceName: workspace.name,
1949
+ graph,
1950
+ edgeCount: graph.edges.length,
1951
+ });
1952
+ }
1953
+ /**
1954
+ * POST /workspaces/:id/federated-context
1955
+ *
1956
+ * Builds the full federated context for a set of changed files in the primary repo.
1957
+ * This is the multi-repo blast radius analysis endpoint.
1958
+ *
1959
+ * Body: { primaryRepo: string; changedFiles: string[] }
1960
+ *
1961
+ * Returns:
1962
+ * - affectedDownstreamRepos: services that call the changed code
1963
+ * - relevantUpstreamRepos: services the changed code calls
1964
+ * - federatedBlindSpots: structural coupling invisible to code scanning
1965
+ * - summary.requiresCoordinatedDeploy: true if any high-confidence edge exists
1966
+ */
1967
+ async function handleGetFederatedContext(req, res, workspaceId) {
1968
+ const workspace = (0, workspace_runtime_1.getWorkspaceById)(workspaceId, process.cwd());
1969
+ if (!workspace) {
1970
+ failure(res, `Workspace not found: ${workspaceId}`, 404, { code: 'workspace.not_found' });
1971
+ return;
1972
+ }
1973
+ let body = {};
1974
+ try {
1975
+ body = JSON.parse(await readBody(req));
1976
+ }
1977
+ catch {
1978
+ failure(res, 'Invalid JSON body', 400, { code: 'invalid_body' });
1979
+ return;
1980
+ }
1981
+ const primaryRepo = asNonEmptyString(body.primaryRepo);
1982
+ if (!primaryRepo) {
1983
+ failure(res, 'Missing required field: primaryRepo', 400, { code: 'missing_field' });
1984
+ return;
1985
+ }
1986
+ const changedFiles = Array.isArray(body.changedFiles)
1987
+ ? body.changedFiles.filter((f) => typeof f === 'string')
1988
+ : [];
1989
+ if (changedFiles.length === 0) {
1990
+ failure(res, 'changedFiles must be a non-empty array of strings', 400, { code: 'missing_field' });
1991
+ return;
1992
+ }
1993
+ const repoExists = workspace.repositories.some((r) => r.name === primaryRepo);
1994
+ if (!repoExists) {
1995
+ failure(res, `Repo "${primaryRepo}" not found in workspace "${workspaceId}"`, 404, { code: 'repo.not_found' });
1996
+ return;
1997
+ }
1998
+ const pkg = (0, workspace_1.buildFederatedContext)({
1999
+ workspaceName: workspace.name,
2000
+ repos: workspace.repositories,
2001
+ primaryRepoName: primaryRepo,
2002
+ changedFiles,
2003
+ });
2004
+ const { workspaceName: _wn, ...pkgRest } = pkg;
2005
+ success(res, {
2006
+ schemaVersion: 'neurcode.federated-context.v1',
2007
+ workspaceId,
2008
+ workspaceName: workspace.name,
2009
+ ...pkgRest,
2010
+ humanSummary: (0, workspace_1.formatFederatedContextSummary)(pkg),
2011
+ });
2012
+ }
1770
2013
  async function handleReplayState(req, res) {
1771
2014
  const requestUrl = new URL(req.url || '/replay/state', 'http://localhost');
1772
2015
  const at = asNonEmptyString(requestUrl.searchParams.get('at')) || new Date().toISOString();
@@ -1875,6 +2118,770 @@ async function handleRuntimeEventStream(req, res) {
1875
2118
  req.on('error', cleanup);
1876
2119
  res.on('close', cleanup);
1877
2120
  }
2121
+ // ── Governance findings handler ───────────────────────────────────────────────
2122
+ // ── Enterprise docs handlers ────────────────────────────────────────────────
2123
+ /** Returns a manifest of all enterprise docs with titles and slugs. */
2124
+ async function handleDocsManifest(res) {
2125
+ // Resolve docs directory relative to repo root (two levels up from packages/cli/dist or src)
2126
+ const possibleRoots = [
2127
+ path.resolve(__dirname, '..', '..', '..', '..', 'docs', 'enterprise'),
2128
+ path.resolve(process.cwd(), 'docs', 'enterprise'),
2129
+ ];
2130
+ let docsDir = '';
2131
+ for (const candidate of possibleRoots) {
2132
+ if (fs.existsSync(candidate)) {
2133
+ docsDir = candidate;
2134
+ break;
2135
+ }
2136
+ }
2137
+ if (!docsDir) {
2138
+ success(res, { docs: [], docsDir: null, error: 'docs/enterprise/ directory not found' });
2139
+ return;
2140
+ }
2141
+ try {
2142
+ const files = fs.readdirSync(docsDir).filter((f) => f.endsWith('.md')).sort();
2143
+ const docs = files.map((filename) => {
2144
+ const slug = filename.replace(/\.md$/, '');
2145
+ let title = slug;
2146
+ try {
2147
+ const firstLine = fs.readFileSync(path.join(docsDir, filename), 'utf8').split('\n')[0] || '';
2148
+ const match = firstLine.match(/^#+ (.+)/);
2149
+ if (match)
2150
+ title = match[1].trim();
2151
+ }
2152
+ catch { /* use slug as title */ }
2153
+ return { slug, filename, title };
2154
+ });
2155
+ success(res, { docs, docsDir });
2156
+ }
2157
+ catch (err) {
2158
+ failure(res, err instanceof Error ? err.message : String(err), 500);
2159
+ }
2160
+ }
2161
+ /** Returns the raw markdown content of a single enterprise doc file. */
2162
+ async function handleDocsContent(res, slug) {
2163
+ // Sanitize: only allow alphanumeric, dash, underscore — no path traversal
2164
+ if (!/^[\w-]+$/.test(slug)) {
2165
+ failure(res, 'Invalid doc slug', 400);
2166
+ return;
2167
+ }
2168
+ const possibleRoots = [
2169
+ path.resolve(__dirname, '..', '..', '..', '..', 'docs', 'enterprise'),
2170
+ path.resolve(process.cwd(), 'docs', 'enterprise'),
2171
+ ];
2172
+ let docFile = '';
2173
+ for (const rootDir of possibleRoots) {
2174
+ const candidate = path.join(rootDir, `${slug}.md`);
2175
+ if (fs.existsSync(candidate)) {
2176
+ docFile = candidate;
2177
+ break;
2178
+ }
2179
+ }
2180
+ if (!docFile) {
2181
+ failure(res, `Doc not found: ${slug}`, 404);
2182
+ return;
2183
+ }
2184
+ try {
2185
+ const content = fs.readFileSync(docFile, 'utf8');
2186
+ res.writeHead(200, { 'Content-Type': 'text/markdown; charset=utf-8', 'Cache-Control': 'no-cache' });
2187
+ res.end(content);
2188
+ }
2189
+ catch (err) {
2190
+ failure(res, err instanceof Error ? err.message : String(err), 500);
2191
+ }
2192
+ }
2193
+ // ── End enterprise docs handlers ────────────────────────────────────────────
2194
+ async function handleGovernanceFindings(req, res) {
2195
+ const cwd = process.cwd();
2196
+ const requestUrl = new URL(req.url || '/governance/findings', 'http://localhost');
2197
+ const limitRaw = parseInt(requestUrl.searchParams.get('limit') || '100', 10);
2198
+ const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? Math.min(limitRaw, 500) : 100;
2199
+ const filterSeverity = requestUrl.searchParams.get('severity') || undefined;
2200
+ const filterRule = requestUrl.searchParams.get('rule') || undefined;
2201
+ const filterFile = requestUrl.searchParams.get('file') || undefined;
2202
+ const filterDeterminism = requestUrl.searchParams.get('determinism') || undefined;
2203
+ const candidatePaths = [
2204
+ path.resolve(cwd, '.neurcode', 'last-verify-output.json'),
2205
+ path.resolve(cwd, '.neurcode', 'verify-output.json'),
2206
+ ];
2207
+ let rawFindings = [];
2208
+ let sourceFile = '';
2209
+ let envelope = {};
2210
+ for (const candidate of candidatePaths) {
2211
+ try {
2212
+ if (fs.existsSync(candidate)) {
2213
+ const parsed = JSON.parse(fs.readFileSync(candidate, 'utf8'));
2214
+ if (parsed && typeof parsed === 'object') {
2215
+ envelope = parsed;
2216
+ const findings = parsed.findings;
2217
+ if (Array.isArray(findings)) {
2218
+ rawFindings = findings;
2219
+ }
2220
+ else {
2221
+ const violations = parsed.violations;
2222
+ if (Array.isArray(violations)) {
2223
+ rawFindings = violations.map((v) => ({
2224
+ findingId: v.ruleId || 'unknown',
2225
+ ruleId: v.ruleId || 'unknown',
2226
+ ruleName: v.message || 'Unknown finding',
2227
+ severity: v.severity || 'advisory',
2228
+ category: 'structural',
2229
+ determinism: 'deterministic-structural',
2230
+ file: v.file,
2231
+ line: v.line,
2232
+ message: v.message,
2233
+ explanation: v.explanation,
2234
+ source: 'structural-rule-engine',
2235
+ confidence: v.confidence || 1.0,
2236
+ remediable: true,
2237
+ remediationStatus: 'pending',
2238
+ }));
2239
+ }
2240
+ }
2241
+ sourceFile = candidate;
2242
+ }
2243
+ break;
2244
+ }
2245
+ }
2246
+ catch {
2247
+ // continue to next candidate
2248
+ }
2249
+ }
2250
+ // Filter
2251
+ let filtered = rawFindings;
2252
+ if (filterSeverity) {
2253
+ filtered = filtered.filter((f) => (f.severity || '') === filterSeverity);
2254
+ }
2255
+ if (filterRule) {
2256
+ filtered = filtered.filter((f) => {
2257
+ const ruleId = f.ruleId || '';
2258
+ const ruleName = f.ruleName || '';
2259
+ return ruleId.includes(filterRule) || ruleName.toLowerCase().includes(filterRule.toLowerCase());
2260
+ });
2261
+ }
2262
+ if (filterFile) {
2263
+ filtered = filtered.filter((f) => {
2264
+ const filePath = f.file || '';
2265
+ return filePath.includes(filterFile);
2266
+ });
2267
+ }
2268
+ if (filterDeterminism) {
2269
+ filtered = filtered.filter((f) => (f.determinism || '') === filterDeterminism);
2270
+ }
2271
+ const paginated = filtered.slice(0, limit);
2272
+ const blockingCount = rawFindings.filter((f) => {
2273
+ const sev = (f.severity || '').toLowerCase();
2274
+ return sev === 'critical' || sev === 'high' || sev === 'blocking' || sev === 'block';
2275
+ }).length;
2276
+ const advisoryCount = rawFindings.length - blockingCount;
2277
+ const meta = {
2278
+ verdict: envelope.verdict || (rawFindings.length === 0 ? 'PASS' : blockingCount > 0 ? 'FAIL' : 'WARN'),
2279
+ verifiedAt: envelope.verifiedAt || envelope.timestamp || null,
2280
+ projectRoot: envelope.projectRoot || cwd,
2281
+ planId: envelope.planId || null,
2282
+ schemaVersion: envelope.schemaVersion || null,
2283
+ fingerprint: envelope.fingerprint || null,
2284
+ totalFindings: rawFindings.length,
2285
+ blockingCount,
2286
+ advisoryCount,
2287
+ structuralCount: rawFindings.filter((f) => f.category === 'structural').length,
2288
+ semanticCount: rawFindings.filter((f) => f.category === 'semantic').length,
2289
+ sourceFile: sourceFile || null,
2290
+ filtered: filtered.length,
2291
+ returned: paginated.length,
2292
+ };
2293
+ success(res, { meta, findings: paginated });
2294
+ }
2295
+ // ── Governance overview handler ───────────────────────────────────────────────
2296
+ async function handleGovernanceOverview(_req, res) {
2297
+ const cwd = process.cwd();
2298
+ // Read last verify output
2299
+ let lastVerifyMeta = {};
2300
+ let lastVerifyFindings = [];
2301
+ const verifyPath = path.resolve(cwd, '.neurcode', 'last-verify-output.json');
2302
+ try {
2303
+ if (fs.existsSync(verifyPath)) {
2304
+ const parsed = JSON.parse(fs.readFileSync(verifyPath, 'utf8'));
2305
+ if (parsed && typeof parsed === 'object') {
2306
+ lastVerifyMeta = parsed;
2307
+ const findings = parsed.findings;
2308
+ const violations = parsed.violations;
2309
+ if (Array.isArray(findings))
2310
+ lastVerifyFindings = findings;
2311
+ else if (Array.isArray(violations))
2312
+ lastVerifyFindings = violations;
2313
+ }
2314
+ }
2315
+ }
2316
+ catch { /* ignore */ }
2317
+ // Read provenance file for recent runs
2318
+ const provenancePaths = [
2319
+ path.resolve(cwd, '.neurcode', 'provenance.json'),
2320
+ path.resolve(cwd, '.neurcode', 'provenance-chain.json'),
2321
+ ];
2322
+ let recentRuns = [];
2323
+ for (const pp of provenancePaths) {
2324
+ try {
2325
+ if (fs.existsSync(pp)) {
2326
+ const parsed = JSON.parse(fs.readFileSync(pp, 'utf8'));
2327
+ if (Array.isArray(parsed)) {
2328
+ recentRuns = parsed.slice(-20).reverse();
2329
+ }
2330
+ else if (parsed && typeof parsed === 'object') {
2331
+ const records = parsed.records;
2332
+ if (Array.isArray(records))
2333
+ recentRuns = records.slice(-20).reverse();
2334
+ }
2335
+ break;
2336
+ }
2337
+ }
2338
+ catch { /* ignore */ }
2339
+ }
2340
+ // Compute top triggered rules
2341
+ const ruleFreq = {};
2342
+ for (const f of lastVerifyFindings) {
2343
+ const ruleId = f.ruleId || 'unknown';
2344
+ ruleFreq[ruleId] = (ruleFreq[ruleId] || 0) + 1;
2345
+ }
2346
+ const topRules = Object.entries(ruleFreq)
2347
+ .sort((a, b) => b[1] - a[1])
2348
+ .slice(0, 5)
2349
+ .map(([ruleId, count]) => ({ ruleId, count }));
2350
+ // Read remediation artifacts
2351
+ const remediationDir = path.resolve(cwd, '.neurcode', 'remediation');
2352
+ let remediationCount = 0;
2353
+ let remediationPending = 0;
2354
+ let remediationValidated = 0;
2355
+ try {
2356
+ if (fs.existsSync(remediationDir)) {
2357
+ const entries = fs.readdirSync(remediationDir);
2358
+ for (const entry of entries) {
2359
+ if (entry.endsWith('-request.json'))
2360
+ remediationCount++;
2361
+ if (entry.endsWith('-receipt.json'))
2362
+ remediationValidated++;
2363
+ }
2364
+ remediationPending = remediationCount - remediationValidated;
2365
+ }
2366
+ }
2367
+ catch { /* ignore */ }
2368
+ const blockingCount = lastVerifyFindings.filter((f) => {
2369
+ const sev = (f.severity || '').toLowerCase();
2370
+ return sev === 'critical' || sev === 'high' || sev === 'blocking' || sev === 'block';
2371
+ }).length;
2372
+ success(res, {
2373
+ lastVerify: {
2374
+ verdict: lastVerifyMeta.verdict || (lastVerifyFindings.length === 0 ? 'PASS' : blockingCount > 0 ? 'FAIL' : 'WARN'),
2375
+ verifiedAt: lastVerifyMeta.verifiedAt || lastVerifyMeta.timestamp || null,
2376
+ planId: lastVerifyMeta.planId || null,
2377
+ totalFindings: lastVerifyFindings.length,
2378
+ blockingCount,
2379
+ advisoryCount: lastVerifyFindings.length - blockingCount,
2380
+ topRules,
2381
+ },
2382
+ recentRuns: recentRuns.slice(0, 10),
2383
+ remediation: {
2384
+ total: remediationCount,
2385
+ pending: remediationPending,
2386
+ validated: remediationValidated,
2387
+ },
2388
+ projectRoot: cwd,
2389
+ });
2390
+ }
2391
+ // ── Brain cache status handler ────────────────────────────────────────────────
2392
+ async function handleBrainCacheStatus(_req, res) {
2393
+ const cwd = process.cwd();
2394
+ const brainDir = path.resolve(cwd, '.neurcode', 'brain-cache');
2395
+ const manifestPath = path.resolve(brainDir, 'manifest.json');
2396
+ let manifest = null;
2397
+ let cacheSize = 0;
2398
+ let fileCount = 0;
2399
+ let staleCount = 0;
2400
+ let cacheExists = false;
2401
+ try {
2402
+ cacheExists = fs.existsSync(brainDir);
2403
+ if (cacheExists) {
2404
+ const entries = fs.readdirSync(brainDir);
2405
+ fileCount = entries.length;
2406
+ for (const entry of entries) {
2407
+ try {
2408
+ const stat = fs.statSync(path.resolve(brainDir, entry));
2409
+ cacheSize += stat.size;
2410
+ }
2411
+ catch { /* ignore */ }
2412
+ }
2413
+ if (fs.existsSync(manifestPath)) {
2414
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
2415
+ const files = manifest.files;
2416
+ if (Array.isArray(files)) {
2417
+ const now = Date.now();
2418
+ for (const f of files) {
2419
+ const cachedAt = f.cachedAt;
2420
+ if (typeof cachedAt === 'string') {
2421
+ const age = now - new Date(cachedAt).getTime();
2422
+ if (age > 24 * 60 * 60 * 1000)
2423
+ staleCount++;
2424
+ }
2425
+ }
2426
+ }
2427
+ }
2428
+ }
2429
+ }
2430
+ catch { /* ignore */ }
2431
+ // Check semantic index
2432
+ const semanticIndexPath = path.resolve(brainDir, 'semantic-index.json');
2433
+ const hasSemanticIndex = fs.existsSync(semanticIndexPath);
2434
+ let semanticIndexSize = 0;
2435
+ let semanticIndexAt = null;
2436
+ try {
2437
+ if (hasSemanticIndex) {
2438
+ const stat = fs.statSync(semanticIndexPath);
2439
+ semanticIndexSize = stat.size;
2440
+ const idx = JSON.parse(fs.readFileSync(semanticIndexPath, 'utf8'));
2441
+ semanticIndexAt = idx.indexedAt || null;
2442
+ }
2443
+ }
2444
+ catch { /* ignore */ }
2445
+ success(res, {
2446
+ cacheExists,
2447
+ brainDir,
2448
+ manifest: manifest
2449
+ ? {
2450
+ schemaVersion: manifest.schemaVersion,
2451
+ createdAt: manifest.createdAt,
2452
+ updatedAt: manifest.updatedAt,
2453
+ projectId: manifest.projectId,
2454
+ fileCount: Array.isArray(manifest.files) ? manifest.files.length : fileCount,
2455
+ staleCount,
2456
+ stalePercent: fileCount > 0 ? Math.round((staleCount / fileCount) * 100) : 0,
2457
+ }
2458
+ : null,
2459
+ semanticIndex: {
2460
+ exists: hasSemanticIndex,
2461
+ sizeBytes: semanticIndexSize,
2462
+ indexedAt: semanticIndexAt,
2463
+ },
2464
+ cacheDirSizeBytes: cacheSize,
2465
+ cwd,
2466
+ });
2467
+ }
2468
+ // ── Remediation status handler ────────────────────────────────────────────────
2469
+ async function handleRemediationStatus(_req, res) {
2470
+ const cwd = process.cwd();
2471
+ const remediationDir = path.resolve(cwd, '.neurcode', 'remediation');
2472
+ const artifacts = [];
2473
+ try {
2474
+ if (fs.existsSync(remediationDir)) {
2475
+ const entries = fs.readdirSync(remediationDir).sort().reverse();
2476
+ for (const entry of entries.slice(0, 50)) {
2477
+ if (!entry.endsWith('.json'))
2478
+ continue;
2479
+ try {
2480
+ const fullPath = path.resolve(remediationDir, entry);
2481
+ const stat = fs.statSync(fullPath);
2482
+ const raw = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
2483
+ const isReceipt = entry.endsWith('-receipt.json');
2484
+ const isRequest = entry.endsWith('-request.json');
2485
+ artifacts.push({
2486
+ filename: entry,
2487
+ type: isReceipt ? 'receipt' : isRequest ? 'request' : 'unknown',
2488
+ sizeBytes: stat.size,
2489
+ createdAt: stat.birthtime.toISOString(),
2490
+ modifiedAt: stat.mtime.toISOString(),
2491
+ findingId: raw.findingId || raw.requestId || null,
2492
+ ruleId: raw.ruleId || null,
2493
+ status: isReceipt
2494
+ ? (raw.overallStatus || 'validated')
2495
+ : 'pending',
2496
+ passed: isReceipt ? raw.passed || false : null,
2497
+ });
2498
+ }
2499
+ catch { /* ignore */ }
2500
+ }
2501
+ }
2502
+ }
2503
+ catch { /* ignore */ }
2504
+ const requests = artifacts.filter((a) => a.type === 'request');
2505
+ const receipts = artifacts.filter((a) => a.type === 'receipt');
2506
+ const passedReceipts = receipts.filter((a) => a.passed === true);
2507
+ success(res, {
2508
+ remediationDir,
2509
+ exists: fs.existsSync(remediationDir),
2510
+ summary: {
2511
+ total: requests.length,
2512
+ pending: requests.length - receipts.length,
2513
+ validated: receipts.length,
2514
+ passed: passedReceipts.length,
2515
+ failed: receipts.length - passedReceipts.length,
2516
+ },
2517
+ artifacts,
2518
+ cwd,
2519
+ });
2520
+ }
2521
+ // ── Pilot report handler ──────────────────────────────────────────────────────
2522
+ // Aggregates existing local artifacts only: governance JSONL telemetry, provenance
2523
+ // index/legacy chain, pilot-metrics.json, last-verify-output — same sources as
2524
+ // `neurcode pilot-report` (no new telemetry systems).
2525
+ function pilotReportWindowDays() {
2526
+ return 7;
2527
+ }
2528
+ function pilotReportCutoffIso(days) {
2529
+ const d = new Date();
2530
+ d.setDate(d.getDate() - days);
2531
+ return d.toISOString().slice(0, 10);
2532
+ }
2533
+ function filterTelemetryByWindow(events, fromDate, toDate) {
2534
+ return events.filter((ev) => {
2535
+ const day = ev.emittedAt.slice(0, 10);
2536
+ return day >= fromDate && day <= toDate;
2537
+ });
2538
+ }
2539
+ function aggregateDeterministicFromTelemetry(events) {
2540
+ let sumDeterministic = 0;
2541
+ let sumFindings = 0;
2542
+ for (const ev of events) {
2543
+ if (ev.eventType !== 'governance.verify.completed')
2544
+ continue;
2545
+ const p = ev.payload;
2546
+ const hist = p.determinismHistogram;
2547
+ const count = p.governanceFindingCount;
2548
+ if (!hist || typeof count !== 'number')
2549
+ continue;
2550
+ sumFindings += count;
2551
+ sumDeterministic += hist['deterministic-structural'] ?? 0;
2552
+ }
2553
+ const deterministicPct = sumFindings > 0 ? sumDeterministic / sumFindings : 0;
2554
+ return { deterministicPct, sumDeterministic, sumFindings };
2555
+ }
2556
+ function governanceTrustFromPct(deterministicPct) {
2557
+ if (deterministicPct >= 0.7)
2558
+ return 'HIGH';
2559
+ if (deterministicPct >= 0.4)
2560
+ return 'MEDIUM';
2561
+ return 'LOW';
2562
+ }
2563
+ function advisoryCaughtInPilotWindow(cwd, from, to) {
2564
+ try {
2565
+ const p = path.join(cwd, '.neurcode', 'pilot-metrics.json');
2566
+ if (!fs.existsSync(p))
2567
+ return 0;
2568
+ const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
2569
+ if (!Array.isArray(raw))
2570
+ return 0;
2571
+ return raw
2572
+ .filter((e) => e.date >= from && e.date <= to)
2573
+ .reduce((s, e) => s + (e.advisoryCaught ?? 0), 0);
2574
+ }
2575
+ catch {
2576
+ return 0;
2577
+ }
2578
+ }
2579
+ function summarizeTelemetryLine(ev) {
2580
+ if (ev.eventType === 'governance.verify.completed') {
2581
+ const p = ev.payload;
2582
+ const verdict = String(p.verdict ?? '');
2583
+ const n = Number(p.governanceFindingCount ?? 0);
2584
+ const ri = p.replayIntegrityStatus;
2585
+ return {
2586
+ emittedAt: ev.emittedAt,
2587
+ eventType: ev.eventType,
2588
+ summary: `Verify ${verdict} · ${n} findings`,
2589
+ verdict,
2590
+ replayIntegrityStatus: ri ?? null,
2591
+ };
2592
+ }
2593
+ if (ev.eventType === 'rule.trigger') {
2594
+ const p = ev.payload;
2595
+ return {
2596
+ emittedAt: ev.emittedAt,
2597
+ eventType: ev.eventType,
2598
+ summary: `Rule ${p.ruleId ?? '?'} · ${p.count ?? 0}×`,
2599
+ };
2600
+ }
2601
+ return {
2602
+ emittedAt: ev.emittedAt,
2603
+ eventType: ev.eventType,
2604
+ summary: ev.eventType.replace(/\./g, ' '),
2605
+ };
2606
+ }
2607
+ async function handlePilotReport(_req, res) {
2608
+ const cwd = process.cwd();
2609
+ const days = pilotReportWindowDays();
2610
+ const periodTo = new Date().toISOString().slice(0, 10);
2611
+ const periodFrom = pilotReportCutoffIso(days);
2612
+ // Canonical governance telemetry (JSONL) — same reader as CLI pilot-report
2613
+ const telemetryEnvelopes = (0, telemetry_1.readGovernanceTelemetryEvents)(cwd);
2614
+ const telemetryWindow = filterTelemetryByWindow(telemetryEnvelopes, periodFrom, periodTo);
2615
+ const verifyCompletedInWindow = telemetryWindow.filter((e) => e.eventType === 'governance.verify.completed');
2616
+ // Legacy JSON telemetry (optional fallback counts for older repos)
2617
+ const telemetryPaths = [
2618
+ path.resolve(cwd, '.neurcode', 'telemetry.json'),
2619
+ path.resolve(cwd, '.neurcode', 'telemetry-events.json'),
2620
+ ];
2621
+ let legacyEvents = [];
2622
+ for (const tp of telemetryPaths) {
2623
+ try {
2624
+ if (fs.existsSync(tp)) {
2625
+ const raw = JSON.parse(fs.readFileSync(tp, 'utf8'));
2626
+ if (Array.isArray(raw))
2627
+ legacyEvents = raw;
2628
+ else if (raw && typeof raw === 'object') {
2629
+ const evts = raw.events;
2630
+ if (Array.isArray(evts))
2631
+ legacyEvents = evts;
2632
+ }
2633
+ break;
2634
+ }
2635
+ }
2636
+ catch { /* ignore */ }
2637
+ }
2638
+ const legacyVerifyEvents = legacyEvents.filter((e) => e.type === 'verify' || e.eventType === 'verify');
2639
+ // Provenance index (preferred) + legacy flat chain files
2640
+ const provIndex = (0, governance_provenance_1.loadProvenanceIndex)(cwd);
2641
+ const provenancePaths = [
2642
+ path.resolve(cwd, '.neurcode', 'provenance.json'),
2643
+ path.resolve(cwd, '.neurcode', 'provenance-chain.json'),
2644
+ ];
2645
+ let legacyRuns = [];
2646
+ for (const pp of provenancePaths) {
2647
+ try {
2648
+ if (fs.existsSync(pp)) {
2649
+ const raw = JSON.parse(fs.readFileSync(pp, 'utf8'));
2650
+ if (Array.isArray(raw))
2651
+ legacyRuns = raw;
2652
+ else if (raw && typeof raw === 'object') {
2653
+ const records = raw.records;
2654
+ if (Array.isArray(records))
2655
+ legacyRuns = records;
2656
+ }
2657
+ break;
2658
+ }
2659
+ }
2660
+ catch { /* ignore */ }
2661
+ }
2662
+ const indexRecords = [...provIndex.records].sort((a, b) => (a.runAt < b.runAt ? -1 : a.runAt > b.runAt ? 1 : 0));
2663
+ const runsForRates = indexRecords.length > 0
2664
+ ? indexRecords.map((r) => ({ verdict: r.verdict, verifiedAt: r.runAt, blockingCount: 0, fingerprint: r.fingerprint }))
2665
+ : legacyRuns.map((r) => ({
2666
+ verdict: r.verdict,
2667
+ verifiedAt: r.verifiedAt || r.timestamp,
2668
+ blockingCount: r.blockingCount || 0,
2669
+ fingerprint: r.fingerprint,
2670
+ }));
2671
+ const passRuns = runsForRates.filter((r) => String(r.verdict).toUpperCase() === 'PASS');
2672
+ const failRuns = runsForRates.filter((r) => {
2673
+ const v = String(r.verdict).toUpperCase();
2674
+ return v === 'FAIL' || v === 'BLOCK';
2675
+ });
2676
+ const warnRuns = runsForRates.filter((r) => String(r.verdict).toUpperCase() === 'WARN');
2677
+ const total = runsForRates.length;
2678
+ const passRate = total > 0 ? Math.round((passRuns.length / total) * 100) : 0;
2679
+ const failRate = total > 0 ? Math.round((failRuns.length / total) * 100) : 0;
2680
+ const warnRate = total > 0 ? Math.round((warnRuns.length / total) * 100) : 0;
2681
+ // Top rules: legacy deep records + telemetry rollup (merged)
2682
+ const globalRuleFreq = {};
2683
+ for (const run of legacyRuns) {
2684
+ const violations = run.violations;
2685
+ const findings = run.findings;
2686
+ const items = Array.isArray(violations) ? violations : Array.isArray(findings) ? findings : [];
2687
+ for (const item of items) {
2688
+ const ruleId = item.ruleId || 'unknown';
2689
+ globalRuleFreq[ruleId] = (globalRuleFreq[ruleId] || 0) + 1;
2690
+ }
2691
+ }
2692
+ const rollup = (0, telemetry_1.rollupRulePrecisionFromEvents)(telemetryWindow);
2693
+ const telemetryTop = [...(0, telemetry_1.highTrustRuleLeaderboard)(rollup, 10)];
2694
+ for (const r of telemetryTop) {
2695
+ globalRuleFreq[r.ruleId] = (globalRuleFreq[r.ruleId] || 0) + r.triggerCount;
2696
+ }
2697
+ const topRules = Object.entries(globalRuleFreq)
2698
+ .sort((a, b) => b[1] - a[1])
2699
+ .slice(0, 10)
2700
+ .map(([ruleId, count]) => ({ ruleId, count }));
2701
+ const topRulesTelemetry = telemetryTop.map((r) => ({ ruleId: r.ruleId, triggerCount: r.triggerCount }));
2702
+ // Last verify output for current state
2703
+ let currentVerdict = 'UNKNOWN';
2704
+ let lastVerifiedAt = null;
2705
+ let deterministicFindingCount = 0;
2706
+ const verifyPath = path.resolve(cwd, '.neurcode', 'last-verify-output.json');
2707
+ try {
2708
+ if (fs.existsSync(verifyPath)) {
2709
+ const vf = JSON.parse(fs.readFileSync(verifyPath, 'utf8'));
2710
+ currentVerdict = vf.verdict || 'UNKNOWN';
2711
+ lastVerifiedAt = vf.verifiedAt || vf.timestamp || null;
2712
+ const fArr = Array.isArray(vf.findings) ? vf.findings : [];
2713
+ deterministicFindingCount = fArr.filter((f) => {
2714
+ const det = f.determinism || '';
2715
+ return det.startsWith('deterministic');
2716
+ }).length;
2717
+ }
2718
+ }
2719
+ catch { /* ignore */ }
2720
+ // Trend: last 7 runs chronological (oldest → newest for charts)
2721
+ const trendSource = indexRecords.length > 0 ? indexRecords : legacyRuns;
2722
+ const recentSlice = trendSource.slice(-7);
2723
+ const recentTrend = recentSlice.map((r) => {
2724
+ if ('runAt' in r && typeof r.runAt === 'string') {
2725
+ const row = r;
2726
+ return {
2727
+ verifiedAt: row.runAt,
2728
+ verdict: row.verdict,
2729
+ blockingCount: 0,
2730
+ fingerprint: row.fingerprint,
2731
+ };
2732
+ }
2733
+ const row = r;
2734
+ return {
2735
+ verifiedAt: row.verifiedAt || row.timestamp,
2736
+ verdict: row.verdict,
2737
+ blockingCount: row.blockingCount || 0,
2738
+ fingerprint: row.fingerprint,
2739
+ };
2740
+ });
2741
+ const { deterministicPct, sumDeterministic, sumFindings } = aggregateDeterministicFromTelemetry(telemetryWindow);
2742
+ const deterministicRatioPct = Math.round(deterministicPct * 100);
2743
+ const governanceTrust = governanceTrustFromPct(deterministicPct);
2744
+ // Replay integrity distribution from verify.completed telemetry payloads
2745
+ let replayExact = 0;
2746
+ let replayBounded = 0;
2747
+ let replayUnknown = 0;
2748
+ let lastReplayIntegrityStatus = null;
2749
+ for (const ev of verifyCompletedInWindow) {
2750
+ const p = ev.payload;
2751
+ const ri = p.replayIntegrityStatus;
2752
+ if (ri === 'exact')
2753
+ replayExact += 1;
2754
+ else if (ri === 'bounded-degradation')
2755
+ replayBounded += 1;
2756
+ else
2757
+ replayUnknown += 1;
2758
+ if (!lastReplayIntegrityStatus && ri)
2759
+ lastReplayIntegrityStatus = ri;
2760
+ }
2761
+ for (let i = verifyCompletedInWindow.length - 1; i >= 0; i--) {
2762
+ const p = verifyCompletedInWindow[i].payload;
2763
+ const ri = p.replayIntegrityStatus;
2764
+ if (ri) {
2765
+ lastReplayIntegrityStatus = ri;
2766
+ break;
2767
+ }
2768
+ }
2769
+ // Provenance fingerprint integrity sample (last N index entries)
2770
+ const SAMPLE = 12;
2771
+ let sampleOk = 0;
2772
+ let sampleFail = 0;
2773
+ let sampleTried = 0;
2774
+ for (const entry of provIndex.records.slice(0, SAMPLE)) {
2775
+ const rec = (0, governance_provenance_1.loadProvenanceRecord)(cwd, entry.runId);
2776
+ if (!rec)
2777
+ continue;
2778
+ sampleTried += 1;
2779
+ if ((0, governance_provenance_1.verifyProvenanceIntegrity)(rec))
2780
+ sampleOk += 1;
2781
+ else
2782
+ sampleFail += 1;
2783
+ }
2784
+ const pilotSummary = (0, pilot_metrics_1.generatePilotSummary)(cwd, days);
2785
+ const fingerprintedInWindow = provIndex.records.filter((r) => {
2786
+ const d = r.runAt.slice(0, 10);
2787
+ return d >= periodFrom && d <= periodTo;
2788
+ }).length;
2789
+ const recentGovernanceEvents = telemetryEnvelopes
2790
+ .slice(-20)
2791
+ .reverse()
2792
+ .map(summarizeTelemetryLine);
2793
+ const operationalRisks = [];
2794
+ if (failRate >= 40) {
2795
+ operationalRisks.push({ level: 'high', label: 'Elevated failure rate', detail: `${failRate}% of recorded governance runs failed or blocked in the provenance window.` });
2796
+ }
2797
+ else if (failRate >= 15) {
2798
+ operationalRisks.push({ level: 'medium', label: 'Moderate failure rate', detail: `${failRate}% of runs failed or blocked — review top rules and intent scope.` });
2799
+ }
2800
+ if (pilotSummary.suppressionRate >= 0.25) {
2801
+ operationalRisks.push({
2802
+ level: 'medium',
2803
+ label: 'Suppression activity',
2804
+ detail: `Suppression rate ~${Math.round(pilotSummary.suppressionRate * 100)}% in pilot-metrics window — triage false positives vs policy.`,
2805
+ });
2806
+ }
2807
+ if (deterministicRatioPct < 40 && sumFindings > 0) {
2808
+ operationalRisks.push({
2809
+ level: 'medium',
2810
+ label: 'Deterministic signal mix',
2811
+ detail: `${deterministicRatioPct}% of findings in telemetry window are deterministic-structural — semantic/advisory dominates.`,
2812
+ });
2813
+ }
2814
+ if (replayBounded > replayExact && verifyCompletedInWindow.length >= 3) {
2815
+ operationalRisks.push({
2816
+ level: 'low',
2817
+ label: 'Replay bounded degradation',
2818
+ detail: 'Some verify completions reported bounded-degradation replay integrity — expected under truncation or large-repo limits.',
2819
+ });
2820
+ }
2821
+ if (operationalRisks.length === 0) {
2822
+ operationalRisks.push({
2823
+ level: 'low',
2824
+ label: 'No acute risk flags',
2825
+ detail: 'Within normal bands for this snapshot — keep running verify and monitoring pilot-metrics.',
2826
+ });
2827
+ }
2828
+ const verifyEventCount = verifyCompletedInWindow.length + legacyVerifyEvents.length;
2829
+ const telemetryEventCount = telemetryEnvelopes.length;
2830
+ success(res, {
2831
+ period: { days, from: periodFrom, to: periodTo },
2832
+ summary: {
2833
+ currentVerdict,
2834
+ lastVerifiedAt,
2835
+ totalRuns: total,
2836
+ passRate,
2837
+ failRate,
2838
+ warnRate,
2839
+ deterministicFindingCount,
2840
+ verifyEventCount,
2841
+ },
2842
+ deterministic: {
2843
+ ratioPct: deterministicRatioPct,
2844
+ sumDeterministic,
2845
+ sumFindings,
2846
+ windowDays: days,
2847
+ },
2848
+ governanceTrust,
2849
+ pilotSummary: {
2850
+ totalVerifyRuns: pilotSummary.totalVerifyRuns,
2851
+ averagePassRate: pilotSummary.averagePassRate,
2852
+ suppressionRate: pilotSummary.suppressionRate,
2853
+ totalBlockingCaught: pilotSummary.totalBlockingCaught,
2854
+ totalStructuralCaught: pilotSummary.totalStructuralCaught,
2855
+ advisoryCaught: advisoryCaughtInPilotWindow(cwd, periodFrom, periodTo),
2856
+ aiDebtTrend: pilotSummary.aiDebtTrend,
2857
+ topViolatedRules: pilotSummary.topViolatedRules,
2858
+ },
2859
+ provenanceCoverage: {
2860
+ indexedRuns: provIndex.records.length,
2861
+ fingerprintedInWindow,
2862
+ integritySampleVerified: sampleOk,
2863
+ integritySampleFailed: sampleFail,
2864
+ integritySampleTried: sampleTried,
2865
+ },
2866
+ replayIntegrity: {
2867
+ exact: replayExact,
2868
+ boundedDegradation: replayBounded,
2869
+ unknown: replayUnknown,
2870
+ lastStatus: lastReplayIntegrityStatus,
2871
+ },
2872
+ topRules,
2873
+ topRulesTelemetry,
2874
+ recentTrend,
2875
+ recentGovernanceEvents,
2876
+ operationalRisks,
2877
+ governance: {
2878
+ provenanceRecords: provIndex.records.length > 0 ? provIndex.records.length : legacyRuns.length,
2879
+ telemetryEvents: telemetryEventCount,
2880
+ legacyTelemetryEventCount: legacyEvents.length,
2881
+ cwd,
2882
+ },
2883
+ });
2884
+ }
1878
2885
  // ── Server factory ─────────────────────────────────────────────────────────────
1879
2886
  function createDaemonServer() {
1880
2887
  const server = http.createServer(async (req, res) => {
@@ -2014,6 +3021,12 @@ function createDaemonServer() {
2014
3021
  await handleGetWorkspaceRuntime(req, res, decodeURIComponent(runtimeMatch[1]));
2015
3022
  return;
2016
3023
  }
3024
+ // Cross-repo graph: GET /workspaces/:id/cross-repo-graph
3025
+ const crossRepoGraphMatch = url.match(/^\/workspaces\/([^/]+)\/cross-repo-graph(?:\?.*)?$/);
3026
+ if (crossRepoGraphMatch) {
3027
+ await handleGetCrossRepoGraph(req, res, decodeURIComponent(crossRepoGraphMatch[1]));
3028
+ return;
3029
+ }
2017
3030
  const detailMatch = url.match(/^\/workspaces\/([^/?]+)(?:\?.*)?$/);
2018
3031
  if (detailMatch) {
2019
3032
  await handleGetWorkspace(req, res, decodeURIComponent(detailMatch[1]));
@@ -2034,6 +3047,30 @@ function createDaemonServer() {
2034
3047
  await handleAddWorkspaceRepository(req, res, decodeURIComponent(addRepoMatch[1]));
2035
3048
  return;
2036
3049
  }
3050
+ // Federated context: POST /workspaces/:id/federated-context
3051
+ const federatedContextMatch = url.match(/^\/workspaces\/([^/]+)\/federated-context(?:\?.*)?$/);
3052
+ if (method === 'POST' && federatedContextMatch) {
3053
+ await handleGetFederatedContext(req, res, decodeURIComponent(federatedContextMatch[1]));
3054
+ return;
3055
+ }
3056
+ // Semantic search: POST /workspaces/:id/semantic-search
3057
+ const semanticSearchMatch = url.match(/^\/workspaces\/([^/]+)\/semantic-search(?:\?.*)?$/);
3058
+ if (method === 'POST' && semanticSearchMatch) {
3059
+ await handleSemanticSearch(req, res, decodeURIComponent(semanticSearchMatch[1]));
3060
+ return;
3061
+ }
3062
+ // Semantic index build: POST /workspaces/:id/semantic-index/build
3063
+ const semanticIndexBuildMatch = url.match(/^\/workspaces\/([^/]+)\/semantic-index\/build(?:\?.*)?$/);
3064
+ if (method === 'POST' && semanticIndexBuildMatch) {
3065
+ await handleBuildSemanticIndex(req, res, decodeURIComponent(semanticIndexBuildMatch[1]));
3066
+ return;
3067
+ }
3068
+ // Intent expansion: POST /workspaces/:id/intent-expand
3069
+ const intentExpandMatch = url.match(/^\/workspaces\/([^/]+)\/intent-expand(?:\?.*)?$/);
3070
+ if (method === 'POST' && intentExpandMatch) {
3071
+ await handleIntentExpand(req, res, decodeURIComponent(intentExpandMatch[1]));
3072
+ return;
3073
+ }
2037
3074
  const updateWorkspaceMatch = url.match(/^\/workspaces\/([^/?]+)(?:\?.*)?$/);
2038
3075
  if (method === 'PUT' && updateWorkspaceMatch) {
2039
3076
  await handleUpdateWorkspace(req, res, decodeURIComponent(updateWorkspaceMatch[1]));
@@ -2067,6 +3104,37 @@ function createDaemonServer() {
2067
3104
  return;
2068
3105
  }
2069
3106
  }
3107
+ // ── Governance findings & overview (new surfaces) ───────────────────────
3108
+ if (method === 'GET' && (url === '/governance/findings' || url.startsWith('/governance/findings?'))) {
3109
+ await handleGovernanceFindings(req, res);
3110
+ return;
3111
+ }
3112
+ if (method === 'GET' && (url === '/governance/overview' || url.startsWith('/governance/overview?'))) {
3113
+ await handleGovernanceOverview(req, res);
3114
+ return;
3115
+ }
3116
+ if (method === 'GET' && (url === '/brain/cache-status' || url.startsWith('/brain/cache-status?'))) {
3117
+ await handleBrainCacheStatus(req, res);
3118
+ return;
3119
+ }
3120
+ if (method === 'GET' && (url === '/remediation/status' || url.startsWith('/remediation/status?'))) {
3121
+ await handleRemediationStatus(req, res);
3122
+ return;
3123
+ }
3124
+ if (method === 'GET' && (url === '/pilot-report' || url.startsWith('/pilot-report?'))) {
3125
+ await handlePilotReport(req, res);
3126
+ return;
3127
+ }
3128
+ // ── Enterprise docs serving (renders from docs/enterprise/ on filesystem) ─
3129
+ if (method === 'GET' && url === '/docs/enterprise') {
3130
+ await handleDocsManifest(res);
3131
+ return;
3132
+ }
3133
+ const docsContentMatch = url.match(/^\/docs\/enterprise\/([^/?]+)(?:\?.*)?$/);
3134
+ if (method === 'GET' && docsContentMatch) {
3135
+ await handleDocsContent(res, decodeURIComponent(docsContentMatch[1]));
3136
+ return;
3137
+ }
2070
3138
  if (method === 'POST' && (url === '/execute' || url.startsWith('/execute?'))) {
2071
3139
  await handleExecute(req, res);
2072
3140
  return;
@@ -2204,10 +3272,20 @@ function startDaemon() {
2204
3272
  console.log(` POST /workspaces/:id/activate → set active workspace`);
2205
3273
  console.log(` POST /workspaces/:id/repositories → add repository to workspace`);
2206
3274
  console.log(` POST /workspaces/execute → workspace-scoped deterministic execution`);
3275
+ console.log(` GET /workspaces/:id/cross-repo-graph → detected cross-repo dependency edges`);
3276
+ console.log(` POST /workspaces/:id/federated-context → multi-repo blast radius analysis`);
3277
+ console.log(` POST /workspaces/:id/semantic-search → TF-IDF vector similarity file search`);
3278
+ console.log(` POST /workspaces/:id/semantic-index/build → rebuild semantic index from brain context`);
3279
+ console.log(` POST /workspaces/:id/intent-expand → signed semantic intent governance artifact`);
2207
3280
  console.log(` GET /replay/state → deterministic governance state replay`);
2208
3281
  console.log(` GET /replay/execution/:id → deterministic execution replay`);
2209
3282
  console.log(` GET /replay/workspace/:id → deterministic workspace replay`);
2210
3283
  console.log(` GET /replay/timeline → deterministic governance timeline replay`);
3284
+ console.log(` GET /governance/findings → canonical governance findings (last verify output)`);
3285
+ console.log(` GET /governance/overview → governance posture summary`);
3286
+ console.log(` GET /brain/cache-status → brain cache manifest and freshness`);
3287
+ console.log(` GET /remediation/status → remediation artifacts and receipts`);
3288
+ console.log(` GET /pilot-report → governance health metrics and trend`);
2211
3289
  console.log(`\n CWD: ${cwd}`);
2212
3290
  console.log(` Press Ctrl+C to stop.\n`);
2213
3291
  });