archgraph-argo 0.11.0 → 0.12.0

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,109 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WP2791 Node direct .qea projection CLI (no EA, no third-party deps).
5
+ // node argo/scripts/ea-qea-sync.js --graph <json> --qea <file.qea> --mode import|sync|full|export|watch
6
+ // [--delete-confirm-file <f> | -y] [--dry-run] [--snapshot-dir <dir>] [--out <file>] [--no-backup]
7
+ //
8
+ // import/sync : project design/KG graph into the .qea (update-in-place, batch INSERT,
9
+ // full : clear projection-owned content (kg_sync_meta + ArchGraph Sync subtree) then
10
+ // unchanged fingerprint skip, opt-in EA-only delete with confirmation).
11
+ // export : read .qea back into graph-shaped JSON (roundtrip comparable).
12
+ // watch : on graph JSON change, run sync (fs.watch + polling fallback).
13
+ // Every write first snapshots the target as <file>_before_sync_<ts> unless --no-backup.
14
+
15
+ const path = require('node:path');
16
+ const fs = require('node:fs');
17
+ const lib = require(path.join(__dirname, 'ea-qea-sync-lib.js'));
18
+
19
+ function parseArgs(argv) {
20
+ const args = { mode: 'sync', graph: '', qea: '', allowDelete: false, deleteConfirmFile: '', dryRun: false, snapshotDir: '', out: '', noBackup: false, intervalMs: 2000 };
21
+ for (let i = 0; i < argv.length; i++) {
22
+ const a = argv[i];
23
+ const next = () => (i + 1 < argv.length ? argv[++i] : '');
24
+ if (a === '--graph') { args.graph = next(); }
25
+ else if (a === '--qea') { args.qea = next(); }
26
+ else if (a === '--mode') { args.mode = next(); }
27
+ else if (a === '--delete-confirm-file') { args.deleteConfirmFile = next(); args.allowDelete = true; }
28
+ else if (a === '-y' || a === '--yes') { args.allowDelete = true; }
29
+ else if (a === '--dry-run') { args.dryRun = true; }
30
+ else if (a === '--snapshot-dir') { args.snapshotDir = next(); }
31
+ else if (a === '--out') { args.out = next(); }
32
+ else if (a === '--no-backup') { args.noBackup = true; }
33
+ else if (a === '--interval') { args.intervalMs = Number(next()) || 2000; }
34
+ else if (a.startsWith('-')) { /* ignore unknown */ }
35
+ else if (args.modeSet === undefined) { /* positional not used */ }
36
+ }
37
+ return args;
38
+ }
39
+
40
+ function readGraph(jsonPath) {
41
+ const raw = fs.readFileSync(jsonPath, 'utf8').replace(/^\uFEFF/, '');
42
+ return JSON.parse(raw);
43
+ }
44
+ function confirmDelete(args) {
45
+ if (args.allowDelete) { return true; }
46
+ if (args.deleteConfirmFile) {
47
+ try {
48
+ const text = fs.readFileSync(args.deleteConfirmFile, 'utf8').trim().toLowerCase();
49
+ return text.indexOf('delete') >= 0;
50
+ } catch { return false; }
51
+ }
52
+ return false;
53
+ }
54
+
55
+ async function main() {
56
+ const args = parseArgs(process.argv.slice(2));
57
+ const cwd = process.cwd();
58
+ const graphPath = path.resolve(cwd, args.graph || 'design/KG/SystemArchitecture.json');
59
+ const qeaPath = path.resolve(cwd, args.qea || 'archgraph.qea');
60
+ if (!fs.existsSync(qeaPath)) {
61
+ console.error('qea not found: ' + qeaPath);
62
+ process.exit(2);
63
+ }
64
+ if (args.mode === 'export') {
65
+ const graph = lib.exportQeaToGraph(qeaPath);
66
+ const text = JSON.stringify(graph, null, 2);
67
+ if (args.out) { fs.writeFileSync(path.resolve(cwd, args.out), text, 'utf8'); console.log('export written to ' + args.out); }
68
+ else { console.log(text); }
69
+ return;
70
+ }
71
+ if (!fs.existsSync(graphPath)) {
72
+ console.error('graph not found: ' + graphPath);
73
+ process.exit(2);
74
+ }
75
+ const graph = readGraph(graphPath);
76
+ if (args.mode === 'watch') {
77
+ // eslint-disable-next-line no-constant-condition
78
+ while (true) {
79
+ runOnce(args, graphPath, qeaPath);
80
+ const m0 = statHash(graphPath);
81
+ await sleep(args.intervalMs);
82
+ const m1 = statHash(graphPath);
83
+ if (m0 !== m1) { continue; } // changed while waiting -> immediate re-sync
84
+ }
85
+ } else {
86
+ runOnce(args, graphPath, qeaPath);
87
+ }
88
+ }
89
+ function statHash(p) {
90
+ try { const st = fs.statSync(p); return st.size + ':' + st.mtimeMs; } catch { return 'gone'; }
91
+ }
92
+ function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
93
+ function runOnce(args, graphPath, qeaPath) {
94
+ const graph = readGraph(graphPath);
95
+ let snapshot = null;
96
+ if (!args.dryRun && !args.noBackup) {
97
+ snapshot = lib.snapshotQea(qeaPath, args.snapshotDir || undefined);
98
+ }
99
+ const res = args.mode === 'full'
100
+ ? lib.fullProjection(graph, qeaPath, { dryRun: args.dryRun })
101
+ : lib.syncGraphToQea(graph, qeaPath, { dryRun: args.dryRun, allowDelete: confirmDelete(args) });
102
+ const mode = args.dryRun ? 'dry-run' : 'sync';
103
+ console.log(JSON.stringify({ mode, graph: graphPath, qea: qeaPath, snapshot, result: res }, null, 2));
104
+ }
105
+
106
+ main().catch((e) => {
107
+ console.error('ea-qea-sync failed: ' + (e && e.message));
108
+ process.exit(1);
109
+ });
@@ -1,5 +1,6 @@
1
1
  const fs = require('node:fs');
