@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.
- package/LICENSE +201 -0
- package/dist/commands/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +273 -0
- package/dist/commands/brain.js.map +1 -1
- package/dist/commands/pilot-report.d.ts +9 -0
- package/dist/commands/pilot-report.d.ts.map +1 -0
- package/dist/commands/pilot-report.js +176 -0
- package/dist/commands/pilot-report.js.map +1 -0
- package/dist/commands/remediate-governance.d.ts +54 -0
- package/dist/commands/remediate-governance.d.ts.map +1 -0
- package/dist/commands/remediate-governance.js +375 -0
- package/dist/commands/remediate-governance.js.map +1 -0
- package/dist/commands/remediate.d.ts.map +1 -1
- package/dist/commands/remediate.js.map +1 -1
- package/dist/commands/replay.d.ts.map +1 -1
- package/dist/commands/replay.js +30 -0
- package/dist/commands/replay.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +307 -24
- package/dist/commands/verify.js.map +1 -1
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +1078 -0
- package/dist/daemon/server.js.map +1 -1
- package/dist/explainability/DeterminismClassifier.d.ts +34 -0
- package/dist/explainability/DeterminismClassifier.d.ts.map +1 -0
- package/dist/explainability/DeterminismClassifier.js +104 -0
- package/dist/explainability/DeterminismClassifier.js.map +1 -0
- package/dist/explainability/ViolationFormatter.d.ts +32 -0
- package/dist/explainability/ViolationFormatter.d.ts.map +1 -0
- package/dist/explainability/ViolationFormatter.js +252 -0
- package/dist/explainability/ViolationFormatter.js.map +1 -0
- package/dist/explainability/index.d.ts +15 -0
- package/dist/explainability/index.d.ts.map +1 -0
- package/dist/explainability/index.js +94 -0
- package/dist/explainability/index.js.map +1 -0
- package/dist/explainability/types.d.ts +37 -0
- package/dist/explainability/types.d.ts.map +1 -0
- package/dist/explainability/types.js +3 -0
- package/dist/explainability/types.js.map +1 -0
- package/dist/governance/canonical-pipeline.d.ts +38 -0
- package/dist/governance/canonical-pipeline.d.ts.map +1 -0
- package/dist/governance/canonical-pipeline.js +448 -0
- package/dist/governance/canonical-pipeline.js.map +1 -0
- package/dist/governance/structural-on-diff.d.ts +13 -0
- package/dist/governance/structural-on-diff.d.ts.map +1 -0
- package/dist/governance/structural-on-diff.js +35 -0
- package/dist/governance/structural-on-diff.js.map +1 -0
- package/dist/governance/structural-policy-merge.d.ts +14 -0
- package/dist/governance/structural-policy-merge.d.ts.map +1 -0
- package/dist/governance/structural-policy-merge.js +25 -0
- package/dist/governance/structural-policy-merge.js.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/review-compression/index.d.ts +50 -0
- package/dist/integrations/review-compression/index.d.ts.map +1 -0
- package/dist/integrations/review-compression/index.js +158 -0
- package/dist/integrations/review-compression/index.js.map +1 -0
- package/dist/intent-engine/domain-taxonomy.d.ts +42 -0
- package/dist/intent-engine/domain-taxonomy.d.ts.map +1 -0
- package/dist/intent-engine/domain-taxonomy.js +534 -0
- package/dist/intent-engine/domain-taxonomy.js.map +1 -0
- package/dist/intent-engine/index.d.ts +1 -0
- package/dist/intent-engine/index.d.ts.map +1 -1
- package/dist/intent-engine/index.js +6 -1
- package/dist/intent-engine/index.js.map +1 -1
- package/dist/intent-engine/parser.d.ts.map +1 -1
- package/dist/intent-engine/parser.js +47 -0
- package/dist/intent-engine/parser.js.map +1 -1
- package/dist/intent-engine/semantic-expander.d.ts +104 -0
- package/dist/intent-engine/semantic-expander.d.ts.map +1 -0
- package/dist/intent-engine/semantic-expander.js +480 -0
- package/dist/intent-engine/semantic-expander.js.map +1 -0
- package/dist/patch-engine/patterns.d.ts.map +1 -1
- package/dist/patch-engine/patterns.js +8 -4
- package/dist/patch-engine/patterns.js.map +1 -1
- package/dist/semantic/index.d.ts +14 -0
- package/dist/semantic/index.d.ts.map +1 -0
- package/dist/semantic/index.js +30 -0
- package/dist/semantic/index.js.map +1 -0
- package/dist/semantic/tfidf-engine.d.ts +81 -0
- package/dist/semantic/tfidf-engine.d.ts.map +1 -0
- package/dist/semantic/tfidf-engine.js +278 -0
- package/dist/semantic/tfidf-engine.js.map +1 -0
- package/dist/semantic/vector-store.d.ts +108 -0
- package/dist/semantic/vector-store.d.ts.map +1 -0
- package/dist/semantic/vector-store.js +321 -0
- package/dist/semantic/vector-store.js.map +1 -0
- package/dist/structural-rules/context-severity.d.ts +46 -0
- package/dist/structural-rules/context-severity.d.ts.map +1 -0
- package/dist/structural-rules/context-severity.js +115 -0
- package/dist/structural-rules/context-severity.js.map +1 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.d.ts +11 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.d.ts.map +1 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.js +212 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.js.map +1 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.d.ts +11 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.d.ts.map +1 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.js +213 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.js.map +1 -0
- package/dist/structural-rules/distributed/index.d.ts +3 -0
- package/dist/structural-rules/distributed/index.d.ts.map +1 -0
- package/dist/structural-rules/distributed/index.js +8 -0
- package/dist/structural-rules/distributed/index.js.map +1 -0
- package/dist/structural-rules/engine.d.ts +25 -0
- package/dist/structural-rules/engine.d.ts.map +1 -0
- package/dist/structural-rules/engine.js +90 -0
- package/dist/structural-rules/engine.js.map +1 -0
- package/dist/structural-rules/index.d.ts +41 -0
- package/dist/structural-rules/index.d.ts.map +1 -0
- package/dist/structural-rules/index.js +141 -0
- package/dist/structural-rules/index.js.map +1 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.d.ts +11 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.d.ts.map +1 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.js +66 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.js.map +1 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.d.ts +11 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.d.ts.map +1 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.js +135 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.js.map +1 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.d.ts +11 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.d.ts.map +1 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.js +86 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.js.map +1 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.d.ts +11 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.d.ts.map +1 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.js +167 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.js.map +1 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.d.ts +11 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.d.ts.map +1 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.js +154 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.js.map +1 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.d.ts +11 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.d.ts.map +1 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.js +130 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.js.map +1 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.d.ts +11 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.d.ts.map +1 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.js +93 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.js.map +1 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.d.ts +11 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.d.ts.map +1 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.js +154 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.js.map +1 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.d.ts +11 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.d.ts.map +1 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.js +133 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.js.map +1 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.d.ts +11 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.d.ts.map +1 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.js +80 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.js.map +1 -0
- package/dist/structural-rules/rules/SR001-swallowed-async-rejection.d.ts +11 -0
- package/dist/structural-rules/rules/SR001-swallowed-async-rejection.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR001-swallowed-async-rejection.js +145 -0
- package/dist/structural-rules/rules/SR001-swallowed-async-rejection.js.map +1 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.d.ts +11 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.js +196 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.js.map +1 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.d.ts +11 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.js +148 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.js.map +1 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.d.ts +11 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.js +162 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.js.map +1 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.d.ts +11 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.js +150 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.js.map +1 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.d.ts +11 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.js +161 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.js.map +1 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.d.ts +11 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.js +175 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.js.map +1 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.d.ts +11 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.js +176 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.js.map +1 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.d.ts +11 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.js +168 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.js.map +1 -0
- package/dist/structural-rules/rules/SR010-retry-storm.d.ts +11 -0
- package/dist/structural-rules/rules/SR010-retry-storm.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR010-retry-storm.js +181 -0
- package/dist/structural-rules/rules/SR010-retry-storm.js.map +1 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.d.ts +11 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.js +208 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.js.map +1 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.d.ts +11 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.js +191 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.js.map +1 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.d.ts +11 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.js +219 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.js.map +1 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.d.ts +11 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.js +208 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.js.map +1 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.d.ts +11 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.js +190 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.js.map +1 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.d.ts +11 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.js +187 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.js.map +1 -0
- package/dist/structural-rules/suppressions.d.ts +43 -0
- package/dist/structural-rules/suppressions.d.ts.map +1 -0
- package/dist/structural-rules/suppressions.js +115 -0
- package/dist/structural-rules/suppressions.js.map +1 -0
- package/dist/structural-rules/types.d.ts +43 -0
- package/dist/structural-rules/types.d.ts.map +1 -0
- package/dist/structural-rules/types.js +3 -0
- package/dist/structural-rules/types.js.map +1 -0
- package/dist/utils/brain-cache.d.ts +100 -0
- package/dist/utils/brain-cache.d.ts.map +1 -0
- package/dist/utils/brain-cache.js +346 -0
- package/dist/utils/brain-cache.js.map +1 -0
- package/dist/utils/governance-provenance.d.ts +95 -0
- package/dist/utils/governance-provenance.d.ts.map +1 -0
- package/dist/utils/governance-provenance.js +187 -0
- package/dist/utils/governance-provenance.js.map +1 -0
- package/dist/utils/pilot-metrics.d.ts +46 -0
- package/dist/utils/pilot-metrics.d.ts.map +1 -0
- package/dist/utils/pilot-metrics.js +240 -0
- package/dist/utils/pilot-metrics.js.map +1 -0
- package/dist/utils/replay-runtime.d.ts +34 -0
- package/dist/utils/replay-runtime.d.ts.map +1 -1
- package/dist/utils/replay-runtime.js +207 -0
- package/dist/utils/replay-runtime.js.map +1 -1
- package/dist/workspace/cross-repo-graph.d.ts +111 -0
- package/dist/workspace/cross-repo-graph.d.ts.map +1 -0
- package/dist/workspace/cross-repo-graph.js +450 -0
- package/dist/workspace/cross-repo-graph.js.map +1 -0
- package/dist/workspace/federated-context.d.ts +144 -0
- package/dist/workspace/federated-context.d.ts.map +1 -0
- package/dist/workspace/federated-context.js +347 -0
- package/dist/workspace/federated-context.js.map +1 -0
- package/dist/workspace/index.d.ts +38 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +48 -0
- package/dist/workspace/index.js.map +1 -0
- package/package.json +9 -9
package/dist/daemon/server.js
CHANGED
|
@@ -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
|
});
|