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 beforeCount = nextDocument.views.length;
1233
- nextDocument.views = nextDocument.views.filter(view => view.view_id !== mutation.view_id);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archgraph-argo",
3
- "version": "0.10.10",
3
+ "version": "0.10.12",
4
4
  "description": "Deploy the ArchGraph ARGO toolchain, skills, and rules (schema, scripts, argo-init skill, global rule) with one command.",
5
5
  "license": "MIT",
6
6
  "bin": {