2
2
  const path = require('node:path');
3
+ const { spawn } = require('node:child_process');
3
4
  const readline = require('node:readline');
4
5
  const crypto = require('node:crypto');
5
6
 
@@ -1615,6 +1616,25 @@ async function buildMutationResult(context, mutations, write) {
1615
1616
  writeGraph(context.graphPath.absolutePath, mutationResult.document);
1616
1617
  result.written = true;
1617
1618
 
1619
+ // WP2791: .qea projection parallel to the Neo4j trigger — non-fatal, best-effort.
1620
+ {
1621
+ const qeaTarget = resolveQeaProjectionTarget(context);
1622
+ if (qeaTarget) {
1623
+ try {
1624
+ const projection = await runQeaProjection(qeaTarget);
1625
+ if (projection.ok) {
1626
+ result.qeaProjection = { status: 'passed', qea: qeaTarget.qeaPath, ms: projection.ms };
1627
+ } else {
1628
+ result.qeaProjection = { status: 'failed', qea: qeaTarget.qeaPath, error: projection.error || ('exit ' + projection.code), ms: projection.ms };
1629
+ result.warnings = addUnique(result.warnings || [], ['ea-qea projection failed (non-fatal): ' + (projection.error || ('exit code ' + projection.code))]);
1630
+ }
1631
+ } catch (error) {
1632
+ result.qeaProjection = { status: 'failed', error: String(error && error.message ? error.message : error) };
1633
+ result.warnings = addUnique(result.warnings || [], ['ea-qea projection error (non-fatal): ' + String(error && error.message ? error.message : error)]);
1634
+ }
1635
+ }
1636
+ }
1637
+
1618
1638
  if (shouldSyncCanonicalGraphToNeo4j(context.graphPath.relativePath)) {
1619
1639
  try {
1620
1640
  const syncResult = await syncArchitectureToNeo4j({
@@ -1812,6 +1832,59 @@ function summarizeDocument(document) {
1812
1832
  };
1813
1833
  }
1814
1834
 
1835
+ // --- WP2791: post-canonical-write .qea projection (parallel to Neo4j sync, non-fatal) ---
1836
+ // Target resolution (decision qea-full-wholefile-argo-scripts-no-config): env ARGO_EA_QEA >
1837
+ // the single *.qea at the workspace root (0/many -> no-op with an explicit log). NO config file.
1838
+ // Projection script runs from argo/scripts (same package as the MCP runtime), so a workspace
1839
+ // does not need to ship its own projection script (bundled argo/scripts module).
1840
+ function resolveQeaProjectionTarget(context) {
1841
+ try {
1842
+ const workspaceRoot = String(context && context.workspaceRoot ? context.workspaceRoot : '');
1843
+ if (!workspaceRoot || !fs.existsSync(workspaceRoot)) { return null; }
1844
+ const graphAbsolute = context.graphPath && context.graphPath.absolutePath ? context.graphPath.absolutePath : null;
1845
+ if (!graphAbsolute || !fs.existsSync(graphAbsolute)) { return null; }
1846
+ const pick = (p) => (p && fs.existsSync(p) ? path.resolve(p) : null);
1847
+ let qeaPath = pick(process.env.ARGO_EA_QEA);
1848
+ if (qeaPath) { return { qeaPath, graphPath: graphAbsolute, workspaceRoot }; }
1849
+ let qeas = [];
1850
+ try { qeas = fs.readdirSync(workspaceRoot).filter((n) => n.toLowerCase().endsWith('.qea')); } catch { /* ignore */ }
1851
+ if (qeas.length === 1) {
1852
+ return { qeaPath: path.resolve(workspaceRoot, qeas[0]), graphPath: graphAbsolute, workspaceRoot };
1853
+ }
1854
+ console.log('[ea-qea] projection target: none' + (qeas.length > 1 ? ' (' + qeas.length + ' *.qea found; expected exactly one or ARGO_EA_QEA)' : '') + ' in ' + workspaceRoot);
1855
+ return null;
1856
+ } catch (error) {
1857
+ console.log('[ea-qea] projection target resolution failed: ' + String(error && error.message ? error.message : error));
1858
+ return null;
1859
+ }
1860
+ }
1861
+
1862
+ function runQeaProjection(target) {
1863
+ return new Promise((resolve) => {
1864
+ const script = path.join(__dirname, 'ea-qea-sync.js');
1865
+ if (!fs.existsSync(script)) {
1866
+ resolve({ ok: false, error: 'argo/scripts/ea-qea-sync.js missing', ms: 0 });
1867
+ return;
1868
+ }
1869
+ const snapshotDir = path.join(target.workspaceRoot, '.argo', 'temp', 'qea-backups');
1870
+ const args = [script, '--mode', 'sync', '--graph', target.graphPath, '--qea', target.qeaPath, '--snapshot-dir', snapshotDir];
1871
+ const started = Date.now();
1872
+ let stderr = '';
1873
+ let child;
1874
+ try {
1875
+ child = spawn(process.execPath, args, { cwd: target.workspaceRoot, windowsHide: true });
1876
+ } catch (error) {
1877
+ resolve({ ok: false, error: String(error && error.message ? error.message : error), ms: Date.now() - started });
1878
+ return;
1879
+ }
1880
+ child.stderr.on('data', (d) => { stderr += String(d); });
1881
+ child.on('error', (err) => resolve({ ok: false, error: String(err && err.message ? err.message : err), ms: Date.now() - started, stderr: stderr.slice(0, 600) }));
1882
+ child.on('close', (code) => {
1883
+ resolve({ ok: code === 0, code, ms: Date.now() - started, stderr: stderr.slice(0, 600) });
1884
+ });
1885
+ });
1886
+ }
1887
+
1815
1888
  function writeGraph(graphPath, document) {
1816
1889
  const tempPath = `${graphPath}.${process.pid}.${Date.now()}.tmp`;
1817
1890
  fs.writeFileSync(tempPath, `${JSON.stringify(document, null, 2)}\n`, 'utf8');
package/install-argo.ps1 CHANGED
@@ -26,10 +26,7 @@ param(
26
26
  [string]$OpenClawRepoRoot = '',
27
27
  [switch]$SkipOpenClaw,
28
28
  [string]$McpPath,
29
- [string]$GraphMcpUrl = 'https://argo.derekworkspacev5.com/mcp',
30
- [switch]$SkipEaWeb,
31
- [int]$EaWebPort = 8787,
32
- [string]$EaWebRoot = ''
29
+ [string]$GraphMcpUrl = 'https://argo.derekworkspacev5.com/mcp'
33
30
  )
34
31
 
35
32
  $ErrorActionPreference = 'Stop'
@@ -740,37 +737,37 @@ Write-Host '==> Deploying Argo toolchain'
740
737
 
741
738
  $schemaSrc = Join-Path $argoDir 'schema'
742
739
  $schemaDest = Join-Path $ArgoRoot 'schema'
743
- Write-Host "[1/23] argo\schema -> $schemaDest"
740
+ Write-Host "[1/22] argo\schema -> $schemaDest"
744
741
  Copy-Tree -Source $schemaSrc -Destination $schemaDest
745
742
 
746
743
  $scriptsSrc = Join-Path $argoDir 'scripts'
747
744
  $scriptsDest = Join-Path $ArgoRoot 'scripts'
748
- Write-Host "[2/23] argo\scripts -> $scriptsDest"
745
+ Write-Host "[2/22] argo\scripts -> $scriptsDest"
749
746
  Copy-Tree -Source $scriptsSrc -Destination $scriptsDest
750
747
 
751
748
  $defaultsSrc = Join-Path $argoDir 'defaults'
752
749
  $defaultsDest = Join-Path $ArgoRoot 'defaults'
753
- Write-Host "[3/23] argo\defaults -> $defaultsDest"
750
+ Write-Host "[3/22] argo\defaults -> $defaultsDest"
754
751
  Copy-Tree -Source $defaultsSrc -Destination $defaultsDest
755
752
 
756
753
  $skillSrc = Join-Path (Join-Path $argoDir 'skills') 'argo-init'
757
754
  $skillDest = Join-Path $SkillsRoot 'argo-init'
758
- Write-Host "[4/23] argo\skills\argo-init -> $skillDest"
755
+ Write-Host "[4/22] argo\skills\argo-init -> $skillDest"
759
756
  Copy-Tree -Source $skillSrc -Destination $skillDest
760
757
 
761
758
  $ruleSrc = Join-Path (Join-Path $argoDir 'rules') 'archgraph.instructions.md'
762
759
  $ruleDest = Join-Path $PromptsRoot 'archgraph.instructions.md'
763
- Write-Host "[5/23] argo\rules\archgraph.instructions.md -> $ruleDest"
760
+ Write-Host "[5/22] argo\rules\archgraph.instructions.md -> $ruleDest"
764
761
  New-Item -ItemType Directory -Force -Path $PromptsRoot | Out-Null
765
762
  Copy-Item -Force -Path $ruleSrc -Destination $ruleDest
766
763
 
767
764
  $depsSrc = Join-Path $argoDir 'package.json'
768
765
  $depsDest = Join-Path $ArgoRoot 'package.json'
769
- Write-Host "[6/23] argo\package.json -> $depsDest"
766
+ Write-Host "[6/22] argo\package.json -> $depsDest"
770
767
  Copy-Item -Force -Path $depsSrc -Destination $depsDest
771
768
 
772
769
  $cursorSkillDest = Join-Path $CursorSkillsRoot 'argo-init'
773
- Write-Host "[7/23] argo\skills\argo-init -> $cursorSkillDest (Cursor)"
770
+ Write-Host "[7/22] argo\skills\argo-init -> $cursorSkillDest (Cursor)"
774
771
  Copy-Tree -Source $skillSrc -Destination $cursorSkillDest
775
772
 
776
773
  $mcpBridgeSrc = Join-Path $argoDir 'mcp-bridges'
@@ -779,29 +776,29 @@ Write-Host " argo\mcp-bridges -> $mcpBridgeDest (Cursor graph-mcp stdio bridge)
779
776
  Copy-Tree -Source $mcpBridgeSrc -Destination $mcpBridgeDest
780
777
 
781
778
  $openCodeSkillDest = Join-Path $OpenCodeSkillsRoot 'argo-init'
782
- Write-Host "[8/23] argo\skills\argo-init -> $openCodeSkillDest (OpenCode)"
779
+ Write-Host "[8/22] argo\skills\argo-init -> $openCodeSkillDest (OpenCode)"
783
780
  Copy-Tree -Source $skillSrc -Destination $openCodeSkillDest
784
781
 
785
- Write-Host "[9/23] argo\rules\archgraph.instructions.md -> $OpenCodeAgentsPath (OpenCode global AGENTS.md)"
782
+ Write-Host "[9/22] argo\rules\archgraph.instructions.md -> $OpenCodeAgentsPath (OpenCode global AGENTS.md)"
786
783
  Add-AgentsRule -AgentsPath $OpenCodeAgentsPath -RulePath $ruleSrc
787
784
 
788
785
  $agentsSrc = Join-Path $argoDir 'agents'
789
- Write-Host "[10/23] argo\agents -> $CopilotAgentsRoot (Copilot user-level)"
786
+ Write-Host "[10/22] argo\agents -> $CopilotAgentsRoot (Copilot user-level)"
790
787
  Copy-Agents -Source $agentsSrc -Destination $CopilotAgentsRoot
791
788
 
792
- Write-Host "[11/23] argo\agents -> $CursorAgentsRoot (Cursor user-level, converted to .md)"
789
+ Write-Host "[11/22] argo\agents -> $CursorAgentsRoot (Cursor user-level, converted to .md)"
793
790
  Copy-Agents -Source $agentsSrc -Destination $CursorAgentsRoot -Target cursor
794
791
 
795
- Write-Host "[12/23] argo\agents -> $OpenCodeAgentsRoot (OpenCode user-level, converted to .md)"
792
+ Write-Host "[12/22] argo\agents -> $OpenCodeAgentsRoot (OpenCode user-level, converted to .md)"
796
793
  Copy-Agents -Source $agentsSrc -Destination $OpenCodeAgentsRoot -Target opencode
797
794
 
798
795
  $pluginsSrc = Join-Path $argoDir 'plugins'
799
- Write-Host "[13/23] argo\plugins -> $PluginsRoot (Argo opencode plugins)"
796
+ Write-Host "[13/22] argo\plugins -> $PluginsRoot (Argo opencode plugins)"
800
797
  Copy-Tree -Source $pluginsSrc -Destination $PluginsRoot
801
798
 
802
799
  $cursorRuleSrc = Join-Path (Join-Path $argoDir 'rules') 'archgraph.instructions.md'
803
800
  $cursorRuleDest = Join-Path $CursorRulesRoot 'archgraph.mdc'
804
- Write-Host "[14/23] argo\rules\archgraph.instructions.md -> $cursorRuleDest (Cursor global rule, alwaysApply)"
801
+ Write-Host "[14/22] argo\rules\archgraph.instructions.md -> $cursorRuleDest (Cursor global rule, alwaysApply)"
805
802
  New-Item -ItemType Directory -Force -Path $CursorRulesRoot | Out-Null
806
803
  Convert-RuleFile -SourceFile $cursorRuleSrc -DestinationFile $cursorRuleDest
807
804
 
@@ -813,16 +810,16 @@ if ($SkipDsh) {
813
810
  $patchPath = Join-Path $DshHome 'cordis.patch.yml'
814
811
  $argoServer = (Join-Path $ArgoRoot 'scripts\argo-mcp-server.js').Replace('\', '/')
815
812
 
816
- Write-Host "[15/23] argo\rules\archgraph.instructions.md -> $DshHome\AGENTS.md (DeepSeek Harness user-global rule, frontmatter stripped)"
813
+ Write-Host "[15/22] argo\rules\archgraph.instructions.md -> $DshHome\AGENTS.md (DeepSeek Harness user-global rule, frontmatter stripped)"
817
814
  Write-DshAgentRule -DshHome $DshHome -RuleText $ruleSrcContent
818
815
 
819
- Write-Host "[16/23] argo\skills\argo-init -> $dshSkillDest (DeepSeek Harness skill)"
816
+ Write-Host "[16/22] argo\skills\argo-init -> $dshSkillDest (DeepSeek Harness skill)"
820
817
  Copy-Tree -Source (Join-Path $argoDir 'skills\argo-init') -Destination $dshSkillDest
821
818
 
822
- Write-Host "[17/23] argo\rules\<WakeupGuideline> -> $DshHome\plugins\dsh-argo-wakeup\index.js (DeepSeek Harness wakeup plugin)"
819
+ Write-Host "[17/22] argo\rules\<WakeupGuideline> -> $DshHome\plugins\dsh-argo-wakeup\index.js (DeepSeek Harness wakeup plugin)"
823
820
  $wakeupDshPath = New-DshWakeupPlugin -DshHome $DshHome -RuleText $ruleSrcContent
824
821
 
825
- Write-Host "[18/23] argo-workspace + argo-wakeup rows -> $patchPath (DeepSeek Harness MCP bridge + wakeup plugin)"
822
+ Write-Host "[18/22] argo-workspace + argo-wakeup rows -> $patchPath (DeepSeek Harness MCP bridge + wakeup plugin)"
826
823
  # The generated dsh-argo-workspace bridge connects directly to the argo
827
824
  # server (no dsh-mcp-client row), registers every tool as mcp__argo__* and
828
825
  # injects the current session's workspace (SessionHeader.cwd) as the
@@ -853,7 +850,7 @@ if ($SkipDsh) {
853
850
  Write-DshManagedBlock -Path $patchPath -Block $block -MarkerStart '# BEGIN ArchGraph ARGO deployment' -MarkerEnd '# END ArchGraph ARGO deployment'
854
851
  }
855
852
 
856
- Write-Host "[19/23] argo\agents -> $DshHome\.agent-presets\<id> (DeepSeek Harness agent presets)"
853
+ Write-Host "[19/22] argo\agents -> $DshHome\.agent-presets\<id> (DeepSeek Harness agent presets)"
857
854
  New-DshAgentPresets -DshHome $DshHome -AgentsSrc (Join-Path $argoDir 'agents')
858
855
 
859
856
  Write-Host " note: graph-mcp remote ($GraphMcpUrl) is NOT registered for DeepSeek Harness -"
@@ -874,10 +871,10 @@ if ($SkipOpenClaw) {
874
871
  $openClawAgentsDest = Join-Path $OpenClawWorkspace 'AGENTS.md'
875
872
 
876
873
  Write-Host '==> Deploying OpenClaw integration'
877
- Write-Host "[20/23] argo\rules\archgraph.instructions.md -> $openClawAgentsDest (OpenClaw workspace AGENTS.md, frontmatter stripped)"
874
+ Write-Host "[20/22] argo\rules\archgraph.instructions.md -> $openClawAgentsDest (OpenClaw workspace AGENTS.md, frontmatter stripped)"
878
875
  Write-OpenClawAgentRule -OpenClawWorkspace $OpenClawWorkspace -RuleText $ruleSrcContent
879
876
 
880
- Write-Host "[21/23] argo\skills\argo-init -> $openClawSkillDest (OpenClaw managed skill, all agents)"
877
+ Write-Host "[21/22] argo\skills\argo-init -> $openClawSkillDest (OpenClaw managed skill, all agents)"
881
878
  Copy-Tree -Source (Join-Path $argoDir 'skills\argo-init') -Destination $openClawSkillDest
882
879
 
883
880
  Write-Host ' OpenClaw injects AGENTS.md into Project Context on every session, so the wakeup'
@@ -1046,7 +1043,7 @@ if ($SkipMcp) {
1046
1043
  # location; -OpenClawRepoRoot overrides it (e.g. for tests or when the
1047
1044
  # installer runs from a non-workspace npm-global package dir).
1048
1045
  $openClawRepoRoot = if ($OpenClawRepoRoot) { $OpenClawRepoRoot } else { $repoRoot }
1049
- Write-Host "[22/23] argo MCP server -> $(Join-Path $OpenClawHome 'openclaw.json') (OpenClaw mcp.servers.argo, env.ARGO_REPO_ROOT pinned)"
1046
+ Write-Host "[22/22] argo MCP server -> $(Join-Path $OpenClawHome 'openclaw.json') (OpenClaw mcp.servers.argo, env.ARGO_REPO_ROOT pinned)"
1050
1047
  Write-OpenClawMcpConfig -OpenClawHome $OpenClawHome -RepoRoot $openClawRepoRoot -ArgoServer $argoServer -GraphMcpServer $openClawGraphMcpServer
1051
1048
  }
1052
1049
  }
@@ -1058,68 +1055,5 @@ if (Test-Path $wakeupPluginPath) {
1058
1055
  Write-Host "argo-wakeup plugin registered -> $OpenCodeConfigPath"
1059
1056
  }
1060
1057
 
1061
- # EA local web service (WP2786): deploy the two service scripts next to the
1062
- # harness scripts (REPO_ROOT = $ArgoRoot keeps the relative layout intact) plus
1063
- # the web/ statics, then start the service in the background. Re-running the
1064
- # installer is idempotent: an occupied port skips the startup.
1065
- $eaServiceSrc = Join-Path $repoRoot 'scripts\ea-web-service.js'
1066
- $eaLayoutStoreSrc = Join-Path $repoRoot 'scripts\ea-layout-store.js'
1067
- $eaWebSrc = Join-Path $repoRoot 'web'
1068
- Write-Host "[23/23] scripts\ea-web-service.js + scripts\ea-layout-store.js + web -> $ArgoRoot (EA local web service)"
1069
- if ((Test-Path $eaServiceSrc) -and (Test-Path $eaLayoutStoreSrc) -and (Test-Path $eaWebSrc)) {
1070
- Copy-Item -Force -Path $eaServiceSrc -Destination (Join-Path $scriptsDest 'ea-web-service.js')
1071
- Copy-Item -Force -Path $eaLayoutStoreSrc -Destination (Join-Path $scriptsDest 'ea-layout-store.js')
1072
- Copy-Tree -Source $eaWebSrc -Destination (Join-Path $ArgoRoot 'web')
1073
- Write-Host " service scripts + web statics deployed -> $scriptsDest / $(Join-Path $ArgoRoot 'web')"
1074
- if ($SkipEaWeb) {
1075
- Write-Host ' EA web service startup skipped (-SkipEaWeb)'
1076
- }
1077
- else {
1078
- $eaWebRoot = if ($EaWebRoot) { $EaWebRoot } else { $PWD.Path }
1079
- $eaPortInUse = $false
1080
- try {
1081
- $eaProbe = New-Object System.Net.Sockets.TcpClient
1082
- $eaProbe.Connect('127.0.0.1', $EaWebPort)
1083
- $eaPortInUse = $true
1084
- $eaProbe.Close()
1085
- }
1086
- catch {
1087
- $eaPortInUse = $false
1088
- }
1089
- if ($eaPortInUse) {
1090
- Write-Host " EA web service already listening on 127.0.0.1:$EaWebPort; startup skipped (idempotent re-deploy)"
1091
- }
1092
- else {
1093
- $nodeCmd = Get-Command node -ErrorAction SilentlyContinue
1094
- if (-not $nodeCmd) {
1095
- throw 'EA web service startup failed: node not found on PATH'
1096
- }
1097
- $eaServiceDest = Join-Path $scriptsDest 'ea-web-service.js'
1098
- $eaLogOut = Join-Path $ArgoRoot 'ea-web-service.log'
1099
- $eaLogErr = Join-Path $ArgoRoot 'ea-web-service.err.log'
1100
- # Start the service through WMI (Win32_Process.Create) instead of
1101
- # Start-Process: the process is then created by the WMI service and
1102
- # inherits NONE of this installer's stdio pipe handles. Inherited
1103
- # pipes would keep spawnSync-based launchers (argo-deploy bin, tests)
1104
- # blocked until the service exits. cmd.exe is used only to redirect
1105
- # the service stdout/stderr into log files.
1106
- $q = [char]34
1107
- $eaCmdLine = "cmd.exe /S /c $q$q$($nodeCmd.Source)$q $q$eaServiceDest$q --root $q$eaWebRoot$q --port $EaWebPort > $q$eaLogOut$q 2> $q$eaLogErr$q$q"
1108
- $eaCreate = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{
1109
- CommandLine = $eaCmdLine
1110
- CurrentDirectory = $ArgoRoot
1111
- }
1112
- if ($eaCreate.ReturnValue -ne 0) {
1113
- throw "EA web service startup failed: Win32_Process.Create ReturnValue $($eaCreate.ReturnValue)"
1114
- }
1115
- Write-Host " EA web service started in background (pid $($eaCreate.ProcessId)): http://127.0.0.1:$EaWebPort (root: $eaWebRoot)"
1116
- Write-Host " logs: $eaLogOut / $eaLogErr"
1117
- }
1118
- }
1119
- }
1120
- else {
1121
- Write-Host ' EA web service sources not present in this package; step skipped'
1122
- }
1123
-
1124
1058
  Write-Host ''
1125
1059
  Write-Host 'Argo deployment complete.'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archgraph-argo",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
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": {
@@ -16,9 +16,6 @@
16
16
  "argo/skills/argo-init",
17
17
  "argo/rules",
18
18
  "argo/package.json",
19
- "scripts/ea-web-service.js",
20
- "scripts/ea-layout-store.js",
21
- "web",
22
19
  "vendor",
23
20
  "install-argo.ps1",
24
21
  "bin",
@@ -41,7 +38,8 @@
41
38
  "neo4j-driver": "^6.2.0"
42
39
  },
43
40
  "devDependencies": {
44
- "@resvg/resvg-js": "^2.6.2"
41
+ "@resvg/resvg-js": "^2.6.2",
42
+ "playwright": "1.61.1"
45
43
  },
46
44
  "scripts": {
47
45
  "test": "node --test \"tests/*.test.js\""