archgraph-argo 0.25.0 → 0.26.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.
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Agent search diagnosis (framework, zero-dependency).
|
|
4
|
+
*
|
|
5
|
+
* Turns ONE agent session into a self-contained diagnostic bundle so the user
|
|
6
|
+
* can hand it back for analysis:
|
|
7
|
+
*
|
|
8
|
+
* <workspace>/.argo/temp/diagnosis/<session-id>/
|
|
9
|
+
* ├─ diagnosis.md human-readable summary (incl. heuristic hints)
|
|
10
|
+
* ├─ metrics.json the numbers behind the summary
|
|
11
|
+
* ├─ session.ndjson the raw host session (full tool input/output)
|
|
12
|
+
* ├─ cost-log.slice.ndjson the matching slice of the background cost log
|
|
13
|
+
* └─ manifest.json file list + sizes + versions
|
|
14
|
+
*
|
|
15
|
+
* Data sources (user's choice: both):
|
|
16
|
+
* - PRIMARY: the host session NDJSON (opencode export / run --format json),
|
|
17
|
+
* which carries every tool's full input/output + timestamps + tokens.
|
|
18
|
+
* - SUPPLEMENT: the framework cost log <workspace>/.argo/temp/agent-cost-log.ndjson.
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* node agentSearchDiagnose.js --session <session.ndjson> [--session-id ID]
|
|
22
|
+
* [--cost-log <path>] [--workspace <root>] [--out <dir>] [--json]
|
|
23
|
+
*
|
|
24
|
+
* Read-only except for writing the bundle dir. Never mutates the graph or repo.
|
|
25
|
+
*/
|
|
26
|
+
const fs = require('node:fs');
|
|
27
|
+
const path = require('node:path');
|
|
28
|
+
const crypto = require('node:crypto');
|
|
29
|
+
|
|
30
|
+
const BUNDLE_VERSION = 'temp2.2';
|
|
31
|
+
const SCHEMA_VERSION = 1;
|
|
32
|
+
const GRAPH_TOOLS = ['getSystemArchitecture', 'getIntentElementContext', 'getArchitectureViewContext', 'queryNeo4jGraph', 'memory_search'];
|
|
33
|
+
const GRAPH_WRITE_TOOLS = ['previewSystemArchitectureMutation', 'applySystemArchitectureMutation', 'addArchitectureElement', 'updateArchitectureElement', 'removeArchitectureElement', 'addArchitectureRelationship', 'updateArchitectureRelationship', 'removeArchitectureRelationship', 'addArchitectureView', 'updateArchitectureView', 'removeArchitectureView'];
|
|
34
|
+
const VALIDATOR_TOOLS = ['validateSystemArchitecture', 'runArchitectureTests', 'initializeWorkspace'];
|
|
35
|
+
const REPO_TOOLS = ['read', 'grep', 'glob', 'list', 'bash', 'webfetch', 'edit', 'write'];
|
|
36
|
+
|
|
37
|
+
function backendOf(tool) {
|
|
38
|
+
const n = String(tool || '');
|
|
39
|
+
const hit = (l) => l.some(t => n.includes(t));
|
|
40
|
+
if (hit(GRAPH_WRITE_TOOLS)) return 'graph';
|
|
41
|
+
if (hit(GRAPH_TOOLS)) return 'graph';
|
|
42
|
+
if (hit(VALIDATOR_TOOLS)) return 'framework';
|
|
43
|
+
if (hit(REPO_TOOLS)) return 'repo';
|
|
44
|
+
return 'other';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function classifyQuery(tool, input) {
|
|
48
|
+
const n = String(tool || '');
|
|
49
|
+
if (n.includes('question')) return 'human-wait';
|
|
50
|
+
if (n.includes('task')) return 'subagent';
|
|
51
|
+
if (n.includes('bash')) return 'bash';
|
|
52
|
+
if (n.includes('getSystemArchitecture') || n.includes('memory_search')) return 'semantic';
|
|
53
|
+
if (n.includes('queryNeo4jGraph')) return 'structured-cypher';
|
|
54
|
+
if (n.includes('getIntentElementContext') || n.includes('getArchitectureViewContext')) return 'structured-context';
|
|
55
|
+
if (n.includes('validateSystemArchitecture') || n.includes('runArchitectureTests')) return 'validate';
|
|
56
|
+
if (n.includes('initializeWorkspace')) return 'init';
|
|
57
|
+
if (n.includes('previewSystemArchitectureMutation') || GRAPH_WRITE_TOOLS.some(t => n.includes(t))) return 'mutation';
|
|
58
|
+
if (n.includes('read')) return 'file-read';
|
|
59
|
+
if (n.includes('grep')) return 'grep';
|
|
60
|
+
if (n.includes('glob') || n.includes('list')) return 'list';
|
|
61
|
+
if (n.includes('edit') || n.includes('write')) return 'edit';
|
|
62
|
+
if (n.includes('todowrite')) return 'todo';
|
|
63
|
+
return 'other';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Tools that block on a HUMAN (time spent waiting for the person, not the agent).
|
|
67
|
+
const HUMAN_WAIT_TOOLS = ['question'];
|
|
68
|
+
const CONTEXT_BLOWUP_TOKENS = 100000;
|
|
69
|
+
|
|
70
|
+
function estimateTokens(text) {
|
|
71
|
+
if (!text) return 0;
|
|
72
|
+
const s = String(text);
|
|
73
|
+
const cjk = (s.match(/[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g) || []).length;
|
|
74
|
+
return cjk + Math.ceil((s.length - cjk) / 4);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function stableStringify(value) {
|
|
78
|
+
if (value === null || typeof value !== 'object') return JSON.stringify(value);
|
|
79
|
+
if (Array.isArray(value)) return '[' + value.map(stableStringify).join(',') + ']';
|
|
80
|
+
return '{' + Object.keys(value).sort().map(k => JSON.stringify(k) + ':' + stableStringify(value[k])).join(',') + '}';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function signature(tool, input) {
|
|
84
|
+
try { return crypto.createHash('sha1').update(String(tool) + '|' + stableStringify(input || {})).digest('hex').slice(0, 12); }
|
|
85
|
+
catch (_) { return 'unknown'; }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function inputPreview(tool, input) {
|
|
89
|
+
if (!input || typeof input !== 'object') return '';
|
|
90
|
+
const pick = input.query && (input.query.intent || input.query) || input.pattern || input.elementId || input.elementName || input.view_id || input.path || input.filePath || input.cypher || input.intent;
|
|
91
|
+
const s = typeof pick === 'string' ? pick : (pick ? JSON.stringify(pick) : JSON.stringify(input));
|
|
92
|
+
return String(s).slice(0, 160);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function eventTime(e) {
|
|
96
|
+
const cands = [e.time, e.part && e.part.time, e.part && e.part.state && e.part.state.time];
|
|
97
|
+
for (const t of cands) {
|
|
98
|
+
if (!t) continue;
|
|
99
|
+
if (typeof t === 'number') return t;
|
|
100
|
+
if (typeof t.start === 'number' && typeof t.end === 'number') return t.end;
|
|
101
|
+
if (typeof t.start === 'number') return t.start;
|
|
102
|
+
if (typeof t.end === 'number') return t.end;
|
|
103
|
+
}
|
|
104
|
+
return typeof e.timestamp === 'number' ? e.timestamp : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isExportJson(text) {
|
|
108
|
+
const t = String(text || '').trim();
|
|
109
|
+
if (!t.startsWith('{')) return false;
|
|
110
|
+
try { const j = JSON.parse(t); return !!(j && Array.isArray(j.messages)); } catch (_) { return false; }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Accept BOTH the live event stream (opencode run --format json, NDJSON) and the
|
|
114
|
+
// session export JSON (opencode export -> { info, messages:[{info, parts:[...]}] }).
|
|
115
|
+
function exportToNdjson(text) {
|
|
116
|
+
const j = JSON.parse(text);
|
|
117
|
+
const ev = [];
|
|
118
|
+
for (const m of j.messages || []) {
|
|
119
|
+
const info = m.info || {};
|
|
120
|
+
const ts = info.time && (info.time.created || info.time.completed);
|
|
121
|
+
for (const p of m.parts || []) {
|
|
122
|
+
if (!p || typeof p !== 'object') continue;
|
|
123
|
+
if (p.type === 'tool') ev.push({ type: 'tool', timestamp: (p.state && p.state.time && p.state.time.end) || ts, part: p });
|
|
124
|
+
else if (p.type === 'step-start') ev.push({ type: 'step_start', timestamp: ts, part: p });
|
|
125
|
+
else if (p.type === 'text') ev.push({ type: 'text', timestamp: ts, part: p });
|
|
126
|
+
}
|
|
127
|
+
if (info.role === 'assistant' && info.tokens) ev.push({ type: 'step_finish', timestamp: info.time && info.time.completed, part: { tokens: info.tokens, cost: info.cost } });
|
|
128
|
+
}
|
|
129
|
+
return ev.map(e => JSON.stringify(e)).join('\n') + '\n';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function parseSession(text) {
|
|
133
|
+
const raw = String(text || '');
|
|
134
|
+
const toolCalls = [];
|
|
135
|
+
const usages = [];
|
|
136
|
+
let steps = 0; let tMin = null; let tMax = null; let cost = 0;
|
|
137
|
+
const texts = [];
|
|
138
|
+
for (const line of raw.split('\n')) {
|
|
139
|
+
const t = line.trim();
|
|
140
|
+
if (!t.startsWith('{')) continue;
|
|
141
|
+
let e; try { e = JSON.parse(t); } catch (_) { continue; }
|
|
142
|
+
const part = e.part || e;
|
|
143
|
+
if (part && part.type === 'text' && part.text) texts.push(part.text);
|
|
144
|
+
if (part && part.type === 'tool') {
|
|
145
|
+
const tool = part.tool || part.name || (part.state && part.state.tool) || 'tool';
|
|
146
|
+
const st = part.state || {};
|
|
147
|
+
const tm = st.time || {};
|
|
148
|
+
const out = st.output != null ? String(st.output) : '';
|
|
149
|
+
toolCalls.push({
|
|
150
|
+
tool, backend: backendOf(tool), queryClass: classifyQuery(tool, st.input),
|
|
151
|
+
durationMs: (typeof tm.start === 'number' && typeof tm.end === 'number') ? Math.max(0, tm.end - tm.start) : null,
|
|
152
|
+
ok: st.status ? st.status !== 'error' : true,
|
|
153
|
+
error: st.error ? String(st.error).slice(0, 200) : null,
|
|
154
|
+
signature: signature(tool, st.input), inputPreview: inputPreview(tool, st.input),
|
|
155
|
+
outputBytes: out.length, outputTokens: estimateTokens(out),
|
|
156
|
+
path: (st.input && (st.input.filePath || st.input.path || st.input.file)) || null,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (e.type === 'step_start') steps += 1;
|
|
160
|
+
if (e.type === 'step_finish') {
|
|
161
|
+
const fin = (e.part && e.part.finish) ? e.part.finish : (e.part || e);
|
|
162
|
+
if (fin && typeof fin.cost === 'number') cost += fin.cost;
|
|
163
|
+
const tk = fin && (fin.tokens || (fin.finish && fin.finish.tokens));
|
|
164
|
+
if (tk) usages.push({ input: tk.input || 0, output: tk.output || 0, reasoning: tk.reasoning || 0, cacheRead: (tk.cache && tk.cache.read) || 0, cacheWrite: (tk.cache && tk.cache.write) || 0 });
|
|
165
|
+
}
|
|
166
|
+
const time = eventTime(e);
|
|
167
|
+
if (time !== null) { tMin = tMin === null ? time : Math.min(tMin, time); tMax = tMax === null ? time : Math.max(tMax, time); }
|
|
168
|
+
}
|
|
169
|
+
const tokensIn = usages.reduce((a, u) => a + u.input, 0);
|
|
170
|
+
const tokensOut = usages.reduce((a, u) => a + u.output, 0);
|
|
171
|
+
const tokensReasoning = usages.reduce((a, u) => a + u.reasoning, 0);
|
|
172
|
+
return { toolCalls, usages, texts, steps, cost, wallMs: (tMin !== null && tMax !== null && tMax >= tMin) ? tMax - tMin : null, tokensIn, tokensOut, tokensReasoning, tokens: tokensIn + tokensOut + tokensReasoning };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function countRoundTrips(toolCalls) {
|
|
176
|
+
const seq = toolCalls.map(c => c.backend).filter(b => b === 'graph' || b === 'repo');
|
|
177
|
+
let n = 0;
|
|
178
|
+
for (let i = 1; i < seq.length; i++) if (seq[i] !== seq[i - 1]) n += 1;
|
|
179
|
+
return n;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function diagnose(session, opts = {}) {
|
|
183
|
+
const tc = session.toolCalls;
|
|
184
|
+
const byTool = {};
|
|
185
|
+
const byBackend = { graph: { calls: 0, ms: 0 }, repo: { calls: 0, ms: 0 }, framework: { calls: 0, ms: 0 }, other: { calls: 0, ms: 0 } };
|
|
186
|
+
const byQueryClass = {};
|
|
187
|
+
const sigCount = {};
|
|
188
|
+
const pathReads = {};
|
|
189
|
+
let toolMs = 0; let humanWaitMs = 0; let errors = 0; let empty = 0;
|
|
190
|
+
for (const c of tc) {
|
|
191
|
+
const isHuman = c.queryClass === 'human-wait' || HUMAN_WAIT_TOOLS.some(t => String(c.tool).includes(t));
|
|
192
|
+
const bt = byTool[c.tool] || (byTool[c.tool] = { calls: 0, ms: 0, tokens: 0, errors: 0 });
|
|
193
|
+
bt.calls += 1; bt.ms += c.durationMs || 0; bt.tokens += c.outputTokens || 0;
|
|
194
|
+
if (isHuman) {
|
|
195
|
+
humanWaitMs += c.durationMs || 0; // human time is NOT agent/tool work
|
|
196
|
+
} else {
|
|
197
|
+
const b = byBackend[c.backend] || (byBackend[c.backend] = { calls: 0, ms: 0 });
|
|
198
|
+
b.calls += 1; b.ms += c.durationMs || 0;
|
|
199
|
+
toolMs += c.durationMs || 0;
|
|
200
|
+
}
|
|
201
|
+
if (c.ok === false) { bt.errors += 1; errors += 1; }
|
|
202
|
+
if ((c.outputBytes || 0) < 2) empty += 1;
|
|
203
|
+
byQueryClass[c.queryClass] = (byQueryClass[c.queryClass] || 0) + 1;
|
|
204
|
+
sigCount[c.signature] = (sigCount[c.signature] || 0) + 1;
|
|
205
|
+
if (c.queryClass === 'file-read' && c.path) pathReads[c.path] = (pathReads[c.path] || 0) + 1;
|
|
206
|
+
}
|
|
207
|
+
const duplicates = [];
|
|
208
|
+
const seen = new Set();
|
|
209
|
+
for (const c of tc) {
|
|
210
|
+
if (seen.has(c.signature)) continue;
|
|
211
|
+
seen.add(c.signature);
|
|
212
|
+
if (sigCount[c.signature] > 1) duplicates.push({ tool: c.tool, queryClass: c.queryClass, count: sigCount[c.signature], preview: c.inputPreview });
|
|
213
|
+
}
|
|
214
|
+
const repeatedReads = Object.entries(pathReads).filter(([, n]) => n > 1).map(([p, n]) => ({ path: p, count: n }));
|
|
215
|
+
// longest no-progress streak: consecutive calls that were empty/failed/duplicate
|
|
216
|
+
let streak = 0; let best = 0;
|
|
217
|
+
for (const c of tc) {
|
|
218
|
+
const bad = c.ok === false || (c.outputBytes || 0) < 2 || sigCount[c.signature] > 1;
|
|
219
|
+
streak = bad ? streak + 1 : 0;
|
|
220
|
+
if (streak > best) best = streak;
|
|
221
|
+
}
|
|
222
|
+
const cumulativeInput = [];
|
|
223
|
+
let acc = 0;
|
|
224
|
+
for (const u of session.usages) { acc += u.input; cumulativeInput.push(u.input); }
|
|
225
|
+
const topByMs = [...tc].sort((a, b) => (b.durationMs || 0) - (a.durationMs || 0)).slice(0, 5).map(c => ({ tool: c.tool, ms: c.durationMs, ok: c.ok, preview: c.inputPreview }));
|
|
226
|
+
const topByTokens = [...tc].sort((a, b) => (b.outputTokens || 0) - (a.outputTokens || 0)).slice(0, 5).map(c => ({ tool: c.tool, tokens: c.outputTokens, preview: c.inputPreview }));
|
|
227
|
+
|
|
228
|
+
const wallMs = opts.wallMs != null ? opts.wallMs : session.wallMs;
|
|
229
|
+
const modelMs = wallMs != null ? Math.max(0, wallMs - toolMs - humanWaitMs) : null;
|
|
230
|
+
const blowups = cumulativeInput.map((v, i) => ({ step: i, input: v })).filter(x => x.input >= CONTEXT_BLOWUP_TOKENS).sort((a, b) => b.input - a.input);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
schemaVersion: SCHEMA_VERSION, bundleVersion: BUNDLE_VERSION, generatedAt: new Date().toISOString(),
|
|
234
|
+
workspace: opts.workspace || null, sessionId: opts.sessionId || null,
|
|
235
|
+
overview: {
|
|
236
|
+
steps: session.steps, toolCalls: tc.length, roundTrips: countRoundTrips(tc),
|
|
237
|
+
wallMs, modelMs, mcpMs: byBackend.graph.ms, repoToolMs: byBackend.repo.ms, toolMs, humanWaitMs,
|
|
238
|
+
tokensIn: session.tokensIn, tokensOut: session.tokensOut, tokensReasoning: session.tokensReasoning, tokens: session.tokens,
|
|
239
|
+
cost: session.cost, toolErrors: errors, emptyResults: empty,
|
|
240
|
+
distinctSignatures: new Set(tc.map(c => c.signature)).size,
|
|
241
|
+
contextBlowup: { thresholdTokens: CONTEXT_BLOWUP_TOKENS, count: blowups.length, max: blowups.length ? blowups[0].input : 0, top: blowups.slice(0, 5) },
|
|
242
|
+
},
|
|
243
|
+
byTool, byBackend, byQueryClass,
|
|
244
|
+
tokenGrowth: { perStepInput: cumulativeInput, steps: cumulativeInput.length },
|
|
245
|
+
overSearch: {
|
|
246
|
+
duplicateCalls: duplicates,
|
|
247
|
+
repeatedReads,
|
|
248
|
+
emptyOrError: errors + empty,
|
|
249
|
+
noProgressStreak: best,
|
|
250
|
+
},
|
|
251
|
+
topOffendersByTime: topByMs,
|
|
252
|
+
topOffendersByTokens: topByTokens,
|
|
253
|
+
hints: buildHints({ toolCalls: tc.length, duplicates: duplicates.length, emptyOrError: errors + empty, repeatedReads: repeatedReads.length, noProgressStreak: best, modelMs, mcpMs: byBackend.graph.ms, repoToolMs: byBackend.repo.ms, roundTrips: countRoundTrips(tc), blowups: blowups.length, blowupMax: blowups.length ? blowups[0].input : 0 }),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function buildHints(m) {
|
|
258
|
+
const hints = [];
|
|
259
|
+
if (m.blowups > 0) hints.push(`上下文尖峰 ${m.blowups} 个 step(最大 ${m.blowupMax} tokens)→ 多由大工具输出造成;优先考虑截断/分页/摘要工具输出(observation masking)、先摘要后精读(对所有场景统一生效,勿只针对单会话)。`);
|
|
260
|
+
if (m.duplicates > 0) hints.push(`重复/近似重复调用 ${m.duplicates} 组 → 考虑结果缓存或查询归一(同参不重搜)。`);
|
|
261
|
+
if (m.emptyOrError > 0) hints.push(`空结果/失败 ${m.emptyOrError} 次 → 考虑改进查询构造/回退策略,避免"空手→换词→再搜"的循环。`);
|
|
262
|
+
if (m.repeatedReads > 0) hints.push(`同一文件被重复读 ${m.repeatedReads} 处 → 考虑读取缓存或先摘要后精读。`);
|
|
263
|
+
if (m.noProgressStreak >= 3) hints.push(`最长"无进展"连续 ${m.noProgressStreak} 次 → 缺停止判据/预算,建议显式设停止条件。`);
|
|
264
|
+
if (m.roundTrips >= 2) hints.push(`图↔仓往返 ${m.roundTrips} 次 → 检查是否可在一次规划内并发取数。`);
|
|
265
|
+
if (m.modelMs != null && m.modelMs > (m.mcpMs + m.repoToolMs)) hints.push(`模型耗时主导(${m.modelMs}ms > 工具 ${(m.mcpMs + m.repoToolMs)}ms)→ 考虑上下文压缩/减少轮次。`);
|
|
266
|
+
if (hints.length === 0) hints.push('未发现明显过度搜索信号。');
|
|
267
|
+
return hints;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function renderDiagnosis(m, meta) {
|
|
271
|
+
const L = [];
|
|
272
|
+
L.push(`# Agent 搜索诊断报告`);
|
|
273
|
+
L.push('');
|
|
274
|
+
L.push(`- 生成时间:${m.generatedAt}`);
|
|
275
|
+
L.push(`- 会话:${m.sessionId || '(unknown)'} 工作区:${m.workspace || '(unknown)'}`);
|
|
276
|
+
L.push(`- bundle 版本:${m.bundleVersion} schema:${m.schemaVersion}`);
|
|
277
|
+
L.push('');
|
|
278
|
+
L.push(`## 概览`);
|
|
279
|
+
L.push('');
|
|
280
|
+
L.push(`| 指标 | 值 |`);
|
|
281
|
+
L.push(`|---|---|`);
|
|
282
|
+
L.push(`| 轮次 steps | ${m.overview.steps} |`);
|
|
283
|
+
L.push(`| 工具调用 | ${m.overview.toolCalls}(去重签名 ${m.overview.distinctSignatures}) |`);
|
|
284
|
+
L.push(`| 图↔仓往返 | ${m.overview.roundTrips} |`);
|
|
285
|
+
L.push(`| 墙钟 | ${fmtMs(m.overview.wallMs)} = 模型 ${fmtMs(m.overview.modelMs)} + MCP ${fmtMs(m.overview.mcpMs)} + 仓 ${fmtMs(m.overview.repoToolMs)} + 人类等待 ${fmtMs(m.overview.humanWaitMs)} |`);
|
|
286
|
+
L.push(`| tokens | 总 ${m.overview.tokens}(in ${m.overview.tokensIn} / out ${m.overview.tokensOut} / reason ${m.overview.tokensReasoning}) |`);
|
|
287
|
+
L.push(`| 工具失败 / 空结果 | ${m.overview.toolErrors} / ${m.overview.emptyResults} |`);
|
|
288
|
+
L.push('');
|
|
289
|
+
L.push(`## 过度搜索信号`);
|
|
290
|
+
L.push('');
|
|
291
|
+
L.push(`- 重复调用组:${m.overSearch.duplicateCalls.length}`);
|
|
292
|
+
for (const d of m.overSearch.duplicateCalls.slice(0, 10)) L.push(` - ×${d.count} ${d.tool} [${d.queryClass}] ${d.preview}`);
|
|
293
|
+
L.push(`- 重复读同一文件:${m.overSearch.repeatedReads.length}`);
|
|
294
|
+
for (const r of m.overSearch.repeatedReads.slice(0, 10)) L.push(` - ×${r.count} ${r.path}`);
|
|
295
|
+
L.push(`- 空结果/失败合计:${m.overSearch.emptyOrError}`);
|
|
296
|
+
L.push(`- 最长无进展连续:${m.overSearch.noProgressStreak}`);
|
|
297
|
+
L.push('');
|
|
298
|
+
L.push(`## 后端 / 查询类分布`);
|
|
299
|
+
L.push('');
|
|
300
|
+
L.push(`- 后端:` + Object.entries(m.byBackend).map(([k, v]) => `${k}=${v.calls}`).join(' '));
|
|
301
|
+
L.push(`- 查询类:` + Object.entries(m.byQueryClass).map(([k, v]) => `${k}=${v}`).join(' '));
|
|
302
|
+
L.push('');
|
|
303
|
+
L.push(`## 最贵调用(按耗时)`);
|
|
304
|
+
L.push('');
|
|
305
|
+
for (const c of m.topOffendersByTime) L.push(`- ${fmtMs(c.ms)} ${c.tool}${c.ok ? '' : ' (error)'} ${c.preview}`);
|
|
306
|
+
L.push('');
|
|
307
|
+
L.push(`## token 随轮次`);
|
|
308
|
+
L.push('');
|
|
309
|
+
L.push(`每步 input tokens(累计上下文规模):${m.tokenGrowth.perStepInput.join(', ') || '(无)'}`);
|
|
310
|
+
L.push('');
|
|
311
|
+
const cb = m.overview.contextBlowup || { count: 0, max: 0, top: [] };
|
|
312
|
+
L.push(`## 上下文尖峰(≥ ${cb.thresholdTokens} tokens 的 step)`);
|
|
313
|
+
L.push('');
|
|
314
|
+
L.push(`- 尖峰数:${cb.count} 最大:${cb.max} tokens`);
|
|
315
|
+
for (const b of cb.top) L.push(` - step ${b.step}: ${b.input} input tokens`);
|
|
316
|
+
L.push('');
|
|
317
|
+
L.push(`## 诊断建议(启发式,供 Agent 复核)`);
|
|
318
|
+
L.push('');
|
|
319
|
+
for (const h of m.hints) L.push(`- ${h}`);
|
|
320
|
+
L.push('');
|
|
321
|
+
L.push(`---`);
|
|
322
|
+
L.push(`> 本报告由 agentSearchDiagnose 自动生成;请 Agent 结合 metrics.json 与会话片段复核并补充解读。`);
|
|
323
|
+
L.push('');
|
|
324
|
+
return L.join('\n');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function fmtMs(ms) {
|
|
328
|
+
if (ms == null) return 'n/a';
|
|
329
|
+
return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function writeBundle(opts) {
|
|
333
|
+
const workspace = opts.workspace || process.cwd();
|
|
334
|
+
const rawInput = fs.readFileSync(opts.session, 'utf8');
|
|
335
|
+
const sessionText = isExportJson(rawInput) ? exportToNdjson(rawInput) : rawInput;
|
|
336
|
+
const session = parseSession(sessionText);
|
|
337
|
+
const sessionId = opts.sessionId || inferSessionId(sessionText) || 'session';
|
|
338
|
+
const outDir = opts.out || path.join(workspace, '.argo', 'temp', 'diagnosis', sessionId);
|
|
339
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
340
|
+
|
|
341
|
+
const costLogPath = opts.costLog || path.join(workspace, '.argo', 'temp', 'agent-cost-log.ndjson');
|
|
342
|
+
const costLogText = readMaybe(costLogPath);
|
|
343
|
+
|
|
344
|
+
const metrics = diagnose(session, { workspace, sessionId, wallMs: opts.wallMs });
|
|
345
|
+
const files = [];
|
|
346
|
+
const write = (name, body) => { const p = path.join(outDir, name); fs.writeFileSync(p, body); files.push({ path: name, bytes: Buffer.byteLength(body) }); };
|
|
347
|
+
|
|
348
|
+
write('session.ndjson', sessionText);
|
|
349
|
+
write('cost-log.slice.ndjson', sliceCostLog(costLogText, sessionId));
|
|
350
|
+
write('metrics.json', JSON.stringify(metrics, null, 2) + '\n');
|
|
351
|
+
write('diagnosis.md', renderDiagnosis(metrics, { costLogPath }));
|
|
352
|
+
|
|
353
|
+
const manifest = {
|
|
354
|
+
schemaVersion: SCHEMA_VERSION, bundleVersion: BUNDLE_VERSION, generatedAt: metrics.generatedAt,
|
|
355
|
+
workspace, sessionId, sources: { session: path.resolve(opts.session), costLog: costLogPath },
|
|
356
|
+
files,
|
|
357
|
+
};
|
|
358
|
+
fs.writeFileSync(path.join(outDir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
359
|
+
return { outDir, metrics, manifest };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function readMaybe(file) { try { return fs.readFileSync(file, 'utf8'); } catch (_) { return ''; } }
|
|
363
|
+
|
|
364
|
+
function inferSessionId(text) {
|
|
365
|
+
const m = String(text).match(/"sessionID"\s*:\s*"([^"]+)"/);
|
|
366
|
+
return m ? m[1] : null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function sliceCostLog(text, sessionId) {
|
|
370
|
+
if (!text) return '';
|
|
371
|
+
const lines = text.split('\n').filter(Boolean);
|
|
372
|
+
const hit = lines.filter(l => !sessionId || l.includes(sessionId));
|
|
373
|
+
return (hit.length ? hit : []).join('\n') + (hit.length ? '\n' : '');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function parseArgs(argv) {
|
|
377
|
+
const a = { session: null, sessionId: null, costLog: null, workspace: process.cwd(), out: null, json: false };
|
|
378
|
+
for (let i = 0; i < argv.length; i++) {
|
|
379
|
+
const k = argv[i];
|
|
380
|
+
if (k === '--session') a.session = argv[++i];
|
|
381
|
+
else if (k === '--session-id') a.sessionId = argv[++i];
|
|
382
|
+
else if (k === '--cost-log') a.costLog = argv[++i];
|
|
383
|
+
else if (k === '--workspace') a.workspace = argv[++i];
|
|
384
|
+
else if (k === '--out') a.out = argv[++i];
|
|
385
|
+
else if (k === '--json') a.json = true;
|
|
386
|
+
}
|
|
387
|
+
return a;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function main(argv) {
|
|
391
|
+
const args = parseArgs(argv || process.argv.slice(2));
|
|
392
|
+
if (!args.session || !fs.existsSync(args.session)) {
|
|
393
|
+
console.error('agentSearchDiagnose: --session <session.ndjson> is required');
|
|
394
|
+
return 2;
|
|
395
|
+
}
|
|
396
|
+
const { outDir, metrics, manifest } = writeBundle(args);
|
|
397
|
+
if (args.json) { console.log(JSON.stringify({ outDir, overview: metrics.overview, hints: metrics.hints }, null, 2)); return 0; }
|
|
398
|
+
console.log(`diagnosis bundle -> ${outDir}`);
|
|
399
|
+
console.log(`files: ${manifest.files.map(f => f.path).join(', ')}`);
|
|
400
|
+
console.log(`overview: steps=${metrics.overview.steps} tools=${metrics.overview.toolCalls} roundTrips=${metrics.overview.roundTrips} tokens=${metrics.overview.tokens} model=${fmtMs(metrics.overview.modelMs)} mcp=${fmtMs(metrics.overview.mcpMs)} repo=${fmtMs(metrics.overview.repoToolMs)}`);
|
|
401
|
+
for (const h of metrics.hints) console.log(`hint: ${h}`);
|
|
402
|
+
return 0;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
module.exports = { BUNDLE_VERSION, SCHEMA_VERSION, parseSession, classifyQuery, backendOf, signature, diagnose, renderDiagnosis, writeBundle, sliceCostLog, isExportJson, exportToNdjson, main };
|
|
406
|
+
|
|
407
|
+
if (require.main === module) process.exit(main());
|
|
@@ -330,6 +330,8 @@ function intentElementContextInputSchema() {
|
|
|
330
330
|
dependentDepth: { type: 'number', description: 'Default: 1. Semantic dependents that rely on the focus element.' },
|
|
331
331
|
associationDepth: { type: 'number', description: 'Default: 1. Association neighbors are expanded at least one layer.' },
|
|
332
332
|
associationNeighborDependencyDepth: { type: 'number', description: 'Default: 0. Optional dependency expansion from association neighbors.' },
|
|
333
|
+
includeAttributes: { type: 'boolean', description: 'Default: false. Include `attributes` (commit/session/release ledgers) verbatim; omitted by default from this structural read (the focus element always keeps its own).' },
|
|
334
|
+
includeTestcases: { type: 'boolean', description: 'Default: false. Include member `testcases` verbatim; omitted by default from this structural read.' },
|
|
333
335
|
},
|
|
334
336
|
additionalProperties: false,
|
|
335
337
|
};
|
|
@@ -461,6 +461,8 @@ function intentElementContextInputSchema() {
|
|
|
461
461
|
dependentDepth: { type: 'number', description: 'Default: 1. Semantic dependents that rely on the focus element.' },
|
|
462
462
|
associationDepth: { type: 'number', description: 'Default: 1. Association neighbors are expanded at least one layer.' },
|
|
463
463
|
associationNeighborDependencyDepth: { type: 'number', description: 'Default: 0. Optional dependency expansion from association neighbors.' },
|
|
464
|
+
includeAttributes: { type: 'boolean', description: 'Default: false. Include `attributes` (commit/session/release ledgers) verbatim. Omitted by default from this structural read; the focus element always keeps its own. Semantic retrieval embeds attributes, so semantic hits keep them.' },
|
|
465
|
+
includeTestcases: { type: 'boolean', description: 'Default: false. Include member `testcases` verbatim. Omitted by default from this structural read (bookkeeping); pass true when you need acceptance cases. Semantic retrieval embeds testcase descriptions, so semantic hits keep them.' },
|
|
464
466
|
},
|
|
465
467
|
additionalProperties: false,
|
|
466
468
|
};
|
|
@@ -475,6 +477,8 @@ function viewContextInputSchema() {
|
|
|
475
477
|
view_id: { type: 'string', description: 'The id of the view to resolve.' },
|
|
476
478
|
includeParentElement: { type: 'boolean', description: 'Default: true. Resolve the parent element referenced by the view.' },
|
|
477
479
|
includeChildViews: { type: 'boolean', description: 'Default: false. Include child views declared by member elements through subdiagram_views.' },
|
|
480
|
+
includeAttributes: { type: 'boolean', description: 'Default: false. Include member/relationship `attributes` (commit/session/release ledgers) verbatim. Omitted by default from this structural read (bookkeeping); pass true when you need provenance.' },
|
|
481
|
+
includeTestcases: { type: 'boolean', description: 'Default: false. Include member `testcases` verbatim. Omitted by default from this structural read; pass true for acceptance-case lookups.' },
|
|
478
482
|
includeEaGeometry: { type: 'boolean', description: 'Default: false (opt-in). When true, additionally resolve the diagram GEOMETRY (element boxes + connector line routes) for this view from the workspace EA model (.qea) and return it under a `geometry` field aligned by schema id with the resolved members. Each geometry relationship carries: `path` (the EA route from t_diagramlinks.Path, "" when EA auto-routes), `points` (the parsed [{x,y}] waypoints), `edge` (the EDGE route-style token or null) and `geometry` (the raw SX/SY/EX/EY override string, which contains NO waypoints). By default the EA model is never touched and no `geometry` field is returned; a missing EA model/diagram yields geometry.present=false, never an error.' },
|
|
479
483
|
},
|
|
480
484
|
additionalProperties: false,
|
|
@@ -615,6 +619,42 @@ function validateDocument(document, schema, options = {}) {
|
|
|
615
619
|
return errors;
|
|
616
620
|
}
|
|
617
621
|
|
|
622
|
+
// Agent-facing projection of canonical records for STRUCTURAL reads (view
|
|
623
|
+
// membership / intent-element subgraph). `attributes` (commit/session/release
|
|
624
|
+
// ledgers) and `testcases` are bookkeeping: measured as ~52% of the element
|
|
625
|
+
// bytes but NOT needed to answer structural reads, so they are omitted by
|
|
626
|
+
// default and returned only on opt-in (includeAttributes / includeTestcases).
|
|
627
|
+
//
|
|
628
|
+
// This is NOT summarisation: every retained field is returned verbatim. It is
|
|
629
|
+
// also NOT the semantic match surface — semantic retrieval embeds attributes and
|
|
630
|
+
// testcase descriptions (semanticRecordText.js), so semantic results keep them;
|
|
631
|
+
// and the focus element of an intent-element read always keeps its own.
|
|
632
|
+
function projectAgentFields(record, opts = {}) {
|
|
633
|
+
const value = clone(record);
|
|
634
|
+
let omittedAttributes = 0;
|
|
635
|
+
let omittedTestcases = 0;
|
|
636
|
+
if (!opts.includeAttributes && Array.isArray(value.attributes) && value.attributes.length) {
|
|
637
|
+
omittedAttributes = value.attributes.length;
|
|
638
|
+
delete value.attributes;
|
|
639
|
+
}
|
|
640
|
+
if (!opts.includeTestcases && Array.isArray(value.testcases) && value.testcases.length) {
|
|
641
|
+
omittedTestcases = value.testcases.length;
|
|
642
|
+
delete value.testcases;
|
|
643
|
+
}
|
|
644
|
+
return { value, omittedAttributes, omittedTestcases };
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function buildAgentProjection(omitted) {
|
|
648
|
+
if (!omitted || (omitted.attributes === 0 && omitted.testcases === 0)) {
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
attributesOmitted: omitted.attributes,
|
|
653
|
+
testcasesOmitted: omitted.testcases,
|
|
654
|
+
note: 'Member attributes/testcases are bookkeeping (commit/session/release ledgers, acceptance cases) and are omitted from this structural read by default. Pass includeAttributes:true / includeTestcases:true to include them verbatim. Semantic retrieval already embeds their text, so semantic hits keep them.',
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
618
658
|
function buildIntentElementContext(context, args = {}) {
|
|
619
659
|
const profile = args.profile || 'generic-agent';
|
|
620
660
|
const focusResult = resolveFocusElement(context.document, args);
|
|
@@ -697,7 +737,27 @@ function buildIntentElementContext(context, args = {}) {
|
|
|
697
737
|
associationDepth,
|
|
698
738
|
});
|
|
699
739
|
|
|
700
|
-
|
|
740
|
+
const subgraph = buildNativeSubgraph(context.document, includedElementIds, includedRelationshipIds);
|
|
741
|
+
const includeAttributes = args.includeAttributes === true;
|
|
742
|
+
const includeTestcases = args.includeTestcases === true;
|
|
743
|
+
const omitted = { attributes: 0, testcases: 0 };
|
|
744
|
+
if (!(includeAttributes && includeTestcases)) {
|
|
745
|
+
subgraph.elements = subgraph.elements.map((element) => {
|
|
746
|
+
if (element.id === focusElement.id) {
|
|
747
|
+
return element; // the focus element keeps its own bookkeeping
|
|
748
|
+
}
|
|
749
|
+
const projected = projectAgentFields(element, { includeAttributes, includeTestcases });
|
|
750
|
+
omitted.attributes += projected.omittedAttributes;
|
|
751
|
+
omitted.testcases += projected.omittedTestcases;
|
|
752
|
+
return projected.value;
|
|
753
|
+
});
|
|
754
|
+
subgraph.relationships = subgraph.relationships.map((relationship) => {
|
|
755
|
+
const projected = projectAgentFields(relationship, { includeAttributes });
|
|
756
|
+
omitted.attributes += projected.omittedAttributes;
|
|
757
|
+
return projected.value;
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
const result = {
|
|
701
761
|
status: 'passed',
|
|
702
762
|
query: {
|
|
703
763
|
architecturePath: context.graphPath.relativePath,
|
|
@@ -711,12 +771,15 @@ function buildIntentElementContext(context, args = {}) {
|
|
|
711
771
|
traversalMode: 'archimate-semantic',
|
|
712
772
|
},
|
|
713
773
|
focusElementId: focusElement.id,
|
|
714
|
-
subgraph
|
|
774
|
+
subgraph,
|
|
715
775
|
boundary,
|
|
716
776
|
explorationHints,
|
|
717
777
|
workContext: {},
|
|
718
778
|
diagnostics: [],
|
|
719
779
|
};
|
|
780
|
+
const projection = buildAgentProjection(omitted);
|
|
781
|
+
if (projection) result.projection = projection;
|
|
782
|
+
return result;
|
|
720
783
|
}
|
|
721
784
|
|
|
722
785
|
// ---------------------------------------------------------------------------
|
|
@@ -786,12 +849,22 @@ function buildViewContext(context, args = {}) {
|
|
|
786
849
|
const elementById = new Map((document.elements || []).map(element => [element.id, element]));
|
|
787
850
|
const relationshipById = new Map((document.relationships || []).map(relationship => [relationship.id, relationship]));
|
|
788
851
|
|
|
852
|
+
const includeAttributes = args.includeAttributes === true;
|
|
853
|
+
const includeTestcases = args.includeTestcases === true;
|
|
854
|
+
const omitted = { attributes: 0, testcases: 0 };
|
|
855
|
+
const project = (record) => {
|
|
856
|
+
const projected = projectAgentFields(record, { includeAttributes, includeTestcases });
|
|
857
|
+
omitted.attributes += projected.omittedAttributes;
|
|
858
|
+
omitted.testcases += projected.omittedTestcases;
|
|
859
|
+
return projected.value;
|
|
860
|
+
};
|
|
861
|
+
|
|
789
862
|
const elements = [];
|
|
790
863
|
const missingElementIds = [];
|
|
791
864
|
for (const elementId of view.included_elements || []) {
|
|
792
865
|
const element = elementById.get(elementId);
|
|
793
866
|
if (element) {
|
|
794
|
-
elements.push(
|
|
867
|
+
elements.push(project(element));
|
|
795
868
|
} else {
|
|
796
869
|
missingElementIds.push(elementId);
|
|
797
870
|
}
|
|
@@ -802,7 +875,7 @@ function buildViewContext(context, args = {}) {
|
|
|
802
875
|
for (const relationshipId of view.included_relationships || []) {
|
|
803
876
|
const relationship = relationshipById.get(relationshipId);
|
|
804
877
|
if (relationship) {
|
|
805
|
-
relationships.push(
|
|
878
|
+
relationships.push(project(relationship));
|
|
806
879
|
} else {
|
|
807
880
|
missingRelationshipIds.push(relationshipId);
|
|
808
881
|
}
|
|
@@ -812,7 +885,7 @@ function buildViewContext(context, args = {}) {
|
|
|
812
885
|
let parentElement = null;
|
|
813
886
|
if (includeParentElement && view.parent_element_id) {
|
|
814
887
|
const parent = elementById.get(view.parent_element_id);
|
|
815
|
-
parentElement = parent ?
|
|
888
|
+
parentElement = parent ? project(parent) : null;
|
|
816
889
|
}
|
|
817
890
|
|
|
818
891
|
const includeChildViews = args.includeChildViews === true;
|
|
@@ -847,6 +920,8 @@ function buildViewContext(context, args = {}) {
|
|
|
847
920
|
if (args.includeEaGeometry === true) {
|
|
848
921
|
result.geometry = readEaViewGeometry(context.workspaceRoot, viewId);
|
|
849
922
|
}
|
|
923
|
+
const projection = buildAgentProjection(omitted);
|
|
924
|
+
if (projection) result.projection = projection;
|
|
850
925
|
return result;
|
|
851
926
|
}
|
|
852
927
|
|
|
@@ -2750,7 +2825,7 @@ async function memorySearchTool(args = {}, dependencies = undefined) {
|
|
|
2750
2825
|
.filter(element => element && typeof element.semanticScore === 'number')
|
|
2751
2826
|
.sort((left, right) => right.semanticScore - left.semanticScore)
|
|
2752
2827
|
.slice(0, topK)
|
|
2753
|
-
.map(element => Object.freeze(memoryHitCard(element, maxDescLen)));
|
|
2828
|
+
.map(element => Object.freeze(memoryHitCard(element, maxDescLen, query)));
|
|
2754
2829
|
return {
|
|
2755
2830
|
status: 'passed',
|
|
2756
2831
|
query,
|
|
@@ -2763,7 +2838,7 @@ async function memorySearchTool(args = {}, dependencies = undefined) {
|
|
|
2763
2838
|
// Build one compact memory hit: id/name/type/score + an excerpt of the
|
|
2764
2839
|
// description bounded by maxDescLen, plus the full text length so the caller
|
|
2765
2840
|
// knows how much content exists and can decide whether to expand.
|
|
2766
|
-
function memoryHitCard(element, maxDescLen) {
|
|
2841
|
+
function memoryHitCard(element, maxDescLen, query) {
|
|
2767
2842
|
const description = typeof element.description === 'string' ? element.description : '';
|
|
2768
2843
|
const descriptionLength = description.length;
|
|
2769
2844
|
let excerpt = '';
|
|
@@ -2783,6 +2858,13 @@ function memoryHitCard(element, maxDescLen) {
|
|
|
2783
2858
|
card.truncated = true;
|
|
2784
2859
|
}
|
|
2785
2860
|
}
|
|
2861
|
+
// A memory match may be driven by an attribute/testcase (both are embedded);
|
|
2862
|
+
// surface the matching bookkeeping text so the agent sees WHY it hit without
|
|
2863
|
+
// a second call.
|
|
2864
|
+
const snippet = bookkeepingSnippet(element, 'Element', query);
|
|
2865
|
+
if (snippet) {
|
|
2866
|
+
card.matchedSnippet = snippet;
|
|
2867
|
+
}
|
|
2786
2868
|
return card;
|
|
2787
2869
|
}
|
|
2788
2870
|
|
|
@@ -3290,11 +3372,31 @@ function buildBusinessSemanticSummary(retrieved, query = {}) {
|
|
|
3290
3372
|
supplementaryReasons: Array.isArray(item.supplementaryReasons) ? [...item.supplementaryReasons] : [],
|
|
3291
3373
|
},
|
|
3292
3374
|
]));
|
|
3375
|
+
const recordById = new Map();
|
|
3376
|
+
const putRecord = (objectType, record, id) => { if (record && id) recordById.set(`${objectType}:${id}`, record); };
|
|
3377
|
+
for (const element of (source.closure && source.closure.elements) || []) putRecord('Element', element, element.id);
|
|
3378
|
+
for (const view of (source.viewClosure && source.viewClosure.views) || []) {
|
|
3379
|
+
putRecord('View', view, view.view_id);
|
|
3380
|
+
for (const member of view.memberElements || []) putRecord('Element', member, member.id);
|
|
3381
|
+
for (const member of view.memberRelationships || []) putRecord('ArchitectureRelationship', member, member.id);
|
|
3382
|
+
}
|
|
3383
|
+
for (const relationship of (source.endpointClosure && source.endpointClosure.relationships) || []) {
|
|
3384
|
+
putRecord('ArchitectureRelationship', relationship, relationship.id);
|
|
3385
|
+
if (relationship.source) putRecord('Element', relationship.source, relationship.source.id);
|
|
3386
|
+
if (relationship.target) putRecord('Element', relationship.target, relationship.target.id);
|
|
3387
|
+
}
|
|
3388
|
+
const ctx = {
|
|
3389
|
+
query,
|
|
3390
|
+
recordById,
|
|
3391
|
+
hitElementIds: buildHitIdSet(source, 'elements'),
|
|
3392
|
+
hitRelationshipIds: buildHitIdSet(source, 'relationships'),
|
|
3393
|
+
hitViewIds: buildHitIdSet(source, 'views'),
|
|
3394
|
+
};
|
|
3293
3395
|
const seedLimit = businessSummaryLimit(query);
|
|
3294
|
-
const semanticSeeds = summarizeSeeds(source.seedsByType, hitReasonByKey, seedLimit);
|
|
3295
|
-
const elements = summarizeElements(source, hitReasonByKey, seedLimit * 2);
|
|
3296
|
-
const relationships = summarizeRelationships(source, hitReasonByKey, seedLimit * 2);
|
|
3297
|
-
const views = summarizeViews(source, hitReasonByKey, seedLimit);
|
|
3396
|
+
const semanticSeeds = summarizeSeeds(source.seedsByType, hitReasonByKey, seedLimit, ctx);
|
|
3397
|
+
const elements = summarizeElements(source, hitReasonByKey, seedLimit * 2, ctx);
|
|
3398
|
+
const relationships = summarizeRelationships(source, hitReasonByKey, seedLimit * 2, ctx);
|
|
3399
|
+
const views = summarizeViews(source, hitReasonByKey, seedLimit, ctx);
|
|
3298
3400
|
const includedObjectIds = Object.freeze([
|
|
3299
3401
|
...elements.map(item => item.id),
|
|
3300
3402
|
...relationships.map(item => item.id),
|
|
@@ -3688,7 +3790,70 @@ function businessSummaryLimit(query) {
|
|
|
3688
3790
|
return Number.isInteger(supplied) && supplied > 0 ? Math.min(supplied, 50) : 8;
|
|
3689
3791
|
}
|
|
3690
3792
|
|
|
3691
|
-
|
|
3793
|
+
// A hit may be driven by an attribute/testcase (both are embedded), so surface
|
|
3794
|
+
// the matching bookkeeping text on HIT records only — so the agent sees WHY it
|
|
3795
|
+
// matched without a second lookup. Never attached to pruned neighbours.
|
|
3796
|
+
function tokenizeForMatch(text) {
|
|
3797
|
+
const tokens = new Set();
|
|
3798
|
+
const s = String(text || '');
|
|
3799
|
+
for (const word of s.toLowerCase().match(/[a-z0-9]{3,}/g) || []) tokens.add(word);
|
|
3800
|
+
for (const run of s.match(/[\u4e00-\u9fff]{2,}/g) || []) {
|
|
3801
|
+
tokens.add(run);
|
|
3802
|
+
for (let i = 0; i + 2 <= run.length; i += 1) tokens.add(run.slice(i, i + 2));
|
|
3803
|
+
}
|
|
3804
|
+
return [...tokens];
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
function bookkeepingSnippet(record, objectType, query, maxLen = 240) {
|
|
3808
|
+
if (!record || typeof record !== 'object') return null;
|
|
3809
|
+
const candidates = [];
|
|
3810
|
+
if (objectType === 'ArchitectureRelationship' || objectType === 'Relationship') {
|
|
3811
|
+
if (typeof record.statement === 'string' && record.statement.trim()) candidates.push(record.statement.trim());
|
|
3812
|
+
if (typeof record.description === 'string' && record.description.trim()) candidates.push(record.description.trim());
|
|
3813
|
+
}
|
|
3814
|
+
for (const attribute of Array.isArray(record.attributes) ? record.attributes : []) {
|
|
3815
|
+
if (!attribute || typeof attribute.name !== 'string') continue;
|
|
3816
|
+
const text = (typeof attribute.value === 'string' && attribute.value.trim())
|
|
3817
|
+
|| (typeof attribute.description === 'string' && attribute.description.trim()) || '';
|
|
3818
|
+
if (text) candidates.push(`${attribute.name}: ${text}`);
|
|
3819
|
+
}
|
|
3820
|
+
for (const testcase of Array.isArray(record.testcases) ? record.testcases : []) {
|
|
3821
|
+
if (!testcase) continue;
|
|
3822
|
+
const text = typeof testcase === 'string'
|
|
3823
|
+
? testcase
|
|
3824
|
+
: (testcase.description || testcase.coverage || testcase.name || '');
|
|
3825
|
+
if (typeof text === 'string' && text.trim()) candidates.push(`AT ${testcase.name || ''}: ${text.trim()}`.trim());
|
|
3826
|
+
}
|
|
3827
|
+
if (!candidates.length) return null;
|
|
3828
|
+
const q = typeof query === 'string' ? query : (query && query.intent) || '';
|
|
3829
|
+
const tokens = tokenizeForMatch(q);
|
|
3830
|
+
const scored = candidates.map((text, index) => {
|
|
3831
|
+
const lower = text.toLowerCase();
|
|
3832
|
+
let overlap = 0;
|
|
3833
|
+
for (const token of tokens) if (lower.includes(token)) overlap += 1;
|
|
3834
|
+
return { text, overlap, index };
|
|
3835
|
+
}).sort((a, b) => (b.overlap - a.overlap) || (b.text.length - a.text.length) || (a.index - b.index));
|
|
3836
|
+
let out = '';
|
|
3837
|
+
for (const entry of scored) {
|
|
3838
|
+
if (out && out.length + entry.text.length + 2 > maxLen) break;
|
|
3839
|
+
out = out ? `${out} | ${entry.text}` : entry.text;
|
|
3840
|
+
if (out.length >= maxLen) break;
|
|
3841
|
+
}
|
|
3842
|
+
if (!out) return null;
|
|
3843
|
+
return out.length > maxLen ? `${out.slice(0, maxLen - 3)}...` : out;
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
function buildHitIdSet(source, typeKey) {
|
|
3847
|
+
const set = new Set();
|
|
3848
|
+
const seeds = (source && source.seedsByType && source.seedsByType[typeKey]) || [];
|
|
3849
|
+
for (const seed of Array.isArray(seeds) ? seeds : []) {
|
|
3850
|
+
const raw = seed && (seed.id || seed.objectId || seed.canonicalIdentity);
|
|
3851
|
+
if (typeof raw === 'string' && raw) set.add(raw.includes(':') ? raw.split(':').pop() : raw);
|
|
3852
|
+
}
|
|
3853
|
+
return set;
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
function summarizeSeeds(seedsByType = {}, hitReasonByKey, limit, ctx = {}) {
|
|
3692
3857
|
return Object.freeze(Object.fromEntries(Object.entries(seedsByType).map(([type, seeds]) => [
|
|
3693
3858
|
type,
|
|
3694
3859
|
Object.freeze((Array.isArray(seeds) ? seeds : [])
|
|
@@ -3697,14 +3862,18 @@ function summarizeSeeds(seedsByType = {}, hitReasonByKey, limit) {
|
|
|
3697
3862
|
.slice(0, limit)
|
|
3698
3863
|
.map(seed => {
|
|
3699
3864
|
const objectType = seed.objectType || seed.channel || inferObjectTypeFromSeedType(type);
|
|
3700
|
-
const
|
|
3865
|
+
const rawId = seed.id || seed.objectId || seed.canonicalIdentity;
|
|
3866
|
+
const objectId = typeof rawId === 'string' && rawId.includes(':') ? rawId.split(':').pop() : rawId;
|
|
3701
3867
|
const reasons = hitReasonByKey.get(`${objectType}:${objectId}`) || {};
|
|
3868
|
+
const record = ctx.recordById ? ctx.recordById.get(`${objectType}:${objectId}`) : null;
|
|
3869
|
+
const snippet = record ? bookkeepingSnippet(record, objectType, ctx.query) : null;
|
|
3702
3870
|
return Object.freeze({
|
|
3703
3871
|
objectId,
|
|
3704
3872
|
objectType,
|
|
3705
3873
|
score: typeof seed.score === 'number' ? seed.score : undefined,
|
|
3706
3874
|
hitReason: reasons.firstInclusionReason || 'semantic-seed',
|
|
3707
3875
|
supplementaryReasons: Object.freeze(reasons.supplementaryReasons || []),
|
|
3876
|
+
...(snippet ? { matchedSnippet: snippet } : {}),
|
|
3708
3877
|
});
|
|
3709
3878
|
})),
|
|
3710
3879
|
])));
|
|
@@ -3716,19 +3885,25 @@ function inferObjectTypeFromSeedType(type) {
|
|
|
3716
3885
|
return 'Element';
|
|
3717
3886
|
}
|
|
3718
3887
|
|
|
3719
|
-
function summarizeElements(source, hitReasonByKey, limit) {
|
|
3888
|
+
function summarizeElements(source, hitReasonByKey, limit, ctx = {}) {
|
|
3889
|
+
// Semantic subgraph rule: the attribute/testcase-DERIVED fields (status /
|
|
3890
|
+
// functionalPoints / testCoverage) and the matchedSnippet are the match
|
|
3891
|
+
// surface for the HIT elements only; closure neighbours drop them
|
|
3892
|
+
// (bookkeepingOmitted) — mirroring the structural-read projection.
|
|
3893
|
+
const hitIds = ctx.hitElementIds || buildHitIdSet(source, 'elements');
|
|
3720
3894
|
return uniqueById([
|
|
3721
3895
|
...(((source.closure && source.closure.elements) || [])),
|
|
3722
3896
|
...((((source.viewClosure && source.viewClosure.views) || []).flatMap(view => view.memberElements || []))),
|
|
3723
3897
|
...((((source.endpointClosure && source.endpointClosure.relationships) || []).flatMap(relationship => [relationship.source, relationship.target]).filter(Boolean))),
|
|
3724
|
-
], 'id').slice(0, limit).map(element => summarizeElement(element, hitReasonByKey));
|
|
3898
|
+
], 'id').slice(0, limit).map(element => summarizeElement(element, hitReasonByKey, hitIds, ctx));
|
|
3725
3899
|
}
|
|
3726
3900
|
|
|
3727
|
-
function summarizeRelationships(source, hitReasonByKey, limit) {
|
|
3901
|
+
function summarizeRelationships(source, hitReasonByKey, limit, ctx = {}) {
|
|
3902
|
+
const hitIds = ctx.hitRelationshipIds || buildHitIdSet(source, 'relationships');
|
|
3728
3903
|
return uniqueById([
|
|
3729
3904
|
...(((source.endpointClosure && source.endpointClosure.relationships) || [])),
|
|
3730
3905
|
...((((source.viewClosure && source.viewClosure.views) || []).flatMap(view => view.memberRelationships || []))),
|
|
3731
|
-
], 'id').slice(0, limit).map(relationship => summarizeRelationship(relationship, hitReasonByKey));
|
|
3906
|
+
], 'id').slice(0, limit).map(relationship => summarizeRelationship(relationship, hitReasonByKey, hitIds, ctx));
|
|
3732
3907
|
}
|
|
3733
3908
|
|
|
3734
3909
|
function summarizeViews(source, hitReasonByKey, limit) {
|
|
@@ -3737,27 +3912,38 @@ function summarizeViews(source, hitReasonByKey, limit) {
|
|
|
3737
3912
|
.map(view => summarizeView(view, hitReasonByKey));
|
|
3738
3913
|
}
|
|
3739
3914
|
|
|
3740
|
-
function summarizeElement(element, hitReasonByKey) {
|
|
3915
|
+
function summarizeElement(element, hitReasonByKey, hitIds = null, ctx = {}) {
|
|
3741
3916
|
const attributes = attributesMap(element);
|
|
3742
3917
|
const reasons = hitReasonByKey.get(`Element:${element.id}`) || {};
|
|
3743
|
-
|
|
3918
|
+
const isHit = !hitIds || hitIds.size === 0 || hitIds.has(element.id);
|
|
3919
|
+
const base = {
|
|
3744
3920
|
id: element.id,
|
|
3745
3921
|
name: element.name,
|
|
3746
3922
|
type: element.type,
|
|
3747
3923
|
descriptionSummary: summarizeText(element.description),
|
|
3748
|
-
status: attributes.deliveryStatus || attributes.status,
|
|
3749
|
-
functionalPoints: Object.freeze(Object.entries(attributes)
|
|
3750
|
-
.filter(([name]) => name.startsWith('functionalPoint'))
|
|
3751
|
-
.map(([, value]) => value)),
|
|
3752
|
-
testCoverage: summarizeTestcases(element.testcases),
|
|
3753
3924
|
hitReason: reasons.firstInclusionReason,
|
|
3754
3925
|
supplementaryReasons: Object.freeze(reasons.supplementaryReasons || []),
|
|
3755
|
-
}
|
|
3926
|
+
};
|
|
3927
|
+
if (isHit) {
|
|
3928
|
+
const snippet = bookkeepingSnippet(element, 'Element', ctx.query);
|
|
3929
|
+
return Object.freeze({
|
|
3930
|
+
...base,
|
|
3931
|
+
status: attributes.deliveryStatus || attributes.status,
|
|
3932
|
+
functionalPoints: Object.freeze(Object.entries(attributes)
|
|
3933
|
+
.filter(([name]) => name.startsWith('functionalPoint'))
|
|
3934
|
+
.map(([, value]) => value)),
|
|
3935
|
+
testCoverage: summarizeTestcases(element.testcases),
|
|
3936
|
+
...(snippet ? { matchedSnippet: snippet } : {}),
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
3939
|
+
const hasBookkeeping = Object.keys(attributes).length > 0
|
|
3940
|
+
|| (Array.isArray(element.testcases) && element.testcases.length > 0);
|
|
3941
|
+
return Object.freeze({ ...base, ...(hasBookkeeping ? { bookkeepingOmitted: true } : {}) });
|
|
3756
3942
|
}
|
|
3757
3943
|
|
|
3758
|
-
function summarizeRelationship(relationship, hitReasonByKey) {
|
|
3944
|
+
function summarizeRelationship(relationship, hitReasonByKey, hitIds = null, ctx = {}) {
|
|
3759
3945
|
const reasons = hitReasonByKey.get(`ArchitectureRelationship:${relationship.id}`) || {};
|
|
3760
|
-
|
|
3946
|
+
const base = {
|
|
3761
3947
|
id: relationship.id,
|
|
3762
3948
|
name: relationship.name,
|
|
3763
3949
|
type: relationship.type,
|
|
@@ -3765,7 +3951,13 @@ function summarizeRelationship(relationship, hitReasonByKey) {
|
|
|
3765
3951
|
target_id: relationship.target_id,
|
|
3766
3952
|
hitReason: reasons.firstInclusionReason,
|
|
3767
3953
|
supplementaryReasons: Object.freeze(reasons.supplementaryReasons || []),
|
|
3768
|
-
}
|
|
3954
|
+
};
|
|
3955
|
+
const isHit = !hitIds || hitIds.size === 0 || hitIds.has(relationship.id);
|
|
3956
|
+
if (!isHit) {
|
|
3957
|
+
return Object.freeze(base);
|
|
3958
|
+
}
|
|
3959
|
+
const snippet = bookkeepingSnippet(relationship, 'ArchitectureRelationship', ctx.query);
|
|
3960
|
+
return Object.freeze({ ...base, ...(snippet ? { matchedSnippet: snippet } : {}) });
|
|
3769
3961
|
}
|
|
3770
3962
|
|
|
3771
3963
|
function summarizeView(view, hitReasonByKey) {
|
|
@@ -4239,6 +4431,7 @@ module.exports = {
|
|
|
4239
4431
|
GET_SYSTEM_ARCHITECTURE_OUTPUT_SCHEMA,
|
|
4240
4432
|
TOOLS,
|
|
4241
4433
|
applyMutations,
|
|
4434
|
+
buildBusinessSemanticSummary,
|
|
4242
4435
|
buildSemanticDedupAdvisory,
|
|
4243
4436
|
selectCreatedElementAdds,
|
|
4244
4437
|
evaluateSemanticDedupGate,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-search-diagnosis
|
|
3
|
+
description: "诊断一次 Agent 会话的检索/搜索行为(是否过度搜索、轮次/token/耗时花在哪、是否重复或空手、图↔仓往返),产出一份自包含的『诊断包』(diagnosis.md 总结 + metrics.json + 会话 NDJSON + 插件日志切片 + manifest.json),供人类伙伴打包回传以便进一步分析优化。Use when the user says an agent session searched too many rounds / wants to diagnose retrieval cost or over-search, or asks to produce a diagnostic bundle to send back. Keywords: 过度搜索, 诊断, over-search, search diagnosis, retrieval cost, diagnostic bundle, agent-search-diagnosis."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agent Search Diagnosis
|
|
7
|
+
|
|
8
|
+
把**一次 Agent 会话**变成一份自包含的**诊断包**:现场出诊断总结 + 聚合关键数据到一个目录,用户打包回传即可。
|
|
9
|
+
|
|
10
|
+
## 数据来源
|
|
11
|
+
|
|
12
|
+
- **主**:宿主会话 NDJSON(含每个工具的**完整入参/输出**、时间戳、tokens)。
|
|
13
|
+
- **辅**:框架后台日志 `<workspace>/.argo/temp/agent-cost-log.ndjson`(跨会话成本形态)。
|
|
14
|
+
|
|
15
|
+
## 输出(固定目录)
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
<workspace>/.argo/temp/diagnosis/<session-id>/
|
|
19
|
+
├─ diagnosis.md # 人读诊断总结(含启发式建议,供 Agent 复核)
|
|
20
|
+
├─ metrics.json # 总结背后的数字
|
|
21
|
+
├─ session.ndjson # 原始会话(全保真)
|
|
22
|
+
├─ cost-log.slice.ndjson # 匹配该会话的后台日志切片
|
|
23
|
+
└─ manifest.json # 文件清单 + 大小 + 版本
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Workflow
|
|
27
|
+
|
|
28
|
+
### 1. 定位并导出会话
|
|
29
|
+
|
|
30
|
+
- 若用户给出了 sessionID:`opencode export <sessionID> > <tmp>.ndjson`。
|
|
31
|
+
- 若未给出:`opencode session list` 取最近会话,或请用户指定。
|
|
32
|
+
- 宿主不支持导出时:退化为**仅用后台日志**(`agent-cost-log.ndjson`,诊断能力受限,须在报告中注明)。
|
|
33
|
+
|
|
34
|
+
### 2. 运行诊断脚本(确定性)
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
node ~/.argo/scripts/agentSearchDiagnose.js --session <session.ndjson> \
|
|
38
|
+
[--session-id <id>] [--workspace .] [--cost-log <path>] [--json]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
脚本**只读**会话与日志,写出上面的 bundle 目录,输出概览与启发式 hints。
|
|
42
|
+
支持两种输入:`opencode export` 的会话 JSON(`{info, messages}`)与 `opencode run --format json` 的事件流 NDJSON。
|
|
43
|
+
|
|
44
|
+
### 3. 复核并补充诊断总结(Agent)
|
|
45
|
+
|
|
46
|
+
- 读 `diagnosis.md` 与 `metrics.json`。
|
|
47
|
+
- 在报告末尾追加一节 `## Agent 复核解读`:结合会话片段指出**最可能的过度搜索根因**与**建议的优化方向**(去重/缓存、查询构造、停止判据、上下文压缩、图↔仓往返合并等)。
|
|
48
|
+
- 不臆造:结论必须能从 metrics/会话片段找到依据。
|
|
49
|
+
|
|
50
|
+
### 4. 交付给用户
|
|
51
|
+
|
|
52
|
+
- 报告 bundle 目录路径,请用户**打包该目录**回传。
|
|
53
|
+
- 给一段简短结论(轮次/token/耗时拆分 + 主要信号)。
|
|
54
|
+
|
|
55
|
+
## Rules(MUST / MUST NOT)
|
|
56
|
+
|
|
57
|
+
- **MUST** 只用确定性脚本 + 会话/日志读取;**MUST NOT** 修改图谱、仓库或任何框架内容。
|
|
58
|
+
- **MUST NOT** 读取或打印 `.env`/凭据;若会话片段疑似含密钥,**MUST** 提醒用户回传前先审查 `session.ndjson`(其中是原始工具 I/O)。
|
|
59
|
+
- **MUST** 在结论中区分「事实(metrics)」与「推断(根因假设)」,推断须标注依据。
|
|
60
|
+
- 若无法导出会话或脚本不可用,**MUST** 如实说明并给出退化方案,不得臆测诊断结果。
|
package/install-argo.ps1
CHANGED
|
@@ -788,8 +788,11 @@ $skillDest = Join-Path $SkillsRoot 'argo-init'
|
|
|
788
788
|
Write-Host "[4/22] argo\skills\argo-init -> $skillDest"
|
|
789
789
|
Copy-Tree -Source $skillSrc -Destination $skillDest
|
|
790
790
|
$reconcileSkillSrc = Join-Path (Join-Path $argoDir 'skills') 'ea-human-reconcile'
|
|
791
|
+
$diagSkillSrc = Join-Path (Join-Path $argoDir 'skills') 'agent-search-diagnosis'
|
|
791
792
|
Write-Host ' argo\skills\ea-human-reconcile -> $SkillsRoot\ea-human-reconcile (EA human draft reconcile skill)'
|
|
792
793
|
Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $SkillsRoot 'ea-human-reconcile')
|
|
794
|
+
Write-Host ' argo\skills\agent-search-diagnosis -> $SkillsRoot\agent-search-diagnosis (agent search diagnosis skill)'
|
|
795
|
+
Copy-Tree -Source $diagSkillSrc -Destination (Join-Path $SkillsRoot 'agent-search-diagnosis')
|
|
793
796
|
|
|
794
797
|
$ruleSrc = Join-Path (Join-Path $argoDir 'rules') 'archgraph.instructions.md'
|
|
795
798
|
$ruleDest = Join-Path $PromptsRoot 'archgraph.instructions.md'
|
|
@@ -807,6 +810,8 @@ Write-Host "[7/22] argo\skills\argo-init -> $cursorSkillDest (Cursor)"
|
|
|
807
810
|
Copy-Tree -Source $skillSrc -Destination $cursorSkillDest
|
|
808
811
|
Write-Host ' argo\skills\ea-human-reconcile -> $CursorSkillsRoot\ea-human-reconcile (Cursor)'
|
|
809
812
|
Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $CursorSkillsRoot 'ea-human-reconcile')
|
|
813
|
+
Write-Host ' argo\skills\agent-search-diagnosis -> $CursorSkillsRoot\agent-search-diagnosis (Cursor)'
|
|
814
|
+
Copy-Tree -Source $diagSkillSrc -Destination (Join-Path $CursorSkillsRoot 'agent-search-diagnosis')
|
|
810
815
|
|
|
811
816
|
$mcpBridgeSrc = Join-Path $argoDir 'mcp-bridges'
|
|
812
817
|
$mcpBridgeDest = Join-Path $CursorMcpBridgesRoot ''
|
|
@@ -818,6 +823,8 @@ Write-Host "[8/22] argo\skills\argo-init -> $openCodeSkillDest (OpenCode)"
|
|
|
818
823
|
Copy-Tree -Source $skillSrc -Destination $openCodeSkillDest
|
|
819
824
|
Write-Host ' argo\skills\ea-human-reconcile -> $OpenCodeSkillsRoot\ea-human-reconcile (OpenCode)'
|
|
820
825
|
Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $OpenCodeSkillsRoot 'ea-human-reconcile')
|
|
826
|
+
Write-Host ' argo\skills\agent-search-diagnosis -> $OpenCodeSkillsRoot\agent-search-diagnosis (OpenCode)'
|
|
827
|
+
Copy-Tree -Source $diagSkillSrc -Destination (Join-Path $OpenCodeSkillsRoot 'agent-search-diagnosis')
|
|
821
828
|
|
|
822
829
|
Write-Host "[9/22] argo\rules\archgraph.instructions.md -> $OpenCodeAgentsPath (OpenCode global AGENTS.md)"
|
|
823
830
|
Add-AgentsRule -AgentsPath $OpenCodeAgentsPath -RulePath $ruleSrc
|
|
@@ -857,6 +864,8 @@ if ($SkipDsh) {
|
|
|
857
864
|
Copy-Tree -Source (Join-Path $argoDir 'skills\argo-init') -Destination $dshSkillDest
|
|
858
865
|
Write-Host ' argo\skills\ea-human-reconcile -> $DshHome\skills\ea-human-reconcile (DeepSeek Harness skill)'
|
|
859
866
|
Copy-Tree -Source (Join-Path $argoDir 'skills\ea-human-reconcile') -Destination (Join-Path (Join-Path $DshHome 'skills') 'ea-human-reconcile')
|
|
867
|
+
Write-Host ' argo\skills\agent-search-diagnosis -> $DshHome\skills\agent-search-diagnosis (DeepSeek Harness skill)'
|
|
868
|
+
Copy-Tree -Source $diagSkillSrc -Destination (Join-Path (Join-Path $DshHome 'skills') 'agent-search-diagnosis')
|
|
860
869
|
|
|
861
870
|
Write-Host "[17/22] argo\rules\<WakeupGuideline> -> $DshHome\plugins\dsh-argo-wakeup\index.js (DeepSeek Harness wakeup plugin)"
|
|
862
871
|
$wakeupDshPath = New-DshWakeupPlugin -DshHome $DshHome -RuleText $ruleSrcContent
|
|
@@ -918,8 +927,10 @@ if ($SkipOpenClaw) {
|
|
|
918
927
|
|
|
919
928
|
Write-Host "[21/22] argo\skills\argo-init -> $openClawSkillDest (OpenClaw managed skill, all agents)"
|
|
920
929
|
Copy-Tree -Source (Join-Path $argoDir 'skills\argo-init') -Destination $openClawSkillDest
|
|
921
|
-
|
|
922
|
-
|
|
930
|
+
Write-Host ' argo\skills\ea-human-reconcile -> $OpenClawHome\skills\ea-human-reconcile (OpenClaw managed skill, all agents)'
|
|
931
|
+
Copy-Tree -Source (Join-Path $argoDir 'skills\ea-human-reconcile') -Destination (Join-Path (Join-Path $OpenClawHome 'skills') 'ea-human-reconcile')
|
|
932
|
+
Write-Host ' argo\skills\agent-search-diagnosis -> $OpenClawHome\skills\agent-search-diagnosis (OpenClaw managed skill, all agents)'
|
|
933
|
+
Copy-Tree -Source $diagSkillSrc -Destination (Join-Path (Join-Path $OpenClawHome 'skills') 'agent-search-diagnosis')
|
|
923
934
|
|
|
924
935
|
Write-Host ' OpenClaw injects AGENTS.md into Project Context on every session, so the wakeup'
|
|
925
936
|
Write-Host ' gate (UNCONDITIONAL STARTUP GATE) is active on the next OpenClaw session; restart'
|
package/package.json
CHANGED
|
@@ -1,52 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "archgraph-argo",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Deploy the ArchGraph ARGO toolchain, skills, and rules (schema, scripts, argo-init skill, global rule) with one command.",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"bin": {
|
|
7
|
-
"argo-deploy": "bin/argo-deploy.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"argo/scripts",
|
|
11
|
-
"argo/schema",
|
|
12
|
-
"argo/defaults",
|
|
13
|
-
"argo/agents",
|
|
14
|
-
"argo/plugins",
|
|
15
|
-
"argo/mcp-bridges",
|
|
16
|
-
"argo/skills/argo-init",
|
|
17
|
-
"argo/skills/ea-human-reconcile",
|
|
18
|
-
"argo/
|
|
19
|
-
"argo/
|
|
20
|
-
"argo
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"dsh-argo-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"./dsh-argo-
|
|
31
|
-
"./
|
|
32
|
-
"./
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "archgraph-argo",
|
|
3
|
+
"version": "0.26.1",
|
|
4
|
+
"description": "Deploy the ArchGraph ARGO toolchain, skills, and rules (schema, scripts, argo-init skill, global rule) with one command.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"argo-deploy": "bin/argo-deploy.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"argo/scripts",
|
|
11
|
+
"argo/schema",
|
|
12
|
+
"argo/defaults",
|
|
13
|
+
"argo/agents",
|
|
14
|
+
"argo/plugins",
|
|
15
|
+
"argo/mcp-bridges",
|
|
16
|
+
"argo/skills/argo-init",
|
|
17
|
+
"argo/skills/ea-human-reconcile",
|
|
18
|
+
"argo/skills/agent-search-diagnosis",
|
|
19
|
+
"argo/rules",
|
|
20
|
+
"argo/package.json",
|
|
21
|
+
"argo/.env.example",
|
|
22
|
+
"vendor",
|
|
23
|
+
"install-argo.ps1",
|
|
24
|
+
"bin",
|
|
25
|
+
"cordis.patch.yml",
|
|
26
|
+
"dsh-argo-workspace",
|
|
27
|
+
"dsh-argo-wakeup"
|
|
28
|
+
],
|
|
29
|
+
"exports": {
|
|
30
|
+
"./dsh-argo-workspace": "./dsh-argo-workspace/index.js",
|
|
31
|
+
"./dsh-argo-wakeup": "./dsh-argo-wakeup/index.js",
|
|
32
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"dsh": {
|
|
36
|
+
"bundle": {
|
|
37
|
+
"patch": "./cordis.patch.yml"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"neo4j-driver": "^6.2.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
45
|
+
"playwright": "1.61.1"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"test": "node --test \"tests/*.test.js\""
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
}
|
|
53
|
+
}
|