archgraph-argo 0.10.20 → 0.10.22

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.
@@ -99,7 +99,7 @@ You MUST read/write the intent architecture through the tools provided by the AR
99
99
  16. queryNeo4jGraph: run a read-only Cypher query against the Neo4j structural projection of the intent graph, or request the projection schema with {schema: true}. Use for structural/type-based graph queries (see <GraphQueryGuideline>).
100
100
 
101
101
  <GraphQueryGuideline>
102
- For structural/type-based graph lookups, use the read-only Neo4j Cypher interface `queryNeo4jGraph` instead of reading the canonical JSON file directly. It never mutates the graph; all writes still go through the mutation tools.
102
+ For structural/type-based graph lookups, use the read-only Neo4j Cypher interface `queryNeo4jGraph` instead of reading the canonical JSON file directly. It never mutates the canonical graph; all writes still go through the mutation tools.
103
103
  1. Ask for the projection schema first: call `queryNeo4jGraph` with {schema: true} to learn node labels (ArchitectureGraph, Element, ArchitectureRelationship, View), relationship types (OWNS_ELEMENT, OWNS_RELATIONSHIP, OWNS_VIEW, RELATIONSHIP_SOURCE, RELATIONSHIP_TARGET, ARCHIMATE_RELATES, VIEW_OF, INCLUDES_ELEMENT, INCLUDES_RELATIONSHIP, HAS_SUBDIAGRAM), property keys, and the legal ArchiMate element/relationship type enums (e.g. 'Business Actor', 'Assignment').
104
104
  2. Construct a read-only Cypher statement and scope every pattern to the current graph with the server-injected `$graphKey` parameter (the value is filled by the server; the agent only writes the placeholder):
105
105
  MATCH (e:Element {graphKey: $graphKey, type: 'Business Actor'}) RETURN e.id, e.name ORDER BY e.name
@@ -35,20 +35,22 @@ function requireNeo4jDriver() {
35
35
  return neo4jDriverModule;
36
36
  }
37
37
 
