archgraph-argo 0.20.7 → 0.20.8
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.
|
@@ -154,65 +154,83 @@ function createDefaultSemanticRetrieval(dependencies = {}) {
|
|
|
154
154
|
return Object.freeze({
|
|
155
155
|
async retrieve(request = {}) {
|
|
156
156
|
const composition = await resolveRetrievalComposition(dependencies);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
canonicalGraph,
|
|
166
|
-
activeReadinessBoundary,
|
|
167
|
-
);
|
|
168
|
-
if (!evidence.alignment.aligned) {
|
|
169
|
-
await attemptAutomaticAlignment({
|
|
170
|
-
composition,
|
|
171
|
-
request,
|
|
172
|
-
alignment: evidence.alignment,
|
|
173
|
-
});
|
|
174
|
-
evidence = await readAndEvaluatePersistentReadiness(
|
|
157
|
+
try {
|
|
158
|
+
const activeTestComposition = testCompositionStorage.getStore();
|
|
159
|
+
const activeReadinessBoundary = activeTestComposition
|
|
160
|
+
&& activeTestComposition.useReadinessBoundary !== true
|
|
161
|
+
? undefined
|
|
162
|
+
: readinessBoundary;
|
|
163
|
+
let configurationEvidence = await composition.resolveConfiguration();
|
|
164
|
+
let evidence = await readAndEvaluatePersistentReadiness(
|
|
175
165
|
composition,
|
|
176
166
|
canonicalGraph,
|
|
177
167
|
activeReadinessBoundary,
|
|
178
168
|
);
|
|
179
169
|
if (!evidence.alignment.aligned) {
|
|
180
|
-
|
|
170
|
+
await attemptAutomaticAlignment({
|
|
171
|
+
composition,
|
|
172
|
+
request,
|
|
173
|
+
alignment: evidence.alignment,
|
|
174
|
+
});
|
|
175
|
+
evidence = await readAndEvaluatePersistentReadiness(
|
|
176
|
+
composition,
|
|
177
|
+
canonicalGraph,
|
|
178
|
+
activeReadinessBoundary,
|
|
179
|
+
);
|
|
180
|
+
if (!evidence.alignment.aligned) {
|
|
181
|
+
throw semanticAutomaticAlignmentFailed(evidence.alignment);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return await executeWpP2Retrieval({
|
|
185
|
+
composition,
|
|
186
|
+
request,
|
|
187
|
+
canonicalGraph,
|
|
188
|
+
readiness: evidence.readiness,
|
|
189
|
+
configurationEvidence,
|
|
190
|
+
});
|
|
191
|
+
} finally {
|
|
192
|
+
if (typeof composition.dispose === 'function') {
|
|
193
|
+
await composition.dispose();
|
|
181
194
|
}
|
|
182
195
|
}
|
|
183
|
-
return executeWpP2Retrieval({
|
|
184
|
-
composition,
|
|
185
|
-
request,
|
|
186
|
-
canonicalGraph,
|
|
187
|
-
readiness: evidence.readiness,
|
|
188
|
-
configurationEvidence,
|
|
189
|
-
});
|
|
190
196
|
},
|
|
191
197
|
async probeQueryability(request = {}, readiness = {}) {
|
|
192
198
|
const composition = await resolveRetrievalComposition(dependencies);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
try {
|
|
200
|
+
const configurationEvidence = await composition.resolveConfiguration();
|
|
201
|
+
return await executeWpP2Retrieval({
|
|
202
|
+
composition,
|
|
203
|
+
request,
|
|
204
|
+
canonicalGraph,
|
|
205
|
+
readiness,
|
|
206
|
+
configurationEvidence,
|
|
207
|
+
});
|
|
208
|
+
} finally {
|
|
209
|
+
if (typeof composition.dispose === 'function') {
|
|
210
|
+
await composition.dispose();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
201
213
|
},
|
|
202
214
|
async readReadiness() {
|
|
203
215
|
const composition = await resolveRetrievalComposition(dependencies);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
try {
|
|
217
|
+
const activeTestComposition = testCompositionStorage.getStore();
|
|
218
|
+
const activeReadinessBoundary = activeTestComposition
|
|
219
|
+
&& activeTestComposition.useReadinessBoundary !== true
|
|
220
|
+
? undefined
|
|
221
|
+
: readinessBoundary;
|
|
222
|
+
await composition.resolveConfiguration();
|
|
223
|
+
const evidence = await readAndEvaluatePersistentReadiness(
|
|
224
|
+
composition,
|
|
225
|
+
canonicalGraph,
|
|
226
|
+
activeReadinessBoundary,
|
|
227
|
+
);
|
|
228
|
+
return publicReadinessOutcome(evidence.alignment);
|
|
229
|
+
} finally {
|
|
230
|
+
if (typeof composition.dispose === 'function') {
|
|
231
|
+
await composition.dispose();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
216
234
|
},
|
|
217
235
|
});
|
|
218
236
|
}
|
|
@@ -333,6 +351,28 @@ async function createProductionComposition(dependencies) {
|
|
|
333
351
|
const repositoryRoot = dependencies.repositoryRoot
|
|
334
352
|
|| getWorkspaceRoot();
|
|
335
353
|
let configurationEvidence;
|
|
354
|
+
let neo4jHandle = null;
|
|
355
|
+
async function disposeNeo4j() {
|
|
356
|
+
const handle = neo4jHandle;
|
|
357
|
+
neo4jHandle = null;
|
|
358
|
+
if (!handle) return;
|
|
359
|
+
try { await handle.session.close(); } catch { /* ignore */ }
|
|
360
|
+
try { await handle.driver.close(); } catch { /* ignore */ }
|
|
361
|
+
}
|
|
362
|
+
function ensureNeo4jSession() {
|
|
363
|
+
if (neo4jHandle) return neo4jHandle.session;
|
|
364
|
+
const configuration = configurationEvidence.configuration;
|
|
365
|
+
const neo4j = require('neo4j-driver');
|
|
366
|
+
const driver = neo4j.driver(
|
|
367
|
+
configuration.neo4jDatabaseUrl,
|
|
368
|
+
neo4j.auth.basic(configuration.neo4jDatabaseUsername, configuration.neo4jDatabasePassword),
|
|
369
|
+
);
|
|
370
|
+
const session = driver.session(configuration.neo4jDatabase === undefined
|
|
371
|
+
? undefined
|
|
372
|
+
: { database: configuration.neo4jDatabase });
|
|
373
|
+
neo4jHandle = { driver, session };
|
|
374
|
+
return session;
|
|
375
|
+
}
|
|
336
376
|
return Object.freeze({
|
|
337
377
|
async resolveConfiguration() {
|
|
338
378
|
configurationEvidence = await resolveApprovedLiveConfiguration({
|
|
@@ -354,59 +394,61 @@ async function createProductionComposition(dependencies) {
|
|
|
354
394
|
if (!configurationEvidence) {
|
|
355
395
|
throw safeError('EXTERNAL_CREDENTIALS_REQUIRED');
|
|
356
396
|
}
|
|
357
|
-
|
|
397
|
+
if (operation && operation.kind === 'semantic-auto-alignment-attempt') {
|
|
398
|
+
return runScriptOwnedSemanticAlignment(operation);
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
return await runOperationOnSession(ensureNeo4jSession(), operation);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
// A failed run may leave the session unusable; drop the handle so the
|
|
404
|
+
// rest of this retrieval (or the next one) reconnects cleanly.
|
|
405
|
+
await disposeNeo4j();
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
358
408
|
},
|
|
359
409
|
}),
|
|
410
|
+
async dispose() {
|
|
411
|
+
await disposeNeo4j();
|
|
412
|
+
},
|
|
360
413
|
});
|
|
361
414
|
}
|
|
362
415
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
416
|
+
// Pure operation runner: runs one Cypher operation on a caller-owned Neo4j
|
|
417
|
+
// session and maps the result. The session/driver lifecycle lives in the
|
|
418
|
+
// production composition (ONE handle per retrieval, reused across every vector
|
|
419
|
+
// window, then closed) -- correctly reused without leaving a lingering handle,
|
|
420
|
+
// which would keep short-lived processes alive. Creating AND closing a driver
|
|
421
|
+
// per operation caused the rapid handle churn that intermittently aborted the
|
|
422
|
+
// MCP with a native libuv assertion on Windows (confirmed crash phase:
|
|
423
|
+
// retrieval:vector-window:ArchitectureRelationship).
|
|
424
|
+
async function runOperationOnSession(session, operation) {
|
|
425
|
+
const result = await session.run(operation.cypher, operation.parameters);
|
|
426
|
+
if (operation.kind === 'semantic-readiness-read') {
|
|
427
|
+
const readiness = result.records[0] && result.records[0].get('readiness');
|
|
428
|
+
return { records: readiness ? [readiness] : [] };
|
|
366
429
|
}
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
),
|
|
374
|
-
);
|
|
375
|
-
const session = driver.session(configuration.neo4jDatabase === undefined
|
|
376
|
-
? undefined
|
|
377
|
-
: { database: configuration.neo4jDatabase });
|
|
378
|
-
try {
|
|
379
|
-
const result = await session.run(operation.cypher, operation.parameters);
|
|
380
|
-
if (operation.kind === 'semantic-readiness-read') {
|
|
381
|
-
const readiness = result.records[0] && result.records[0].get('readiness');
|
|
382
|
-
return { records: readiness ? [readiness] : [] };
|
|
383
|
-
}
|
|
384
|
-
const records = result.records.map(record => ({
|
|
385
|
-
...record.get('record'),
|
|
386
|
-
score: numberValue(record.get('score')),
|
|
387
|
-
}));
|
|
388
|
-
if (operation.kind === 'semantic-lexical-query') {
|
|
389
|
-
return { records };
|
|
390
|
-
}
|
|
391
|
-
const offset = operation.parameters.offset;
|
|
392
|
-
const windowSize = operation.parameters.windowSize;
|
|
393
|
-
const returnedCount = Math.max(0, records.length - offset);
|
|
394
|
-
const hasMore = records.length === operation.parameters.topK;
|
|
395
|
-
return {
|
|
396
|
-
records,
|
|
397
|
-
windowEvidence: {
|
|
398
|
-
offset,
|
|
399
|
-
windowSize,
|
|
400
|
-
returnedCount,
|
|
401
|
-
hasMore,
|
|
402
|
-
nextOffset: hasMore ? operation.parameters.topK : null,
|
|
403
|
-
windowExhausted: !hasMore,
|
|
404
|
-
},
|
|
405
|
-
};
|
|
406
|
-
} finally {
|
|
407
|
-
await session.close();
|
|
408
|
-
await driver.close();
|
|
430
|
+
const records = result.records.map(record => ({
|
|
431
|
+
...record.get('record'),
|
|
432
|
+
score: numberValue(record.get('score')),
|
|
433
|
+
}));
|
|
434
|
+
if (operation.kind === 'semantic-lexical-query') {
|
|
435
|
+
return { records };
|
|
409
436
|
}
|
|
437
|
+
const offset = operation.parameters.offset;
|
|
438
|
+
const windowSize = operation.parameters.windowSize;
|
|
439
|
+
const returnedCount = Math.max(0, records.length - offset);
|
|
440
|
+
const hasMore = records.length === operation.parameters.topK;
|
|
441
|
+
return {
|
|
442
|
+
records,
|
|
443
|
+
windowEvidence: {
|
|
444
|
+
offset,
|
|
445
|
+
windowSize,
|
|
446
|
+
returnedCount,
|
|
447
|
+
hasMore,
|
|
448
|
+
nextOffset: hasMore ? operation.parameters.topK : null,
|
|
449
|
+
windowExhausted: !hasMore,
|
|
450
|
+
},
|
|
451
|
+
};
|
|
410
452
|
}
|
|
411
453
|
|
|
412
454
|
async function attemptAutomaticAlignment({ composition, request, alignment }) {
|
|
@@ -46,6 +46,18 @@ function isProjectInitialized(repositoryRoot) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// A readiness record proves the workspace was initialized (argo init) before.
|
|
50
|
+
// The preheat only RECONCILES a previously-initialized-but-now-stale workspace;
|
|
51
|
+
// a never-aligned workspace (brand-new project, or a synthetic test copy) must
|
|
52
|
+
// be initialized explicitly, never rebuilt in the background.
|
|
53
|
+
function hasReadinessRecord(repositoryRoot) {
|
|
54
|
+
try {
|
|
55
|
+
return fs.existsSync(readinessRecordPath(repositoryRoot));
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
function alignmentError() {
|
|
50
62
|
const error = new Error('SEMANTIC_AUTO_ALIGNMENT_FAILED');
|
|
51
63
|
error.category = 'SEMANTIC_AUTO_ALIGNMENT_FAILED';
|
|
@@ -57,7 +69,7 @@ function alignmentError() {
|
|
|
57
69
|
|
|
58
70
|
// Run the alignment (once). Returns a promise resolving to { status: 'aligned' }
|
|
59
71
|
// or rejecting with the SEMANTIC_AUTO_ALIGNMENT_FAILED envelope.
|
|
60
|
-
function runSemanticAlignment(repositoryRoot = getWorkspaceRoot()) {
|
|
72
|
+
function runSemanticAlignment(repositoryRoot = getWorkspaceRoot(), options = {}) {
|
|
61
73
|
if (inFlight) {
|
|
62
74
|
return inFlight;
|
|
63
75
|
}
|
|
@@ -70,6 +82,11 @@ function runSemanticAlignment(repositoryRoot = getWorkspaceRoot()) {
|
|
|
70
82
|
env: process.env,
|
|
71
83
|
stdio: ['ignore', 'ignore', 'inherit'],
|
|
72
84
|
});
|
|
85
|
+
if (options.unref && typeof child.unref === 'function') {
|
|
86
|
+
// Background preheat must NOT keep the MCP process alive (a spawnSync-driven
|
|
87
|
+
// caller waits for the server to exit; an attached child would hang it).
|
|
88
|
+
child.unref();
|
|
89
|
+
}
|
|
73
90
|
const finish = (failed, cause) => {
|
|
74
91
|
inFlight = null;
|
|
75
92
|
const ms = Date.now() - startedAt;
|
|
@@ -94,9 +111,14 @@ function preheatSemanticAlignment(repositoryRoot = getWorkspaceRoot()) {
|
|
|
94
111
|
if (preheated || !repositoryRoot || !isProjectInitialized(repositoryRoot) || isSemanticReady(repositoryRoot)) {
|
|
95
112
|
return;
|
|
96
113
|
}
|
|
114
|
+
// Only reconcile a workspace that was initialized before (a readiness record
|
|
115
|
+
// exists); never rebuild a never-aligned project in the background.
|
|
116
|
+
if (!hasReadinessRecord(repositoryRoot)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
97
119
|
preheated = true;
|
|
98
120
|
console.error('[argo] readiness not aligned at startup; preheating semantic alignment in background…');
|
|
99
|
-
runSemanticAlignment(repositoryRoot).catch(() => {});
|
|
121
|
+
runSemanticAlignment(repositoryRoot, { unref: true }).catch(() => {});
|
|
100
122
|
}
|
|
101
123
|
|
|
102
124
|
module.exports = {
|
|
@@ -104,5 +126,6 @@ module.exports = {
|
|
|
104
126
|
preheatSemanticAlignment,
|
|
105
127
|
isSemanticReady,
|
|
106
128
|
isProjectInitialized,
|
|
129
|
+
hasReadinessRecord,
|
|
107
130
|
readinessRecordPath,
|
|
108
131
|
};
|
package/package.json
CHANGED