archgraph-argo 0.10.8 → 0.10.10

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.
package/README.md CHANGED
@@ -19,6 +19,20 @@ engineering:
19
19
 
20
20
  Editable source: [`docs/diagrams/global-architecture.excalidraw`](docs/diagrams/global-architecture.excalidraw)
21
21
 
22
+ ## Supported Harnesses
23
+
24
+ ArchGraph deploys the ARGO toolchain to all major coding-agent environments:
25
+
26
+ | Harness | MCP Server | Skills | Rules / Instructions | Agents | Wakeup Gate |
27
+ |--------------------|:----------:|:------:|:--------------------:|:------:|:-----------:|
28
+ | GitHub Copilot | ✓ | ✓ | ✓ | ✓ | — |
29
+ | Cursor | ✓ | ✓ | ✓ | ✓ | — |
30
+ | OpenCode | ✓ | ✓ | ✓ | ✓ | ✓ |
31
+ | DeepSeek Harness | ✓ | ✓ | ✓ | ✓ | ✓ |
32
+
33
+ A single `argo-deploy` registers the `argo` MCP server and installs all artifacts into each harness
34
+ automatically.
35
+
22
36
  ## Install
23
37
 
24
38
  ```powershell
@@ -1,3 +1,4 @@
1
+ const crypto = require('node:crypto');
1
2
  const fs = require('node:fs');
2
3
  const path = require('node:path');
3
4
 
@@ -145,6 +146,23 @@ function readArchitectureDocument(architecturePath = DEFAULT_GRAPH_PATH) {
145
146
  }
146
147
  }
147
148
 
149
+ function digestCanonicalArchitecture(architecturePath = DEFAULT_GRAPH_PATH) {
150
+ const absolutePath = resolveArchitecturePath(architecturePath);
151
+ try {
152
+ return crypto.createHash('sha256').update(fs.readFileSync(absolutePath)).digest('hex');
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+
158
+ function isNeo4jGraphSyncStale(syncState, currentDigest) {
159
+ if (!syncState || typeof syncState !== 'object') return false;
160
+ if (!currentDigest || typeof currentDigest !== 'string') return false;
161
+ const hasDigest = typeof syncState.canonicalDigest === 'string' && syncState.canonicalDigest.length > 0;
162
+ if (hasDigest) return syncState.canonicalDigest !== currentDigest;
163
+ return typeof syncState.lastSuccessAt === 'string' && syncState.lastSuccessAt.length > 0;
164
+ }
165
+
148
166
  function sanitizeProps(properties) {
149
167
  return Object.fromEntries(
150
168
  Object.entries(properties).filter(([, value]) => value !== undefined),
@@ -226,12 +244,14 @@ function markNeo4jSyncDirty(architecturePath, error) {
226
244
 
227
245
  function markNeo4jSyncClean(architecturePath, verification) {
228
246
  const current = getNeo4jGraphSyncState(architecturePath);
247
+ const canonicalDigest = digestCanonicalArchitecture(architecturePath);
229
248
  return updateNeo4jGraphSyncState(architecturePath, {
230
249
  dirty: false,
231
250
  lastError: undefined,
232
251
  lastRecoveredAt: current.dirty ? new Date().toISOString() : current.lastRecoveredAt,
233
252
  lastSuccessAt: new Date().toISOString(),
234
253
  lastVerifiedCounts: verification ? verification.actual : current.lastVerifiedCounts,
254
+ canonicalDigest,
235
255
  });
236
256
  }
237
257
 
@@ -599,11 +619,13 @@ async function recoverNeo4jSyncIfNeeded(options = {}) {
599
619
  }
600
620
 
601
621
  const syncState = getNeo4jGraphSyncState(architecturePath);
602
- if (!syncState.dirty) {
622
+ const stale = isNeo4jGraphSyncStale(syncState, digestCanonicalArchitecture(architecturePath));
623
+ if (!syncState.dirty && !stale) {
603
624
  return {
604
625
  attempted: false,
605
626
  eligible: true,
606
627
  dirty: false,
628
+ stale: false,
607
629
  };
608
630
  }
609
631
 
@@ -613,6 +635,7 @@ async function recoverNeo4jSyncIfNeeded(options = {}) {
613
635
  attempted: true,
614
636
  eligible: true,
615
637
  dirty: false,
638
+ stale: false,
616
639
  status: 'passed',
617
640
  graphKey: result.graphKey,
618
641
  databaseProvision: result.databaseProvision,
@@ -624,6 +647,7 @@ async function recoverNeo4jSyncIfNeeded(options = {}) {
624
647
  attempted: true,
625
648
  eligible: true,
626
649
  dirty: true,
650
+ stale: false,
627
651
  status: 'failed',
628
652
  graphKey: syncState.graphKey,
629
653
  previousFailure: syncState.lastError,
@@ -761,11 +785,13 @@ module.exports = {
761
785
  DEFAULT_GRAPH_PATH,
762
786
  buildGraphKey,
763
787
  createDriver,
788
+ digestCanonicalArchitecture,
764
789
  ensureDatabaseExists,
765
790
  getNeo4jGraphSyncState,
766
791
  getNeo4jConfig,
767
792
  getDefaultNeo4jDatabaseName,
768
793
  isCanonicalArchitecturePath,
794
+ isNeo4jGraphSyncStale,
769
795
  markNeo4jSyncDirty,
770
796
  readArchitectureDocument,
771
797
  readNeo4jSyncState,
@@ -0,0 +1,6 @@
1
+ # ArchGraph ARGO bundle patch:随 bundle 安装,按包名引用包内插件(非 file://)。
2
+ - insert:
3
+ - id: argo-workspace
4
+ name: archgraph-argo/dsh-argo-workspace
5
+ - id: argo-wakeup
6
+ name: archgraph-argo/dsh-argo-wakeup
@@ -0,0 +1,17 @@
1
+ // dsh-argo-wakeup - generated by install-argo.ps1 from
2
+ // argo/rules/archgraph.instructions.md (single source of truth: the rule file;
3
+ // this file is a deployment artifact, do not edit by hand).
4
+ // DeepSeek Harness equivalent of the OpenCode hook argo/plugins/argo-wakeup.js:
5
+ // registers the unconditional wakeup gate as the first system-prompt section
6
+ // after the harness identity, so every session identifies its Business Actor
7
+ // through the argo MCP server before responding.
8
+ export const name = 'dsh-argo-wakeup'
9
+ export const inject = ['systemPrompt']
10
+ const WAKEUP_GATE = "STEP 0 — UNCONDITIONAL STARTUP GATE. This applies to the very first message of every session, including greetings, casual chat, and questions; it may never be skipped, deferred, or treated as optional.\r\n1. Your very FIRST tool call in the session MUST be an ARGO MCP query that lists all `Business Actor`s (getSystemArchitecture with purpose \"audit\" and subject \"Business Actor\"). Identify which `Business Actor` you are. If you are not sure, consult your human partner to confirm your role before doing anything else.\r\n2. Restore your long-term memory: the SUBVIEW hierarchy mounted under that `Business Actor` element — the Views whose `parent_element_id` points to this Actor, plus the elements, relationships, and further nested sub-views inside them (NOT the View that merely includes the Actor in its `included_elements`) — and load it into session memory.\r\n3. If the `agent` attribute of the confirmed Actor differs from your current Agent type, switch to that Agent type, or delegate to an Agent of that type per `\u003cCoperationGuideline\u003e` item 2.\r\n4. If the ARGO MCP is unavailable or errors, state that explicitly to the user before doing anything else. Only after completing this gate may you respond to the user or take any other action."
11
+ export function apply(ctx) {
12
+ ctx.effect(() => ctx.systemPrompt.section({
13
+ name: 'argo:wakeup',
14
+ order: -90,
15
+ text: WAKEUP_GATE,
16
+ }), 'argo:wakeup.section()')
17
+ }
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,141 @@
1
+ // dsh-argo-workspace - generated by install-argo.ps1 (single source of truth:
2
+ // the installer; this file is a deployment artifact, do not edit by hand).
3
+ //
4
+ // Direct MCP bridge to the argo server for DeepSeek Harness. Registers every
5
+ // argo tool as mcp__argo__* and injects the current session's workspace
6
+ // directory (SessionHeader.cwd) as the per-call `workspaceRoot` argument, so
7
+ // one dsh instance follows whichever workspace the user switched to - the
8
+ // model sees no extra parameters and no internal tool names. The argo server
9
+ // honors the injected workspaceRoot unconditionally.
10
+ //
11
+ // Zero dependencies on purpose: implements the minimal MCP stdio client
12
+ // (JSON-RPC 2.0, one JSON object per line) with Node built-ins only, so the
13
+ // plugin runs from any DSH layout without resolving @modelcontextprotocol/sdk.
14
+ import { spawn } from 'node:child_process'
15
+ import readline from 'node:readline'
16
+ import { fileURLToPath } from 'node:url'
17
+
18
+ export const name = 'dsh-argo-workspace'
19
+ export const inject = ['tools']
20
+
21
+ // 包内默认:<pkg>/argo/scripts/argo-mcp-server.js —— 随包走,跨机器可移植。
22
+ const DEFAULT_SERVER_PATH = fileURLToPath(
23
+ new URL('../argo/scripts/argo-mcp-server.js', import.meta.url),
24
+ )
25
+
26
+ /** Minimal MCP stdio client over the argo server process. */
27
+ function createArgoClient(serverPath, env, cwd) {
28
+ const child = spawn('node', [serverPath], {
29
+ stdio: ['pipe', 'pipe', 'pipe'],
30
+ env,
31
+ ...(cwd ? { cwd } : {}),
32
+ })
33
+ const pending = new Map()
34
+ let nextId = 1
35
+ readline.createInterface({ input: child.stdout }).on('line', (line) => {
36
+ let message
37
+ try { message = JSON.parse(line) } catch { return }
38
+ if (message && typeof message.id === 'number' && pending.has(message.id)) {
39
+ const { resolve, reject } = pending.get(message.id)
40
+ pending.delete(message.id)
41
+ if (message.error) reject(new Error(JSON.stringify(message.error)))
42
+ else resolve(message.result)
43
+ }
44
+ })
45
+ child.stderr.on('data', () => {}) // drain; the argo server logs to stderr
46
+ const request = (method, params) => new Promise((resolve, reject) => {
47
+ const id = nextId++
48
+ pending.set(id, { resolve, reject })
49
+ child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n')
50
+ })
51
+ return {
52
+ request,
53
+ notify: (method, params) => {
54
+ child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method, params }) + '\n')
55
+ },
56
+ close: () => child.kill(),
57
+ }
58
+ }
59
+
60
+ /** Connect, list tools, register them, and keep the client until disposal. */
61
+ export async function apply(ctx, config) {
62
+ const serverPath = config.serverPath
63
+ ?? process.env.ARGO_SERVER_PATH
64
+ ?? DEFAULT_SERVER_PATH
65
+ const workspaces = Array.isArray(config.workspaces) ? config.workspaces : []
66
+ const env = { ...process.env }
67
+ if (workspaces.length > 0) env.ARGO_WORKSPACE_ROOTS = workspaces.join(';')
68
+ const client = createArgoClient(serverPath, env, config.cwd)
69
+ ctx.effect(() => () => client.close(), 'dsh-argo-workspace.dispose()')
70
+
71
+ let tools = []
72
+ try {
73
+ await client.request('initialize', {
74
+ protocolVersion: '2024-11-05',
75
+ capabilities: {},
76
+ clientInfo: { name: 'dsh-argo-workspace', version: '0.1.0' },
77
+ })
78
+ client.notify('notifications/initialized', {})
79
+ const listed = await client.request('tools/list', {})
80
+ tools = (listed && Array.isArray(listed.tools)) ? listed.tools : []
81
+ } catch (error) {
82
+ console.warn(`[dsh-argo-workspace] failed to connect to the argo MCP server (${serverPath}): ${error && error.message ? error.message : error}`)
83
+ return
84
+ }
85
+
86
+ const disposers = []
87
+ for (const tool of tools) {
88
+ const publicName = 'mcp__argo__' + tool.name
89
+ disposers.push(ctx.tools.register({
90
+ name: publicName,
91
+ description: tool.description ?? '',
92
+ parameters: tool.inputSchema,
93
+ output: {
94
+ schema: {
95
+ type: 'object',
96
+ properties: {
97
+ content: { type: 'array', items: {} },
98
+ structuredContent: {},
99
+ },
100
+ required: ['content'],
101
+ additionalProperties: false,
102
+ },
103
+ render: (_args, value) => value.content,
104
+ },
105
+ execute: async (args, exec) => {
106
+ // SessionHeader.cwd (the durable session workspace), NOT
107
+ // requestHeader() — that returns the request EpochHeader
108
+ // (config/system/tools) which has no cwd field.
109
+ const sessionCwd = exec && exec.agent && exec.agent.session
110
+ ? exec.agent.session.header?.cwd
111
+ : undefined
112
+ const injected = { ...(args && typeof args === 'object' ? args : {}) }
113
+ if (typeof sessionCwd === 'string' && sessionCwd !== '') {
114
+ injected.workspaceRoot = sessionCwd
115
+ }
116
+ if (exec && exec.signal && exec.signal.aborted) {
117
+ throw new Error('aborted')
118
+ }
119
+ const result = await client.request('tools/call', {
120
+ name: tool.name,
121
+ arguments: injected,
122
+ })
123
+ const text = Array.isArray(result.content)
124
+ ? result.content
125
+ .map((block) => block && block.type === 'text' && typeof block.text === 'string'
126
+ ? block.text
127
+ : JSON.stringify(block))
128
+ .join('\n')
129
+ : (result.toolResult !== undefined ? JSON.stringify(result.toolResult) : '(no output)')
130
+ if (result.isError === true) throw new Error(text)
131
+ return {
132
+ content: [{ type: 'text', text }],
133
+ ...(result.structuredContent !== undefined ? { structuredContent: result.structuredContent } : {}),
134
+ }
135
+ },
136
+ }))
137
+ }
138
+ ctx.effect(() => () => {
139
+ for (const dispose of disposers) dispose()
140
+ }, 'dsh-argo-workspace.tools')
141
+ }
@@ -0,0 +1 @@
1
+ {"type":"module"}
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] 1argo\agents -> $DshHome\.agent-presets\<id> (DeepSeek Harness agent presets)"
729
+ Write-Host "[19/19] argo\agents -> $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.8",
3
+ "version": "0.10.10",
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": {
@@ -17,11 +17,28 @@
17
17
  "argo/package.json",
18
18
  "vendor",
19
19
  "install-argo.ps1",
20
- "bin"
20
+ "bin",
21
+ "cordis.patch.yml",
22
+ "dsh-argo-workspace",
23
+ "dsh-argo-wakeup"
21
24
  ],
22
- "devDependencies": {
25
+ "exports": {
26
+ "./dsh-argo-workspace": "./dsh-argo-workspace/index.js",
27
+ "./dsh-argo-wakeup": "./dsh-argo-wakeup/index.js",
28
+ "./cordis.patch.yml": "./cordis.patch.yml",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "dsh": {
32
+ "bundle": {
33
+ "patch": "./cordis.patch.yml"
34
+ }
35
+ },
36
+ "dependencies": {
23
37
  "neo4j-driver": "^6.2.0"
24
38
  },
39
+ "devDependencies": {
40
+ "@resvg/resvg-js": "^2.6.2"
41
+ },
25
42
  "scripts": {
26
43
  "test": "node --test \"tests/*.test.js\""
27
44
  },