38
- function getRepoRoot() {
39
- return getWorkspaceRoot();
38
+ function getRepoRoot(workspaceRoot) {
39
+ return workspaceRoot && typeof workspaceRoot === 'string' && workspaceRoot.trim() !== ''
40
+ ? path.resolve(workspaceRoot)
41
+ : getWorkspaceRoot();
40
42
  }
41
43
 
42
- function resolveArchitecturePath(architecturePath = DEFAULT_GRAPH_PATH) {
43
- return path.join(getRepoRoot(), architecturePath);
44
+ function resolveArchitecturePath(architecturePath = DEFAULT_GRAPH_PATH, workspaceRoot) {
45
+ return path.join(getRepoRoot(workspaceRoot), architecturePath);
44
46
  }
45
47
 
46
- function resolveSyncStatePath() {
47
- return path.join(getRepoRoot(), SYNC_STATE_RELATIVE_PATH);
48
+ function resolveSyncStatePath(workspaceRoot) {
49
+ return path.join(getRepoRoot(workspaceRoot), SYNC_STATE_RELATIVE_PATH);
48
50
  }
49
51
 
50
- function getDefaultNeo4jDatabaseName() {
51
- const repoName = path.basename(getRepoRoot());
52
+ function getDefaultNeo4jDatabaseName(workspaceRoot) {
53
+ const repoName = path.basename(getRepoRoot(workspaceRoot));
52
54
  const normalized = String(repoName)
53
55
  .toLowerCase()
54
56
  .replace(/[^a-z0-9.-]+/g, '-')
@@ -86,7 +88,7 @@ function getNeo4jConfig(overrides = {}) {
86
88
  uri: external.neo4jUri,
87
89
  username: external.neo4jUsername,
88
90
  password: external.neo4jPassword,
89
- database: overrides.database || process.env.ARGO_NEO4J_DATABASE || getDefaultNeo4jDatabaseName(),
91
+ database: overrides.database || process.env.ARGO_NEO4J_DATABASE || getDefaultNeo4jDatabaseName(overrides.workspaceRoot),
90
92
  };
91
93
  }
92
94
 
@@ -133,8 +135,8 @@ function createDriver(config = {}) {
133
135
  );
134
136
  }
135
137
 
136
- function readArchitectureDocument(architecturePath = DEFAULT_GRAPH_PATH) {
137
- const absolutePath = resolveArchitecturePath(architecturePath);
138
+ function readArchitectureDocument(architecturePath = DEFAULT_GRAPH_PATH, workspaceRoot) {
139
+ const absolutePath = resolveArchitecturePath(architecturePath, workspaceRoot);
138
140
  if (!fs.existsSync(absolutePath)) {
139
141
  throw new Error(`System architecture file is missing at ${architecturePath}`);
140
142
  }
@@ -146,8 +148,8 @@ function readArchitectureDocument(architecturePath = DEFAULT_GRAPH_PATH) {
146
148
  }
147
149
  }
148
150
 
149
- function digestCanonicalArchitecture(architecturePath = DEFAULT_GRAPH_PATH) {
150
- const absolutePath = resolveArchitecturePath(architecturePath);
151
+ function digestCanonicalArchitecture(architecturePath = DEFAULT_GRAPH_PATH, workspaceRoot) {
152
+ const absolutePath = resolveArchitecturePath(architecturePath, workspaceRoot);
151
153
  try {
152
154
  return crypto.createHash('sha256').update(fs.readFileSync(absolutePath)).digest('hex');
153
155
  } catch {
@@ -188,8 +190,8 @@ function createEmptySyncState() {
188
190
  };
189
191
  }
190
192
 
191
- function readNeo4jSyncState() {
192
- const statePath = resolveSyncStatePath();
193
+ function readNeo4jSyncState(workspaceRoot) {
194
+ const statePath = resolveSyncStatePath(workspaceRoot);
193
195
  if (!fs.existsSync(statePath)) {
194
196
  return createEmptySyncState();
195
197
  }
@@ -205,15 +207,15 @@ function readNeo4jSyncState() {
205
207
  }
206
208
  }
207
209
 
208
- function writeNeo4jSyncState(state) {
209
- const statePath = resolveSyncStatePath();
210
+ function writeNeo4jSyncState(state, workspaceRoot) {
211
+ const statePath = resolveSyncStatePath(workspaceRoot);
210
212
  fs.mkdirSync(path.dirname(statePath), { recursive: true });
211
213
  fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
212
214
  }
213
215
 
214
- function getNeo4jGraphSyncState(architecturePath = DEFAULT_GRAPH_PATH) {
216
+ function getNeo4jGraphSyncState(architecturePath = DEFAULT_GRAPH_PATH, workspaceRoot) {
215
217
  const graphKey = buildGraphKey(architecturePath);
216
- const state = readNeo4jSyncState();
218
+ const state = readNeo4jSyncState(workspaceRoot);
217
219
  return {
218
220
  graphKey,
219
221
  dirty: false,
@@ -221,30 +223,30 @@ function getNeo4jGraphSyncState(architecturePath = DEFAULT_GRAPH_PATH) {
221
223
  };
222
224
  }
223
225
 
224
- function updateNeo4jGraphSyncState(architecturePath, patch) {
226
+ function updateNeo4jGraphSyncState(architecturePath, patch, workspaceRoot) {
225
227
  const graphKey = buildGraphKey(architecturePath);
226
- const state = readNeo4jSyncState();
228
+ const state = readNeo4jSyncState(workspaceRoot);
227
229
  state.graphs[graphKey] = {
228
230
  graphKey,
229
231
  dirty: false,
230
232
  ...(state.graphs[graphKey] || {}),
231
233
  ...patch,
232
234
  };
233
- writeNeo4jSyncState(state);
235
+ writeNeo4jSyncState(state, workspaceRoot);
234
236
  return state.graphs[graphKey];
235
237
  }
236
238
 
237
- function markNeo4jSyncDirty(architecturePath, error) {
239
+ function markNeo4jSyncDirty(architecturePath, error, workspaceRoot) {
238
240
  return updateNeo4jGraphSyncState(architecturePath, {
239
241
  dirty: true,
240
242
  lastError: String(error && error.message ? error.message : error),
241
243
  lastFailureAt: new Date().toISOString(),
242
- });
244
+ }, workspaceRoot);
243
245
  }
244
246
 
245
- function markNeo4jSyncClean(architecturePath, verification) {
246
- const current = getNeo4jGraphSyncState(architecturePath);
247
- const canonicalDigest = digestCanonicalArchitecture(architecturePath);
247
+ function markNeo4jSyncClean(architecturePath, verification, workspaceRoot) {
248
+ const current = getNeo4jGraphSyncState(architecturePath, workspaceRoot);
249
+ const canonicalDigest = digestCanonicalArchitecture(architecturePath, workspaceRoot);
248
250
  return updateNeo4jGraphSyncState(architecturePath, {
249
251
  dirty: false,
250
252
  lastError: undefined,
@@ -252,7 +254,7 @@ function markNeo4jSyncClean(architecturePath, verification) {
252
254
  lastSuccessAt: new Date().toISOString(),
253
255
  lastVerifiedCounts: verification ? verification.actual : current.lastVerifiedCounts,
254
256
  canonicalDigest,
255
- });
257
+ }, workspaceRoot);
256
258
  }
257
259
 
258
260
  function isCanonicalArchitecturePath(architecturePath = DEFAULT_GRAPH_PATH) {
@@ -544,8 +546,9 @@ async function writeSubdiagramLinks(tx, graphKey, elements) {
544
546
 
545
547
  async function syncArchitectureToNeo4j(options = {}) {
546
548
  const architecturePath = options.architecturePath || DEFAULT_GRAPH_PATH;
549
+ const workspaceRoot = options.workspaceRoot;
547
550
  const graphKey = buildGraphKey(architecturePath);
548
- const document = options.document || readArchitectureDocument(architecturePath);
551
+ const document = options.document || readArchitectureDocument(architecturePath, workspaceRoot);
549
552
  const config = getNeo4jConfig(options);
550
553
  const driver = options.driver || createDriver(config);
551
554
  const ownDriver = !options.driver;
@@ -583,7 +586,7 @@ async function syncArchitectureToNeo4j(options = {}) {
583
586
  }
584
587
 
585
588
  if (isCanonicalArchitecturePath(architecturePath)) {
586
- markNeo4jSyncClean(architecturePath, verification);
589
+ markNeo4jSyncClean(architecturePath, verification, workspaceRoot);
587
590
  }
588
591
 
589
592
  return {
@@ -598,7 +601,7 @@ async function syncArchitectureToNeo4j(options = {}) {
598
601
  };
599
602
  } catch (error) {
600
603
  if (isCanonicalArchitecturePath(architecturePath)) {
601
- markNeo4jSyncDirty(architecturePath, error);
604
+ markNeo4jSyncDirty(architecturePath, error, workspaceRoot);
602
605
  }
603
606
  throw error;
604
607
  } finally {
@@ -610,6 +613,7 @@ async function syncArchitectureToNeo4j(options = {}) {
610
613
 
611
614
  async function recoverNeo4jSyncIfNeeded(options = {}) {
612
615
  const architecturePath = options.architecturePath || DEFAULT_GRAPH_PATH;
616
+ const workspaceRoot = options.workspaceRoot;
613
617
  if (!isCanonicalArchitecturePath(architecturePath)) {
614
618
  return {
615
619
  attempted: false,
@@ -618,8 +622,8 @@ async function recoverNeo4jSyncIfNeeded(options = {}) {
618
622
  };
619
623
  }
620
624
 
621
- const syncState = getNeo4jGraphSyncState(architecturePath);
622
- const stale = isNeo4jGraphSyncStale(syncState, digestCanonicalArchitecture(architecturePath));
625
+ const syncState = getNeo4jGraphSyncState(architecturePath, workspaceRoot);
626
+ const stale = isNeo4jGraphSyncStale(syncState, digestCanonicalArchitecture(architecturePath, workspaceRoot));
623
627
  if (!syncState.dirty && !stale) {
624
628
  return {
625
629
  attempted: false,
@@ -658,8 +662,9 @@ async function recoverNeo4jSyncIfNeeded(options = {}) {
658
662
 
659
663
  async function verifyArchitectureSync(options = {}) {
660
664
  const architecturePath = options.architecturePath || DEFAULT_GRAPH_PATH;
665
+ const workspaceRoot = options.workspaceRoot;
661
666
  const graphKey = buildGraphKey(architecturePath);
662
- const document = options.document || readArchitectureDocument(architecturePath);
667
+ const document = options.document || readArchitectureDocument(architecturePath, workspaceRoot);
663
668
  const config = getNeo4jConfig(options);
664
669
  const driver = options.driver || createDriver(config);
665
670
  const ownDriver = !options.driver;
@@ -164,6 +164,7 @@ const {
164
164
  } = require('./graph-rag/mutationEmbeddingVectorLifecycle.js');
165
165
  const {
166
166
  DEFAULT_GRAPH_PATH: NEO4J_DEFAULT_GRAPH_PATH,
167
+ assertReadOnlyCypher,
167
168
  buildGraphKey,
168
169
  buildNeo4jGraphSchema,
169
170
  recoverNeo4jSyncIfNeeded,
@@ -498,6 +499,7 @@ async function syncCanonicalStructuralProjection(request) {
498
499
  return syncArchitectureToNeo4j({
499
500
  architecturePath: context.graphPath.relativePath,
500
501
  document: context.document,
502
+ workspaceRoot: context.workspaceRoot,
501
503
  });
502
504
  }
503
505
 
@@ -556,6 +558,7 @@ async function loadContext(args = {}) {
556
558
  context.neo4jSyncRecovery = await recoverNeo4jSyncIfNeeded({
557
559
  architecturePath: graphPath.relativePath,
558
560
  document: context.document,
561
+ workspaceRoot: context.workspaceRoot,
559
562
  });
560
563
  return context;
561
564
  }
@@ -1536,6 +1539,7 @@ async function buildMutationResult(context, mutations, write) {
1536
1539
  const syncResult = await syncArchitectureToNeo4j({
1537
1540
  architecturePath: context.graphPath.relativePath,
1538
1541
  document: mutationResult.document,
1542
+ workspaceRoot: context.workspaceRoot,
1539
1543
  });
1540
1544
  result.neo4jSync = {
1541
1545
  status: 'passed',
@@ -2179,24 +2183,49 @@ async function callTool(name, args = {}, dependencies = undefined) {
2179
2183
 
2180
2184
  async function queryNeo4jGraphTool(args = {}) {
2181
2185
  const architecturePath = args.architecturePath || DEFAULT_GRAPH_PATH;
2186
+ const workspaceRoot = resolveWorkspaceRoot(args);
2182
2187
 
2183
2188
  if (args.schema === true) {
2184
- return queryNeo4jGraphSchemaResult(architecturePath);
2189
+ return queryNeo4jGraphSchemaResult(architecturePath, workspaceRoot);
2185
2190
  }
2186
2191
 
2192
+ let recovery = null;
2187
2193
  try {
2194
+ // Reject invalid/write queries before any side effect.
2195
+ assertReadOnlyCypher(args.cypher);
2196
+
2197
+ // Same projection-recovery mechanism as the loadContext-based tools
2198
+ // (e.g. getSystemArchitecture): when the sync state is dirty or the
2199
+ // recorded canonicalDigest no longer matches the canonical JSON, rebuild
2200
+ // the Neo4j projection before servicing the read-only query.
2201
+ recovery = await recoverNeo4jSyncIfNeeded({
2202
+ architecturePath,
2203
+ workspaceRoot,
2204
+ });
2205
+
2188
2206
  const result = await runNeo4jCypherQuery({
2189
2207
  architecturePath,
2190
2208
  cypher: args.cypher,
2209
+ workspaceRoot,
2191
2210
  });
2192
- return toolResult({
2211
+ const payload = {
2193
2212
  status: 'passed',
2194
2213
  architecturePath: result.architecturePath,
2195
2214
  graphKey: result.graphKey,
2196
2215
  database: result.database,
2197
2216
  records: result.records,
2198
2217
  summary: result.summary,
2199
- });
2218
+ };
2219
+ if (recovery.attempted) {
2220
+ payload.neo4jRecovery = recovery;
2221
+ if (recovery.status === 'failed') {
2222
+ payload.warnings = [
2223
+ `Neo4j automatic resync failed before servicing ${architecturePath}: ${recovery.error}`,
2224
+ 'The read-only query still ran against the current projection. Neo4j will be retried on the next canonical read or write, or you can run node .argo/scripts/syncSystemArchitectureToNeo4j.js manually.',
2225
+ ];
2226
+ }
2227
+ }
2228
+ return toolResult(payload);
2200
2229
  } catch (error) {
2201
2230
  return toolResult(queryError(
2202
2231
  error && error.category ? error.category : 'NEO4J_QUERY_FAILED',
@@ -2207,17 +2236,18 @@ async function queryNeo4jGraphTool(args = {}) {
2207
2236
  ...(error && error.clause ? { clause: error.clause } : {}),
2208
2237
  ...(error && error.queriedDatabase ? { queriedDatabase: error.queriedDatabase } : {}),
2209
2238
  ...(error && error.expectedDatabase ? { expectedDatabase: error.expectedDatabase } : {}),
2239
+ ...(recovery && recovery.attempted ? { neo4jRecovery: recovery } : {}),
2210
2240
  },
2211
2241
  ));
2212
2242
  }
2213
2243
  }
2214
2244
 
2215
- function queryNeo4jGraphSchemaResult(architecturePath) {
2245
+ function queryNeo4jGraphSchemaResult(architecturePath, workspaceRoot) {
2216
2246
  const schema = buildNeo4jGraphSchema(architecturePath);
2217
2247
  let typeEnums = {};
2218
2248
  try {
2219
- const workspaceRoot = resolveWorkspaceRoot({ architecturePath });
2220
- const schemaPath = resolveSchemaPath(workspaceRoot);
2249
+ const resolvedRoot = workspaceRoot || resolveWorkspaceRoot({ architecturePath });
2250
+ const schemaPath = resolveSchemaPath(resolvedRoot);
2221
2251
  const jsonSchema = readJson(schemaPath.absolutePath, schemaPath.relativePath);
2222
2252
  typeEnums = {
2223
2253
  archimateElementTypes: (jsonSchema.$defs.archimateElementType || {}).enum || [],
package/install-argo.ps1 CHANGED
@@ -726,7 +726,7 @@ if ($SkipDsh) {
726
726
  Write-DshManagedBlock -Path $patchPath -Block $block -MarkerStart '# BEGIN ArchGraph ARGO deployment' -MarkerEnd '# END ArchGraph ARGO deployment'
727
727
  }
728
728
 
729
- Write-Host "[19/19] argo\agents9 -> $DshHome\.agent-presets\<id> (DeepSeek Harness agent presets)"
729
+ Write-Host "[19/19] argo\agents11 -> $DshHome\.agent-presets\<id> (DeepSeek Harness agent presets)"
730
730
  New-DshAgentPresets -DshHome $DshHome -AgentsSrc (Join-Path $argoDir 'agents')
731
731
 
732
732
  Write-Host ' Restart `dsh web` to activate the MCP bridge and the wakeup plugin;'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archgraph-argo",
3
- "version": "0.10.20",
3
+ "version": "0.10.22",
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": {