archgraph-argo 0.10.10 → 0.10.12
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.
|
@@ -23,6 +23,9 @@ const {
|
|
|
23
23
|
getWorkspaceRoot,
|
|
24
24
|
resolveArgoPath,
|
|
25
25
|
} = require('./argo-paths.js');
|
|
26
|
+
const {
|
|
27
|
+
repairSubdiagramViews,
|
|
28
|
+
} = require('./repair-subdiagram-views.js');
|
|
26
29
|
|
|
27
30
|
const REQUIRED_TOOL_NAMES = [
|
|
28
31
|
'getSystemArchitecture',
|
|
@@ -51,6 +54,16 @@ async function main() {
|
|
|
51
54
|
});
|
|
52
55
|
report.mcp = verifyArgoMcpServer({ workspaceRoot });
|
|
53
56
|
report.systemArchitecture = await verifyCanonicalSystemArchitecture();
|
|
57
|
+
report.subdiagramViews = report.systemArchitecture.status === 'ok'
|
|
58
|
+
? await ensureSubdiagramViewsConsistency({
|
|
59
|
+
checkOnly: options.checkOnly,
|
|
60
|
+
workspaceRoot,
|
|
61
|
+
})
|
|
62
|
+
: {
|
|
63
|
+
status: 'skipped',
|
|
64
|
+
mode: options.checkOnly ? 'check' : 'fix-direct',
|
|
65
|
+
reason: 'system architecture invalid; subdiagram_views repair skipped',
|
|
66
|
+
};
|
|
54
67
|
report.neo4j = await ensureNeo4jProjection({ checkOnly: options.checkOnly });
|
|
55
68
|
report.semanticLifecycle = await ensureCanonicalSemanticLifecycle({
|
|
56
69
|
checkOnly: options.checkOnly,
|
|
@@ -77,6 +90,9 @@ async function main() {
|
|
|
77
90
|
if (report.semanticLifecycle && report.semanticLifecycle.status === 'failed') {
|
|
78
91
|
report.status = 'failed';
|
|
79
92
|
}
|
|
93
|
+
if (report.subdiagramViews && report.subdiagramViews.status === 'failed') {
|
|
94
|
+
report.status = 'failed';
|
|
95
|
+
}
|
|
80
96
|
|
|
81
97
|
writeJson(reportPath, report);
|
|
82
98
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -264,6 +280,34 @@ async function verifyCanonicalSystemArchitecture() {
|
|
|
264
280
|
};
|
|
265
281
|
}
|
|
266
282
|
|
|
283
|
+
async function ensureSubdiagramViewsConsistency({ checkOnly, workspaceRoot }) {
|
|
284
|
+
const mode = checkOnly ? 'check' : 'fix-direct';
|
|
285
|
+
try {
|
|
286
|
+
const result = await repairSubdiagramViews({
|
|
287
|
+
workspaceRoot,
|
|
288
|
+
architecturePath: DEFAULT_GRAPH_PATH,
|
|
289
|
+
mode,
|
|
290
|
+
});
|
|
291
|
+
return {
|
|
292
|
+
status: result.status,
|
|
293
|
+
mode,
|
|
294
|
+
graphPath: result.graphPath,
|
|
295
|
+
driftCount: result.driftCount,
|
|
296
|
+
fixedCount: result.fixedCount,
|
|
297
|
+
written: Boolean(result.written),
|
|
298
|
+
backupPath: result.backupPath || null,
|
|
299
|
+
reports: result.reports || [],
|
|
300
|
+
...(result.error === undefined ? {} : { error: result.error }),
|
|
301
|
+
};
|
|
302
|
+
} catch (error) {
|
|
303
|
+
return {
|
|
304
|
+
status: 'failed',
|
|
305
|
+
mode,
|
|
306
|
+
error: String(error && error.message ? error.message : error),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
267
311
|
async function ensureNeo4jProjection({ checkOnly }) {
|
|
268
312
|
const config = getNeo4jConfig();
|
|
269
313
|
const dirtyBefore = getNeo4jGraphSyncState(DEFAULT_GRAPH_PATH);
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Repair the parent->child side of the sub-diagram link.
|
|
4
|
+
//
|
|
5
|
+
// The ARGO MCP view-write path maintains `view.parent_element_id` (child -> parent)
|
|
6
|
+
// but historically never updated the mirror field `element.subdiagram_views`
|
|
7
|
+
// (parent -> child). This script rebuilds every element's `subdiagram_views`
|
|
8
|
+
// from the canonical source of truth — the views' `parent_element_id` — so the
|
|
9
|
+
// two sides agree again.
|
|
10
|
+
//
|
|
11
|
+
// Modes:
|
|
12
|
+
// --check (default) dry-run: print the per-element diff, exit 1 if any drift.
|
|
13
|
+
// --fix apply the corrections. For the intent graph
|
|
14
|
+
// (design/KG/SystemArchitecture.json) this writes THROUGH the ARGO MCP
|
|
15
|
+
// (applySystemArchitectureMutation), never touching the file directly.
|
|
16
|
+
// --direct (with --fix) write the JSON file directly with a .bak backup. Use for
|
|
17
|
+
// non-intent-graph copies (KGlibrary samples, defaults template, EA exports)
|
|
18
|
+
// that may not satisfy the intent-graph validation rules.
|
|
19
|
+
//
|
|
20
|
+
// Options:
|
|
21
|
+
// --path <p> Graph JSON path (default: design/KG/SystemArchitecture.json).
|
|
22
|
+
// Relative paths resolve against --workspaceRoot.
|
|
23
|
+
// --workspaceRoot <dir> Workspace root (default: the repository root).
|
|
24
|
+
|
|
25
|
+
const fs = require('node:fs');
|
|
26
|
+
const path = require('node:path');
|
|
27
|
+
|
|
28
|
+
const { applyMutations, callTool } = require('./systemarchitecture-mcp-server.js');
|
|
29
|
+
|
|
30
|
+
const DEFAULT_GRAPH_PATH = 'design/KG/SystemArchitecture.json';
|
|
31
|
+
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const args = { check: true, fix: false, direct: false, graphPath: DEFAULT_GRAPH_PATH };
|
|
34
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
35
|
+
const arg = argv[i];
|
|
36
|
+
if (arg === '--check') {
|
|
37
|
+
args.check = true;
|
|
38
|
+
args.fix = false;
|
|
39
|
+
} else if (arg === '--fix') {
|
|
40
|
+
args.fix = true;
|
|
41
|
+
args.check = false;
|
|
42
|
+
} else if (arg === '--direct') {
|
|
43
|
+
args.direct = true;
|
|
44
|
+
} else if (arg === '--path' && i + 1 < argv.length) {
|
|
45
|
+
args.graphPath = argv[i + 1];
|
|
46
|
+
i += 1;
|
|
47
|
+
} else if (arg === '--workspaceRoot' && i + 1 < argv.length) {
|
|
48
|
+
args.workspaceRoot = argv[i + 1];
|
|
49
|
+
i += 1;
|
|
50
|
+
} else {
|
|
51
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return args;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveGraphPath(args) {
|
|
58
|
+
const workspaceRoot = path.resolve(args.workspaceRoot || path.resolve(__dirname, '..', '..'));
|
|
59
|
+
const resolved = path.isAbsolute(args.graphPath)
|
|
60
|
+
? args.graphPath
|
|
61
|
+
: path.resolve(workspaceRoot, args.graphPath);
|
|
62
|
+
return { workspaceRoot, absolutePath: resolved, relativePath: path.relative(workspaceRoot, resolved) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readDocument(absolutePath, label) {
|
|
66
|
+
return JSON.parse(fs.readFileSync(absolutePath, 'utf8'));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function sortedEntries(entries) {
|
|
70
|
+
if (!Array.isArray(entries)) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
return entries
|
|
74
|
+
.filter(entry => entry && typeof entry.view_id === 'string' && entry.view_id.length > 0)
|
|
75
|
+
.map(entry => ({ view_id: entry.view_id, view_name: String(entry.view_name || '') }))
|
|
76
|
+
.sort((a, b) => a.view_id.localeCompare(b.view_id));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function entriesEqual(left, right) {
|
|
80
|
+
if (left.length !== right.length) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
for (let i = 0; i < left.length; i += 1) {
|
|
84
|
+
if (left[i].view_id !== right[i].view_id || left[i].view_name !== right[i].view_name) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Map element id -> canonical [{ view_id, view_name }] derived from view.parent_element_id.
|
|
92
|
+
function computeDesired(document) {
|
|
93
|
+
const desired = new Map();
|
|
94
|
+
for (const view of document.views || []) {
|
|
95
|
+
if (!view || !view.parent_element_id || !view.view_id) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const list = desired.get(view.parent_element_id) || [];
|
|
99
|
+
if (!list.some(entry => entry.view_id === view.view_id)) {
|
|
100
|
+
list.push({ view_id: view.view_id, view_name: String(view.view_name || '') });
|
|
101
|
+
}
|
|
102
|
+
desired.set(view.parent_element_id, list);
|
|
103
|
+
}
|
|
104
|
+
return desired;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function computeMutations(document, desired) {
|
|
108
|
+
const mutations = [];
|
|
109
|
+
const reports = [];
|
|
110
|
+
for (const element of document.elements || []) {
|
|
111
|
+
if (!element || !element.id) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const canonical = sortedEntries(desired.get(element.id) || []);
|
|
115
|
+
const current = sortedEntries(element.subdiagram_views || []);
|
|
116
|
+
if (entriesEqual(current, canonical)) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
reports.push({
|
|
120
|
+
elementId: element.id,
|
|
121
|
+
elementName: element.name,
|
|
122
|
+
current,
|
|
123
|
+
canonical,
|
|
124
|
+
});
|
|
125
|
+
mutations.push({
|
|
126
|
+
type: 'updateElement',
|
|
127
|
+
id: element.id,
|
|
128
|
+
patch: { subdiagram_views: canonical },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return { mutations, reports };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function describeEntry(entry) {
|
|
135
|
+
return `${entry.view_id}:${entry.view_name}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function printReports(reports) {
|
|
139
|
+
if (reports.length === 0) {
|
|
140
|
+
console.log('OK: all elements have subdiagram_views consistent with view.parent_element_id.');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
console.log(`Found ${reports.length} element(s) with drifted subdiagram_views:`);
|
|
144
|
+
for (const report of reports) {
|
|
145
|
+
console.log(`- ${report.elementId} (${report.elementName})`);
|
|
146
|
+
console.log(` current : [${report.current.map(describeEntry).join(', ')}]`);
|
|
147
|
+
console.log(` canonical: [${report.canonical.map(describeEntry).join(', ')}]`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Rebuild each element's subdiagram_views from view.parent_element_id, returning a
|
|
152
|
+
// structured report. Modes:
|
|
153
|
+
// 'check' dry-run: report drift, never write.
|
|
154
|
+
// 'fix-direct' apply corrections by writing the JSON directly with a .bak backup.
|
|
155
|
+
// 'fix-mcp' apply corrections through ARGO MCP applySystemArchitectureMutation
|
|
156
|
+
// (only for the intent graph, whose validation rules hold).
|
|
157
|
+
async function repairSubdiagramViews({
|
|
158
|
+
workspaceRoot,
|
|
159
|
+
architecturePath = DEFAULT_GRAPH_PATH,
|
|
160
|
+
mode = 'check',
|
|
161
|
+
} = {}) {
|
|
162
|
+
const { workspaceRoot: resolvedRoot, absolutePath, relativePath } = resolveGraphPath({
|
|
163
|
+
workspaceRoot,
|
|
164
|
+
graphPath: architecturePath,
|
|
165
|
+
});
|
|
166
|
+
const document = readDocument(absolutePath, relativePath);
|
|
167
|
+
const desired = computeDesired(document);
|
|
168
|
+
const { mutations, reports } = computeMutations(document, desired);
|
|
169
|
+
|
|
170
|
+
if (mode === 'check') {
|
|
171
|
+
return {
|
|
172
|
+
status: reports.length === 0 ? 'ok' : 'failed',
|
|
173
|
+
graphPath: relativePath,
|
|
174
|
+
driftCount: reports.length,
|
|
175
|
+
fixedCount: 0,
|
|
176
|
+
written: false,
|
|
177
|
+
reports,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (reports.length === 0) {
|
|
182
|
+
return {
|
|
183
|
+
status: 'ok',
|
|
184
|
+
graphPath: relativePath,
|
|
185
|
+
driftCount: 0,
|
|
186
|
+
fixedCount: 0,
|
|
187
|
+
written: false,
|
|
188
|
+
reports: [],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (mode === 'fix-direct') {
|
|
193
|
+
const { document: fixedDocument } = applyMutations(document, mutations);
|
|
194
|
+
const backupPath = `${absolutePath}.bak`;
|
|
195
|
+
fs.copyFileSync(absolutePath, backupPath);
|
|
196
|
+
fs.writeFileSync(absolutePath, `${JSON.stringify(fixedDocument, null, 2)}\n`, 'utf8');
|
|
197
|
+
return {
|
|
198
|
+
status: 'ok',
|
|
199
|
+
graphPath: relativePath,
|
|
200
|
+
driftCount: reports.length,
|
|
201
|
+
fixedCount: reports.length,
|
|
202
|
+
written: true,
|
|
203
|
+
backupPath,
|
|
204
|
+
reports,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (mode === 'fix-mcp') {
|
|
209
|
+
const result = await callTool(
|
|
210
|
+
'applySystemArchitectureMutation',
|
|
211
|
+
{
|
|
212
|
+
mutations,
|
|
213
|
+
workspaceRoot: resolvedRoot,
|
|
214
|
+
architecturePath: relativePath,
|
|
215
|
+
},
|
|
216
|
+
undefined,
|
|
217
|
+
);
|
|
218
|
+
if (result && result.status === 'passed' && result.written === true) {
|
|
219
|
+
return {
|
|
220
|
+
status: 'ok',
|
|
221
|
+
graphPath: relativePath,
|
|
222
|
+
driftCount: reports.length,
|
|
223
|
+
fixedCount: reports.length,
|
|
224
|
+
written: true,
|
|
225
|
+
reports,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
status: 'failed',
|
|
230
|
+
graphPath: relativePath,
|
|
231
|
+
driftCount: reports.length,
|
|
232
|
+
fixedCount: 0,
|
|
233
|
+
written: false,
|
|
234
|
+
reports,
|
|
235
|
+
error: result && Array.isArray(result.errors) ? result.errors : result,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
throw new Error(`Unsupported repair mode: ${mode}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function main() {
|
|
243
|
+
const args = parseArgs(process.argv.slice(2));
|
|
244
|
+
const mode = args.check ? 'check' : (args.direct ? 'fix-direct' : 'fix-mcp');
|
|
245
|
+
const result = await repairSubdiagramViews({
|
|
246
|
+
workspaceRoot: args.workspaceRoot,
|
|
247
|
+
architecturePath: args.graphPath,
|
|
248
|
+
mode,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
if (mode === 'check') {
|
|
252
|
+
printReports(result.reports);
|
|
253
|
+
process.exitCode = result.driftCount === 0 ? 0 : 1;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (result.status !== 'ok') {
|
|
258
|
+
console.error(`Repair failed: ${JSON.stringify(result.error === undefined ? result : result.error, null, 2)}`);
|
|
259
|
+
process.exitCode = 1;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (result.driftCount === 0) {
|
|
264
|
+
console.log('Nothing to fix: subdiagram_views already consistent.');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (mode === 'fix-direct') {
|
|
269
|
+
console.log(`Fixed ${result.fixedCount} element(s) directly in ${result.graphPath} (backup: ${result.backupPath}).`);
|
|
270
|
+
} else {
|
|
271
|
+
console.log(`Fixed ${result.fixedCount} element(s) through ARGO MCP applySystemArchitectureMutation.`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (require.main === module) {
|
|
276
|
+
main().catch((error) => {
|
|
277
|
+
console.error(`Repair failed: ${String(error && error.stack ? error.stack : error)}`);
|
|
278
|
+
process.exitCode = 1;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = {
|
|
283
|
+
computeDesired,
|
|
284
|
+
computeMutations,
|
|
285
|
+
parseArgs,
|
|
286
|
+
repairSubdiagramViews,
|
|
287
|
+
sortedEntries,
|
|
288
|
+
};
|
|
@@ -1204,6 +1204,7 @@ function applyMutations(document, mutations) {
|
|
|
1204
1204
|
throw new Error(`View '${mutation.view.view_id}' already exists`);
|
|
1205
1205
|
}
|
|
1206
1206
|
nextDocument.views.push(clone(mutation.view));
|
|
1207
|
+
upsertSubdiagramViewIntoElement(nextDocument, mutation.view.parent_element_id, mutation.view);
|
|
1207
1208
|
touchedViewIds.add(mutation.view.view_id);
|
|
1208
1209
|
viewLimitCheckIds.add(mutation.view.view_id);
|
|
1209
1210
|
mutationSummaries.push({ type: mutation.type, id: mutation.view.view_id });
|
|
@@ -1218,7 +1219,13 @@ function applyMutations(document, mutations) {
|
|
|
1218
1219
|
if (!view) {
|
|
1219
1220
|
throw new Error(`View '${viewId}' does not exist`);
|
|
1220
1221
|
}
|
|
1222
|
+
const oldParentId = view.parent_element_id;
|
|
1223
|
+
const oldViewId = view.view_id;
|
|
1221
1224
|
Object.assign(view, clone(mutation.patch));
|
|
1225
|
+
if (oldParentId !== view.parent_element_id) {
|
|
1226
|
+
removeSubdiagramViewFromElement(nextDocument, oldParentId, oldViewId);
|
|
1227
|
+
}
|
|
1228
|
+
upsertSubdiagramViewIntoElement(nextDocument, view.parent_element_id, view);
|
|
1222
1229
|
touchedViewIds.add(view.view_id);
|
|
1223
1230
|
if (Object.prototype.hasOwnProperty.call(mutation.patch, 'included_elements')) {
|
|
1224
1231
|
viewLimitCheckIds.add(view.view_id);
|
|
@@ -1229,11 +1236,12 @@ function applyMutations(document, mutations) {
|
|
|
1229
1236
|
|
|
1230
1237
|
if (mutation.type === 'removeView') {
|
|
1231
1238
|
requireId(mutation.view_id, 'mutation.view_id');
|
|
1232
|
-
const
|
|
1233
|
-
|
|
1234
|
-
if (nextDocument.views.length === beforeCount) {
|
|
1239
|
+
const view = findView(nextDocument.views, mutation.view_id);
|
|
1240
|
+
if (!view) {
|
|
1235
1241
|
throw new Error(`View '${mutation.view_id}' does not exist`);
|
|
1236
1242
|
}
|
|
1243
|
+
removeSubdiagramViewFromElement(nextDocument, view.parent_element_id, mutation.view_id);
|
|
1244
|
+
nextDocument.views = nextDocument.views.filter(entry => entry.view_id !== mutation.view_id);
|
|
1237
1245
|
touchedViewIds.add(mutation.view_id);
|
|
1238
1246
|
mutationSummaries.push({ type: mutation.type, id: mutation.view_id });
|
|
1239
1247
|
continue;
|
|
@@ -1311,6 +1319,36 @@ function findView(entries, viewId) {
|
|
|
1311
1319
|
return Array.isArray(entries) ? entries.find(entry => entry && entry.view_id === viewId) : undefined;
|
|
1312
1320
|
}
|
|
1313
1321
|
|
|
1322
|
+
function removeSubdiagramViewFromElement(document, elementId, viewId) {
|
|
1323
|
+
if (!elementId || !viewId) {
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
const element = findById(document.elements, elementId);
|
|
1327
|
+
if (!element || !Array.isArray(element.subdiagram_views)) {
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
element.subdiagram_views = element.subdiagram_views.filter(entry => !(entry && entry.view_id === viewId));
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function upsertSubdiagramViewIntoElement(document, elementId, view) {
|
|
1334
|
+
if (!elementId || !view || !view.view_id) {
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
const element = findById(document.elements, elementId);
|
|
1338
|
+
if (!element) {
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
if (!Array.isArray(element.subdiagram_views)) {
|
|
1342
|
+
element.subdiagram_views = [];
|
|
1343
|
+
}
|
|
1344
|
+
const existing = element.subdiagram_views.find(entry => entry && entry.view_id === view.view_id);
|
|
1345
|
+
if (existing) {
|
|
1346
|
+
existing.view_name = view.view_name;
|
|
1347
|
+
} else {
|
|
1348
|
+
element.subdiagram_views.push({ view_id: view.view_id, view_name: view.view_name });
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1314
1352
|
function addUnique(existing, additions) {
|
|
1315
1353
|
const result = Array.isArray(existing) ? [...existing] : [];
|
|
1316
1354
|
for (const addition of additions) {
|
package/package.json
CHANGED