@papyruslabsai/seshat-mcp 0.10.0 → 0.11.1
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/dist/index.js +20 -4
- package/dist/loader.d.ts +7 -0
- package/dist/loader.js +62 -26
- package/dist/supabase.d.ts +27 -1
- package/dist/supabase.js +19 -11
- package/dist/tools/diff.js +15 -49
- package/dist/tools/functors.js +3 -42
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
33
33
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
34
34
|
import { MultiLoader } from './loader.js';
|
|
35
35
|
import { bootstrap } from './bootstrap.js';
|
|
36
|
+
import { logTelemetry, isSupabaseConfigured } from './supabase.js';
|
|
36
37
|
import { initTools, queryEntities, getEntity, getDependencies, getDataFlow, findByConstraint, getBlastRadius, listModules, getTopology, } from './tools/index.js';
|
|
37
38
|
import { findDeadCode, findLayerViolations, getCouplingMetrics, getAuthMatrix, findErrorGaps, getTestCoverage, getOptimalContext, estimateTaskCost, reportActualBurn, find_runtime_violations, find_ownership_violations, query_traits, simulate_mutation, query_data_targets, find_exposure_leaks, find_semantic_clones, create_symbol, } from './tools/functors.js';
|
|
38
39
|
import { diffBundle, conflictMatrix, } from './tools/diff.js';
|
|
@@ -542,7 +543,7 @@ const TOOLS = [
|
|
|
542
543
|
},
|
|
543
544
|
{
|
|
544
545
|
name: 'conflict_matrix',
|
|
545
|
-
description: 'Given multiple tasks, classify every task pair into conflict tiers to determine parallelization safety. Tier 1 (different files, safe), Tier 2 (same file, different symbols, safe), Tier 3 (same symbol,
|
|
546
|
+
description: 'Given multiple tasks, classify every task pair into conflict tiers to determine parallelization safety. Tier 1 (different files, safe), Tier 2 (same file, different symbols, safe via surgical splicing), Tier 3 (same symbol, MUST sequence to maintain splice integrity). Returns a pairwise matrix and a suggested execution plan.',
|
|
546
547
|
inputSchema: {
|
|
547
548
|
type: 'object',
|
|
548
549
|
properties: {
|
|
@@ -564,7 +565,7 @@ const TOOLS = [
|
|
|
564
565
|
dimensions: {
|
|
565
566
|
type: 'array',
|
|
566
567
|
items: { type: 'string' },
|
|
567
|
-
description: 'Optional: Code scopes this task will modify (
|
|
568
|
+
description: 'Optional: Code scopes this task will modify. (Legacy support, no longer required for Tier 2 safety).',
|
|
568
569
|
},
|
|
569
570
|
expand_blast_radius: {
|
|
570
571
|
type: 'boolean',
|
|
@@ -647,6 +648,7 @@ async function main() {
|
|
|
647
648
|
// Handle tool calls
|
|
648
649
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
649
650
|
const { name, arguments: args } = request.params;
|
|
651
|
+
const startTime = Date.now();
|
|
650
652
|
try {
|
|
651
653
|
if (!loader.isLoaded()) {
|
|
652
654
|
return {
|
|
@@ -659,8 +661,7 @@ async function main() {
|
|
|
659
661
|
};
|
|
660
662
|
}
|
|
661
663
|
let result;
|
|
662
|
-
switch (name) {
|
|
663
|
-
// Meta
|
|
664
|
+
switch (name) { // Meta
|
|
664
665
|
case 'list_projects':
|
|
665
666
|
result = {
|
|
666
667
|
multiProject: isMulti,
|
|
@@ -759,6 +760,21 @@ async function main() {
|
|
|
759
760
|
default:
|
|
760
761
|
result = { error: `Unknown tool: ${name}` };
|
|
761
762
|
}
|
|
763
|
+
// Log telemetry for the tool execution
|
|
764
|
+
if (isSupabaseConfigured() && name !== 'list_projects') {
|
|
765
|
+
const executionMs = Date.now() - startTime;
|
|
766
|
+
const projectHash = args && typeof args === 'object' && 'project' in args
|
|
767
|
+
? String(args.project)
|
|
768
|
+
: loader.getProjectNames()[0] || 'unknown';
|
|
769
|
+
// Fire-and-forget telemetry log
|
|
770
|
+
logTelemetry({
|
|
771
|
+
tool_name: name,
|
|
772
|
+
project_hash: projectHash,
|
|
773
|
+
execution_ms: executionMs,
|
|
774
|
+
// user_id will be added by the Ptah API gateway if passing through there,
|
|
775
|
+
// or stay null if running strictly local CLI without an injected token.
|
|
776
|
+
}).catch(() => { });
|
|
777
|
+
}
|
|
762
778
|
return {
|
|
763
779
|
content: [{
|
|
764
780
|
type: 'text',
|
package/dist/loader.d.ts
CHANGED
|
@@ -11,7 +11,13 @@ export declare class BundleLoader {
|
|
|
11
11
|
private manifest;
|
|
12
12
|
private loaded;
|
|
13
13
|
private seshatDir;
|
|
14
|
+
private bundleMtime;
|
|
14
15
|
constructor(cwd?: string);
|
|
16
|
+
/**
|
|
17
|
+
* Check if the bundle file on disk has changed since last load.
|
|
18
|
+
* If so, reload transparently. Called by all accessors.
|
|
19
|
+
*/
|
|
20
|
+
private ensureFresh;
|
|
15
21
|
load(): void;
|
|
16
22
|
getEntities(): JstfEntity[];
|
|
17
23
|
getTopology(): Topology | null;
|
|
@@ -33,6 +39,7 @@ export declare class MultiLoader {
|
|
|
33
39
|
private dirs;
|
|
34
40
|
constructor(projectDirs: string[]);
|
|
35
41
|
load(): void;
|
|
42
|
+
private loadProject;
|
|
36
43
|
/** Get the resolved project name when only one project is loaded. */
|
|
37
44
|
private defaultProject;
|
|
38
45
|
private resolveProject;
|
package/dist/loader.js
CHANGED
|
@@ -12,10 +12,32 @@ export class BundleLoader {
|
|
|
12
12
|
manifest = null;
|
|
13
13
|
loaded = false;
|
|
14
14
|
seshatDir;
|
|
15
|
+
bundleMtime = 0;
|
|
15
16
|
constructor(cwd) {
|
|
16
17
|
const root = cwd || process.cwd();
|
|
17
18
|
this.seshatDir = path.join(root, '.seshat');
|
|
18
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if the bundle file on disk has changed since last load.
|
|
22
|
+
* If so, reload transparently. Called by all accessors.
|
|
23
|
+
*/
|
|
24
|
+
ensureFresh() {
|
|
25
|
+
if (!this.loaded) {
|
|
26
|
+
this.load();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const bundlePath = path.join(this.seshatDir, '_bundle.json');
|
|
30
|
+
try {
|
|
31
|
+
const currentMtime = fs.statSync(bundlePath).mtimeMs;
|
|
32
|
+
if (currentMtime !== this.bundleMtime) {
|
|
33
|
+
process.stderr.write(`[seshat] Bundle changed on disk, reloading…\n`);
|
|
34
|
+
this.load();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// File may have been deleted — keep stale data rather than crash
|
|
39
|
+
}
|
|
40
|
+
}
|
|
19
41
|
load() {
|
|
20
42
|
const bundlePath = path.join(this.seshatDir, '_bundle.json');
|
|
21
43
|
if (!fs.existsSync(bundlePath)) {
|
|
@@ -24,6 +46,7 @@ export class BundleLoader {
|
|
|
24
46
|
` node ci/extract-and-generate.mjs <repo-path> .seshat <project-name>\n` +
|
|
25
47
|
`Or add the CI workflow to auto-generate on push to main.`);
|
|
26
48
|
}
|
|
49
|
+
this.bundleMtime = fs.statSync(bundlePath).mtimeMs;
|
|
27
50
|
const raw = fs.readFileSync(bundlePath, 'utf-8');
|
|
28
51
|
const bundle = JSON.parse(raw);
|
|
29
52
|
this.entities = bundle.entities || [];
|
|
@@ -52,18 +75,15 @@ export class BundleLoader {
|
|
|
52
75
|
this.loaded = true;
|
|
53
76
|
}
|
|
54
77
|
getEntities() {
|
|
55
|
-
|
|
56
|
-
this.load();
|
|
78
|
+
this.ensureFresh();
|
|
57
79
|
return this.entities;
|
|
58
80
|
}
|
|
59
81
|
getTopology() {
|
|
60
|
-
|
|
61
|
-
this.load();
|
|
82
|
+
this.ensureFresh();
|
|
62
83
|
return this.topology;
|
|
63
84
|
}
|
|
64
85
|
getManifest() {
|
|
65
|
-
|
|
66
|
-
this.load();
|
|
86
|
+
this.ensureFresh();
|
|
67
87
|
return this.manifest;
|
|
68
88
|
}
|
|
69
89
|
getEntityById(id) {
|
|
@@ -96,26 +116,29 @@ export class MultiLoader {
|
|
|
96
116
|
}
|
|
97
117
|
load() {
|
|
98
118
|
for (const dir of this.dirs) {
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
loader.load();
|
|
102
|
-
const manifest = loader.getManifest();
|
|
103
|
-
const name = manifest?.projectName || path.basename(dir);
|
|
104
|
-
this.projects.set(name, loader);
|
|
105
|
-
this.projectPaths.set(name, dir);
|
|
106
|
-
// Stamp entities with _project
|
|
107
|
-
const entities = loader.getEntities();
|
|
108
|
-
for (const e of entities) {
|
|
109
|
-
e._project = name;
|
|
110
|
-
}
|
|
111
|
-
this.projectEntities.set(name, entities);
|
|
112
|
-
}
|
|
113
|
-
catch (err) {
|
|
114
|
-
process.stderr.write(`Warning: Skipping ${dir}: ${err.message}\n`);
|
|
115
|
-
}
|
|
119
|
+
this.loadProject(dir);
|
|
116
120
|
}
|
|
117
121
|
this.loaded = true;
|
|
118
122
|
}
|
|
123
|
+
loadProject(dir) {
|
|
124
|
+
const loader = new BundleLoader(dir);
|
|
125
|
+
try {
|
|
126
|
+
loader.load();
|
|
127
|
+
const manifest = loader.getManifest();
|
|
128
|
+
const name = manifest?.projectName || path.basename(dir);
|
|
129
|
+
this.projects.set(name, loader);
|
|
130
|
+
this.projectPaths.set(name, dir);
|
|
131
|
+
// Stamp entities with _project
|
|
132
|
+
const entities = loader.getEntities();
|
|
133
|
+
for (const e of entities) {
|
|
134
|
+
e._project = name;
|
|
135
|
+
}
|
|
136
|
+
this.projectEntities.set(name, entities);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
process.stderr.write(`Warning: Skipping ${dir}: ${err.message}\n`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
119
142
|
/** Get the resolved project name when only one project is loaded. */
|
|
120
143
|
defaultProject() {
|
|
121
144
|
if (this.projects.size === 1)
|
|
@@ -129,9 +152,22 @@ export class MultiLoader {
|
|
|
129
152
|
if (!this.loaded)
|
|
130
153
|
this.load();
|
|
131
154
|
const p = this.resolveProject(project);
|
|
132
|
-
if (p)
|
|
133
|
-
return
|
|
134
|
-
|
|
155
|
+
if (!p)
|
|
156
|
+
return [];
|
|
157
|
+
// Delegate to BundleLoader (which checks mtime via ensureFresh),
|
|
158
|
+
// then re-stamp and cache if the reference changed.
|
|
159
|
+
const loader = this.projects.get(p);
|
|
160
|
+
if (!loader)
|
|
161
|
+
return [];
|
|
162
|
+
const fresh = loader.getEntities();
|
|
163
|
+
const cached = this.projectEntities.get(p);
|
|
164
|
+
if (fresh !== cached) {
|
|
165
|
+
for (const e of fresh) {
|
|
166
|
+
e._project = p;
|
|
167
|
+
}
|
|
168
|
+
this.projectEntities.set(p, fresh);
|
|
169
|
+
}
|
|
170
|
+
return fresh;
|
|
135
171
|
}
|
|
136
172
|
getTopology(project) {
|
|
137
173
|
if (!this.loaded)
|
package/dist/supabase.d.ts
CHANGED
|
@@ -55,6 +55,31 @@ export interface TokenPredictionRow {
|
|
|
55
55
|
session_id: string | null;
|
|
56
56
|
notes: string | null;
|
|
57
57
|
}
|
|
58
|
+
export interface McpTelemetryLog {
|
|
59
|
+
user_id?: string;
|
|
60
|
+
tool_name: string;
|
|
61
|
+
project_hash: string;
|
|
62
|
+
execution_ms: number;
|
|
63
|
+
}
|
|
64
|
+
export interface JstfSnapshotInsert {
|
|
65
|
+
user_id?: string;
|
|
66
|
+
project_hash: string;
|
|
67
|
+
commit_sha: string;
|
|
68
|
+
language_primary: string;
|
|
69
|
+
total_entities: number;
|
|
70
|
+
metric_typeless_interfaces: number;
|
|
71
|
+
metric_max_in_degree: number;
|
|
72
|
+
metric_circular_dependencies: number;
|
|
73
|
+
metric_untracked_data_transformations: number;
|
|
74
|
+
metric_exposure_leaks: number;
|
|
75
|
+
metric_layer_violations: number;
|
|
76
|
+
metric_ownership_violations: number;
|
|
77
|
+
metric_impure_ratio: number;
|
|
78
|
+
metric_runtime_violations: number;
|
|
79
|
+
metric_semantic_clones: number;
|
|
80
|
+
metric_core_fragility_score: number;
|
|
81
|
+
bundle_payload: unknown;
|
|
82
|
+
}
|
|
58
83
|
/**
|
|
59
84
|
* Insert a prediction row. Returns the row ID or null on failure.
|
|
60
85
|
*/
|
|
@@ -70,4 +95,5 @@ export declare function abandonPrediction(predictionId: string): Promise<boolean
|
|
|
70
95
|
/**
|
|
71
96
|
* List recent predictions for a project (for calibration analysis).
|
|
72
97
|
*/
|
|
73
|
-
export declare function
|
|
98
|
+
export declare function logTelemetry(log: McpTelemetryLog): Promise<void>;
|
|
99
|
+
export declare function insertJstfSnapshot(snapshot: JstfSnapshotInsert): Promise<boolean>;
|
package/dist/supabase.js
CHANGED
|
@@ -18,7 +18,9 @@ export function isSupabaseConfigured() {
|
|
|
18
18
|
return SUPABASE_URL.length > 0 && SUPABASE_KEY.length > 0;
|
|
19
19
|
}
|
|
20
20
|
// ─── REST helpers ────────────────────────────────────────────────
|
|
21
|
-
const
|
|
21
|
+
const TABLE_PREDICTIONS = 'mcp_token_predictions';
|
|
22
|
+
const TABLE_TELEMETRY = 'mcp_telemetry_logs';
|
|
23
|
+
const TABLE_SNAPSHOTS = 'jstf_snapshots';
|
|
22
24
|
async function supabaseRequest(method, path, body, headers) {
|
|
23
25
|
if (!isSupabaseConfigured()) {
|
|
24
26
|
return { ok: false, status: 0, error: 'Supabase not configured' };
|
|
@@ -59,7 +61,7 @@ async function supabaseRequest(method, path, body, headers) {
|
|
|
59
61
|
* Insert a prediction row. Returns the row ID or null on failure.
|
|
60
62
|
*/
|
|
61
63
|
export async function insertPrediction(row) {
|
|
62
|
-
const result = await supabaseRequest('POST',
|
|
64
|
+
const result = await supabaseRequest('POST', TABLE_PREDICTIONS, row);
|
|
63
65
|
if (!result.ok) {
|
|
64
66
|
process.stderr.write(`[seshat] Prediction log failed: ${result.error}\n`);
|
|
65
67
|
return null;
|
|
@@ -72,7 +74,7 @@ export async function insertPrediction(row) {
|
|
|
72
74
|
*/
|
|
73
75
|
export async function updateActualBurn(predictionId, actual) {
|
|
74
76
|
// First fetch the prediction to compute drift
|
|
75
|
-
const fetchResult = await supabaseRequest('GET', `${
|
|
77
|
+
const fetchResult = await supabaseRequest('GET', `${TABLE_PREDICTIONS}?id=eq.${predictionId}&select=predicted_total,status`);
|
|
76
78
|
if (!fetchResult.ok) {
|
|
77
79
|
process.stderr.write(`[seshat] Fetch prediction failed: ${fetchResult.error}\n`);
|
|
78
80
|
return null;
|
|
@@ -98,7 +100,7 @@ export async function updateActualBurn(predictionId, actual) {
|
|
|
98
100
|
status: 'completed',
|
|
99
101
|
...(actual.notes ? { notes: actual.notes } : {}),
|
|
100
102
|
};
|
|
101
|
-
const updateResult = await supabaseRequest('PATCH', `${
|
|
103
|
+
const updateResult = await supabaseRequest('PATCH', `${TABLE_PREDICTIONS}?id=eq.${predictionId}`, updateBody);
|
|
102
104
|
if (!updateResult.ok) {
|
|
103
105
|
process.stderr.write(`[seshat] Update actual burn failed: ${updateResult.error}\n`);
|
|
104
106
|
return null;
|
|
@@ -109,16 +111,22 @@ export async function updateActualBurn(predictionId, actual) {
|
|
|
109
111
|
* Abandon a prediction (task was cancelled or not completed).
|
|
110
112
|
*/
|
|
111
113
|
export async function abandonPrediction(predictionId) {
|
|
112
|
-
const result = await supabaseRequest('PATCH', `${
|
|
114
|
+
const result = await supabaseRequest('PATCH', `${TABLE_PREDICTIONS}?id=eq.${predictionId}&status=eq.predicted`, { status: 'abandoned' });
|
|
113
115
|
return result.ok;
|
|
114
116
|
}
|
|
115
117
|
/**
|
|
116
118
|
* List recent predictions for a project (for calibration analysis).
|
|
117
119
|
*/
|
|
118
|
-
export async function
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
export async function logTelemetry(log) {
|
|
121
|
+
const result = await supabaseRequest('POST', TABLE_TELEMETRY, log);
|
|
122
|
+
if (!result.ok) {
|
|
123
|
+
process.stderr.write(`[seshat] Telemetry log failed: ${result.error}\n`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export async function insertJstfSnapshot(snapshot) {
|
|
127
|
+
const result = await supabaseRequest('POST', TABLE_SNAPSHOTS, snapshot);
|
|
128
|
+
if (!result.ok) {
|
|
129
|
+
process.stderr.write(`[seshat] JSTF snapshot failed: ${result.error}\n`);
|
|
130
|
+
}
|
|
131
|
+
return result.ok;
|
|
124
132
|
}
|
package/dist/tools/diff.js
CHANGED
|
@@ -261,25 +261,8 @@ export async function diffBundle(args) {
|
|
|
261
261
|
}
|
|
262
262
|
return result;
|
|
263
263
|
}
|
|
264
|
-
// ─── Conflict Tier Classification ─────────────────────────────────
|
|
265
|
-
const ZONE_1 = new Set(['edges', 'imports']); // Header / Dependencies
|
|
266
|
-
const ZONE_2 = new Set(['struct', 'constraints', 'traits', 'ownership']); // Signature / Modifiers
|
|
267
|
-
const ZONE_3 = new Set(['semantics', 'data', 'runtime']); // Logic / Implementation
|
|
268
|
-
function getZones(scopes) {
|
|
269
|
-
const zones = new Set();
|
|
270
|
-
for (const s of scopes) {
|
|
271
|
-
const lower = s.toLowerCase();
|
|
272
|
-
if (ZONE_1.has(lower))
|
|
273
|
-
zones.add(1);
|
|
274
|
-
if (ZONE_2.has(lower))
|
|
275
|
-
zones.add(2);
|
|
276
|
-
if (ZONE_3.has(lower))
|
|
277
|
-
zones.add(3);
|
|
278
|
-
}
|
|
279
|
-
return zones;
|
|
280
|
-
}
|
|
281
264
|
function classifyConflictTier(taskA, taskB) {
|
|
282
|
-
// Check symbol overlap
|
|
265
|
+
// Check symbol overlap → Tier 3 (MUST sequence)
|
|
283
266
|
const sharedEntities = [];
|
|
284
267
|
for (const id of taskA.entityIds) {
|
|
285
268
|
if (taskB.entityIds.has(id)) {
|
|
@@ -287,27 +270,9 @@ function classifyConflictTier(taskA, taskB) {
|
|
|
287
270
|
}
|
|
288
271
|
}
|
|
289
272
|
if (sharedEntities.length > 0) {
|
|
290
|
-
// Both touch same symbol. Check scopes to see if we can downgrade from Tier 4 (Sequential) to Tier 3 (Parallelizable Orthogonal)
|
|
291
|
-
if (taskA.dimensions.size > 0 && taskB.dimensions.size > 0) {
|
|
292
|
-
const zonesA = getZones(taskA.dimensions);
|
|
293
|
-
const zonesB = getZones(taskB.dimensions);
|
|
294
|
-
let spatialCollision = false;
|
|
295
|
-
for (const z of zonesA) {
|
|
296
|
-
if (zonesB.has(z))
|
|
297
|
-
spatialCollision = true;
|
|
298
|
-
}
|
|
299
|
-
if (!spatialCollision) {
|
|
300
|
-
return {
|
|
301
|
-
tier: 3,
|
|
302
|
-
reason: `${sharedEntities.length} shared symbols, but orthogonal change scopes (Tier 3) — safe to parallelize`,
|
|
303
|
-
sharedFiles: [],
|
|
304
|
-
sharedEntities,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
273
|
return {
|
|
309
|
-
tier:
|
|
310
|
-
reason: `${sharedEntities.length} shared symbols
|
|
274
|
+
tier: 3,
|
|
275
|
+
reason: `${sharedEntities.length} shared symbols — MUST sequence to maintain surgical splice integrity`,
|
|
311
276
|
sharedFiles: [],
|
|
312
277
|
sharedEntities,
|
|
313
278
|
};
|
|
@@ -327,26 +292,27 @@ function classifyConflictTier(taskA, taskB) {
|
|
|
327
292
|
sharedEntities: [],
|
|
328
293
|
};
|
|
329
294
|
}
|
|
295
|
+
// Same file, different symbols → Tier 2 (Safe via Surgical Splicer)
|
|
330
296
|
return {
|
|
331
297
|
tier: 2,
|
|
332
|
-
reason: `${sharedFiles.length} shared files but different symbols — safe to parallelize`,
|
|
298
|
+
reason: `${sharedFiles.length} shared files but different symbols — safe to parallelize via surgical splicing`,
|
|
333
299
|
sharedFiles,
|
|
334
300
|
sharedEntities: [],
|
|
335
301
|
};
|
|
336
302
|
}
|
|
337
303
|
// ─── Execution Plan Builder ───────────────────────────────────────
|
|
338
304
|
/**
|
|
339
|
-
* Build execution plan from tier-
|
|
340
|
-
* Tasks with no tier-
|
|
341
|
-
* Tasks in the same tier-
|
|
305
|
+
* Build execution plan from tier-3 conflict graph using connected components.
|
|
306
|
+
* Tasks with no tier-3 edges to each other run in parallel.
|
|
307
|
+
* Tasks in the same tier-3 component run sequentially.
|
|
342
308
|
*/
|
|
343
|
-
function buildExecutionPlan(taskIds,
|
|
344
|
-
// Build adjacency list for tier-
|
|
309
|
+
function buildExecutionPlan(taskIds, tier3Edges) {
|
|
310
|
+
// Build adjacency list for tier-3 conflicts
|
|
345
311
|
const adj = new Map();
|
|
346
312
|
for (const id of taskIds) {
|
|
347
313
|
adj.set(id, new Set());
|
|
348
314
|
}
|
|
349
|
-
for (const [a, b] of
|
|
315
|
+
for (const [a, b] of tier3Edges) {
|
|
350
316
|
adj.get(a)?.add(b);
|
|
351
317
|
adj.get(b)?.add(a);
|
|
352
318
|
}
|
|
@@ -454,7 +420,7 @@ export function conflictMatrix(args) {
|
|
|
454
420
|
}
|
|
455
421
|
// Pairwise comparison
|
|
456
422
|
const matrix = [];
|
|
457
|
-
const
|
|
423
|
+
const tier3Edges = [];
|
|
458
424
|
for (let i = 0; i < resolvedTasks.length; i++) {
|
|
459
425
|
for (let j = i + 1; j < resolvedTasks.length; j++) {
|
|
460
426
|
const taskA = resolvedTasks[i];
|
|
@@ -471,14 +437,14 @@ export function conflictMatrix(args) {
|
|
|
471
437
|
if (result.sharedEntities.length > 0)
|
|
472
438
|
entry.sharedEntities = result.sharedEntities;
|
|
473
439
|
matrix.push(entry);
|
|
474
|
-
if (result.tier ===
|
|
475
|
-
|
|
440
|
+
if (result.tier === 3) {
|
|
441
|
+
tier3Edges.push([taskA.id, taskB.id]);
|
|
476
442
|
}
|
|
477
443
|
}
|
|
478
444
|
}
|
|
479
445
|
// Build execution plan
|
|
480
446
|
const taskIds = resolvedTasks.map(t => t.id);
|
|
481
|
-
const executionPlan = buildExecutionPlan(taskIds,
|
|
447
|
+
const executionPlan = buildExecutionPlan(taskIds, tier3Edges);
|
|
482
448
|
const result = {
|
|
483
449
|
taskCount: tasks.length,
|
|
484
450
|
matrix,
|
package/dist/tools/functors.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import { computeBlastRadius } from '../graph.js';
|
|
10
10
|
import { getLoader, getGraph, validateProject, entityName, entityLayer, entitySummary, } from './index.js';
|
|
11
|
-
import { isSupabaseConfigured, insertPrediction, updateActualBurn, abandonPrediction,
|
|
11
|
+
import { isSupabaseConfigured, insertPrediction, updateActualBurn, abandonPrediction, } from '../supabase.js';
|
|
12
12
|
// ─── Layer ordering for violation detection ──────────────────────
|
|
13
13
|
const LAYER_ORDER = {
|
|
14
14
|
route: 0,
|
|
@@ -782,48 +782,9 @@ export async function reportActualBurn(args) {
|
|
|
782
782
|
const { action = 'complete' } = args;
|
|
783
783
|
// List mode: show recent predictions for calibration analysis
|
|
784
784
|
if (action === 'list') {
|
|
785
|
-
const rows = await listPredictions(args.project);
|
|
786
|
-
if (rows.length === 0) {
|
|
787
|
-
return { message: 'No predictions found.', predictions: [] };
|
|
788
|
-
}
|
|
789
|
-
const summary = rows.map((r) => ({
|
|
790
|
-
id: r.id,
|
|
791
|
-
project: r.project,
|
|
792
|
-
targets: r.target_entities,
|
|
793
|
-
predicted: r.predicted_total,
|
|
794
|
-
actual: r.actual_total_tokens,
|
|
795
|
-
drift: r.drift_ratio,
|
|
796
|
-
estimator: r.estimator_used,
|
|
797
|
-
status: r.status,
|
|
798
|
-
createdAt: r.created_at,
|
|
799
|
-
}));
|
|
800
|
-
// Compute aggregate calibration stats for completed predictions
|
|
801
|
-
const completed = rows.filter((r) => r.status === 'completed' && r.drift_ratio != null);
|
|
802
|
-
let calibration;
|
|
803
|
-
if (completed.length >= 3) {
|
|
804
|
-
const drifts = completed.map((r) => r.drift_ratio);
|
|
805
|
-
const meanDrift = drifts.reduce((a, b) => a + b, 0) / drifts.length;
|
|
806
|
-
const sortedDrifts = [...drifts].sort((a, b) => a - b);
|
|
807
|
-
const medianDrift = sortedDrifts[Math.floor(sortedDrifts.length / 2)];
|
|
808
|
-
const maxOvershoot = Math.max(...drifts);
|
|
809
|
-
const maxUndershoot = Math.min(...drifts);
|
|
810
|
-
calibration = {
|
|
811
|
-
completedSamples: completed.length,
|
|
812
|
-
meanDrift: Math.round(meanDrift * 1000) / 1000,
|
|
813
|
-
medianDrift: Math.round(medianDrift * 1000) / 1000,
|
|
814
|
-
maxOvershoot: Math.round(maxOvershoot * 1000) / 1000,
|
|
815
|
-
maxUndershoot: Math.round(maxUndershoot * 1000) / 1000,
|
|
816
|
-
_interpretation: meanDrift > 0.2
|
|
817
|
-
? 'Predictions underestimate — consider increasing iteration multiplier.'
|
|
818
|
-
: meanDrift < -0.2
|
|
819
|
-
? 'Predictions overestimate — consider decreasing iteration multiplier.'
|
|
820
|
-
: 'Predictions are well-calibrated (within 20% mean drift).',
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
785
|
return {
|
|
824
|
-
|
|
825
|
-
predictions:
|
|
826
|
-
...(calibration ? { calibration } : {}),
|
|
786
|
+
message: 'List predictions has been migrated to the Ptah Dashboard.',
|
|
787
|
+
predictions: []
|
|
827
788
|
};
|
|
828
789
|
}
|
|
829
790
|
// Complete or abandon requires prediction_id
|
package/package.json
CHANGED