@ucsandman/legcli 0.8.0 → 0.10.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.
Files changed (125) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/NOTICE +8 -0
  3. package/README.md +639 -560
  4. package/bin/fake-agent.mjs +4 -4
  5. package/bin/leg.mjs +43 -12
  6. package/docs/DECISIONS.md +20 -2
  7. package/docs/ERRORS.md +205 -0
  8. package/docs/README.md +5 -1
  9. package/docs/REUSE.md +1 -1
  10. package/docs/VOCABULARY.md +22 -0
  11. package/docs/board-guide.md +33 -1
  12. package/docs/cli-contracts.md +36 -1
  13. package/docs/concepts.md +42 -3
  14. package/docs/configuration.md +23 -1
  15. package/docs/faq.md +19 -0
  16. package/docs/getting-started.md +272 -251
  17. package/docs/harness.md +319 -0
  18. package/docs/history.md +172 -0
  19. package/docs/runtime-tap.md +156 -0
  20. package/fixtures/verified.json +1 -1
  21. package/package.json +7 -3
  22. package/scripts/build-docs-site.mjs +18 -4
  23. package/scripts/check-branding.mjs +118 -0
  24. package/scripts/check-claims.mjs +1 -1
  25. package/scripts/license-sign.mjs +1 -1
  26. package/scripts/limits-table.mjs +1 -1
  27. package/scripts/live-limits.mjs +1 -1
  28. package/scripts/npm-publish-gate.mjs +114 -0
  29. package/scripts/probe.mjs +4 -3
  30. package/scripts/seed-fake-cards.mjs +4 -3
  31. package/scripts/seed-floor-board.mjs +5 -4
  32. package/scripts/seed-wes-board.mjs +5 -4
  33. package/scripts/stripe-setup.mjs +1 -1
  34. package/scripts/sync-harness-engine.mjs +159 -0
  35. package/scripts/sync-leg-agents.mjs +127 -0
  36. package/src/accounts.mjs +6 -4
  37. package/src/adapters/codex.mjs +1 -1
  38. package/src/attach.mjs +125 -23
  39. package/src/auth.mjs +2 -2
  40. package/src/board/board.css +23 -1
  41. package/src/board/board.js +17 -5
  42. package/src/board/history.js +377 -0
  43. package/src/board/index.html +33 -0
  44. package/src/board/sessions.js +95 -7
  45. package/src/bundle.mjs +54 -8
  46. package/src/chain.mjs +1 -1
  47. package/src/contract.mjs +4 -3
  48. package/src/fsx.mjs +5 -2
  49. package/src/handoff.mjs +6 -6
  50. package/src/harness/cli.mjs +281 -0
  51. package/src/harness/fingerprint.mjs +68 -0
  52. package/src/harness/index.mjs +407 -0
  53. package/src/harness/registry.mjs +124 -0
  54. package/src/harness/vendor/agnostic-ai/LICENSE +21 -0
  55. package/src/harness/vendor/agnostic-ai/UPSTREAM.json +30 -0
  56. package/src/harness/vendor/agnostic-ai/core/safety/guards.json +96 -0
  57. package/src/harness/vendor/agnostic-ai/core/templates/targets.json +252 -0
  58. package/src/harness/vendor/agnostic-ai/engine/harness/README.md +199 -0
  59. package/src/harness/vendor/agnostic-ai/engine/harness/apply.cjs +247 -0
  60. package/src/harness/vendor/agnostic-ai/engine/harness/bundle.cjs +243 -0
  61. package/src/harness/vendor/agnostic-ai/engine/harness/capture.cjs +119 -0
  62. package/src/harness/vendor/agnostic-ai/engine/harness/common.cjs +375 -0
  63. package/src/harness/vendor/agnostic-ai/engine/harness/index.cjs +55 -0
  64. package/src/harness/vendor/agnostic-ai/engine/harness/sources/claude.cjs +330 -0
  65. package/src/harness/vendor/agnostic-ai/engine/harness/sources/codex.cjs +314 -0
  66. package/src/harness/vendor/agnostic-ai/engine/harness/status.cjs +171 -0
  67. package/src/harness/vendor/agnostic-ai/engine/harness/targets/agy.cjs +113 -0
  68. package/src/harness/vendor/agnostic-ai/engine/harness/targets/claude.cjs +158 -0
  69. package/src/harness/vendor/agnostic-ai/engine/harness/targets/codex.cjs +832 -0
  70. package/src/harness/vendor/agnostic-ai/engine/harness/targets/cursor.cjs +87 -0
  71. package/src/harness/vendor/agnostic-ai/engine/harness/targets/gemini.cjs +128 -0
  72. package/src/harness/vendor/agnostic-ai/engine/harness/targets/generic.cjs +424 -0
  73. package/src/harness/vendor/agnostic-ai/engine/harness/toml.cjs +149 -0
  74. package/src/harness/vendor/agnostic-ai/engine/hooks/shim.cjs +431 -0
  75. package/src/history/cli.mjs +159 -0
  76. package/src/history/common.mjs +119 -0
  77. package/src/history/index.mjs +429 -0
  78. package/src/history/providers/agy.mjs +91 -0
  79. package/src/history/providers/claude.mjs +161 -0
  80. package/src/history/providers/codex.mjs +133 -0
  81. package/src/history/providers/copilot.mjs +94 -0
  82. package/src/history/providers/grok.mjs +138 -0
  83. package/src/history/worktrees.mjs +116 -0
  84. package/src/hook.mjs +49 -49
  85. package/src/land.mjs +7 -35
  86. package/src/launcher.mjs +38 -26
  87. package/src/ledger.mjs +6 -6
  88. package/src/license.mjs +10 -9
  89. package/src/live-capture.mjs +1 -1
  90. package/src/mergequeue.mjs +5 -5
  91. package/src/orchestrator.mjs +28 -4
  92. package/src/preferences.mjs +37 -3
  93. package/src/redact.mjs +24 -6
  94. package/src/resume.mjs +17 -15
  95. package/src/runner.mjs +2 -2
  96. package/src/scheduler.mjs +1 -1
  97. package/src/server.mjs +224 -18
  98. package/src/session-detail.mjs +15 -1
  99. package/src/sessions.mjs +15 -3
  100. package/src/share.mjs +2 -2
  101. package/src/stations/agent.mjs +1 -1
  102. package/src/sync/dashclaw.mjs +4 -4
  103. package/src/synthesis.mjs +165 -0
  104. package/src/taps/agy.mjs +2 -2
  105. package/src/taps/claude-usage.mjs +1 -1
  106. package/src/taps/claude.mjs +177 -170
  107. package/src/taps/codex.mjs +286 -286
  108. package/src/taps/grok.mjs +2 -2
  109. package/src/taps/mod.mjs +340 -0
  110. package/src/trust.mjs +205 -36
  111. package/src/usage.mjs +5 -1
  112. package/src/worktree.mjs +6 -5
  113. package/fixtures/live/agy/attempt-1-scratch-workspace.out.log +0 -1
  114. package/fixtures/live/agy/err.log +0 -0
  115. package/fixtures/live/agy/out.log +0 -1
  116. package/fixtures/live/agy/supervisor.log +0 -2
  117. package/fixtures/live/claude/err.log +0 -0
  118. package/fixtures/live/claude/out.log +0 -1
  119. package/fixtures/live/claude/supervisor.log +0 -2
  120. package/fixtures/live/codex/err.log +0 -1
  121. package/fixtures/live/codex/out.log +0 -8
  122. package/fixtures/live/codex/supervisor.log +0 -2
  123. package/fixtures/live/grok/err.log +0 -32
  124. package/fixtures/live/grok/out.log +0 -7
  125. package/fixtures/live/grok/supervisor.log +0 -2
@@ -0,0 +1,96 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "version": "2.0.0",
4
+ "_note": "SINGLE SOURCE OF TRUTH for all safety policy. Consumed read-only by agent/governance/guard.py (Python), engine/hooks/secret-guard.cjs, engine/hooks/dashclaw-guard.cjs (Node), and the dashboard. Do not hardcode policy anywhere else.",
5
+ "guards": {
6
+ "secretScan": {
7
+ "enabled": true,
8
+ "blockedFiles": [
9
+ "**/.env",
10
+ "**/.env.*",
11
+ "**/.secrets.env",
12
+ "**/.secrets",
13
+ "**/id_rsa",
14
+ "**/id_ed25519",
15
+ "**/id_ecdsa",
16
+ "**/id_dsa",
17
+ "**/*.pem",
18
+ "**/*.pfx",
19
+ "**/*.key",
20
+ "**/*.p12",
21
+ "**/credentials.json",
22
+ "**/.aws/credentials",
23
+ "**/.npmrc",
24
+ "**/.dashclaw-local/secrets/**"
25
+ ],
26
+ "secretPathRegexes": [
27
+ "(?i)(^|[\\s=\"'`/\\\\(@<>:,*&|;])\\.env(rc)?\\b",
28
+ "(?i)\\.secrets(\\.env)?\\b",
29
+ "(?i)\\bid_(rsa|ed25519|ecdsa|dsa)\\b",
30
+ "(?i)\\.(pem|pfx|p12|key)\\b",
31
+ "(?i)\\bcredentials\\.json\\b",
32
+ "(?i)\\.aws[\\\\/]credentials\\b",
33
+ "(?i)\\.dashclaw-local[\\\\/]secrets"
34
+ ],
35
+ "sensitivePatterns": [
36
+ "(?i)sk_live_[0-9a-zA-Z]{24,}",
37
+ "(?i)sk-[0-9a-zA-Z]{20,}",
38
+ "(?i)ghp_[0-9a-zA-Z]{36}",
39
+ "(?i)gho_[0-9a-zA-Z]{36}",
40
+ "(?i)xox[baprs]-[0-9a-zA-Z-]{10,}",
41
+ "(?i)AIza[0-9A-Za-z-_]{35}",
42
+ "(?i)-----BEGIN [A-Z ]*PRIVATE KEY-----",
43
+ "(?i)bearer\\s+[a-zA-Z0-9_\\-\\.]{20,}"
44
+ ]
45
+ },
46
+ "processControl": {
47
+ "protectedProcesses": [
48
+ "explorer.exe",
49
+ "system",
50
+ "powershell.exe",
51
+ "pwsh.exe",
52
+ "cmd.exe",
53
+ "svchost.exe",
54
+ "claude.exe",
55
+ "agy.exe",
56
+ "node.exe"
57
+ ],
58
+ "blockedCommands": [
59
+ "(?i)taskkill.*\\/(im\\s+(explorer|svchost|system)|f)",
60
+ "(?i)rmdir.*\\/s.*\\/q.*[c-z]:\\\\(windows|users)?$",
61
+ "(?i)format\\s+[c-z]:"
62
+ ]
63
+ },
64
+ "hardStops": {
65
+ "_note": "Matched anywhere in the command string; order-independent. These require explicit human approval.",
66
+ "requireApprovalPatterns": [
67
+ "(?i)\\bgit\\s+push\\b.*(--force\\b|--force-with-lease\\b|\\s-f\\b)",
68
+ "(?i)\\bgit\\s+push\\s+-f\\b",
69
+ "(?i)\\bgit\\s+reset\\s+.*--hard\\b",
70
+ "(?i)\\bgit\\s+clean\\s+.*-[a-z]*f",
71
+ "(?i)\\brm\\s+-[a-z]*r[a-z]*f|\\brm\\s+-[a-z]*f[a-z]*r",
72
+ "(?i)\\brmdir\\s+/s\\b|\\bRemove-Item\\b.*-Recurse",
73
+ "(?i)\\b(drop\\s+(database|table)|truncate\\s+table)\\b",
74
+ "(?i)\\bnpm\\s+publish\\b",
75
+ "(?i)\\bnpm\\s+audit\\s+fix\\s+--force\\b",
76
+ "(?i)\\bvercel\\s+(--prod|deploy\\s+--prod)\\b",
77
+ "(?i)\\brailway\\s+up\\b",
78
+ "(?i)\\brender\\s+deploy\\b",
79
+ "(?i)\\bstripe\\s+(delete|refund|payout)\\b",
80
+ "(?i)\\b(deploy|migrate)\\s+(prod|production)\\b"
81
+ ]
82
+ },
83
+ "dashclaw": {
84
+ "enabled": true,
85
+ "autoDiscover": true,
86
+ "defaultRiskThreshold": 50,
87
+ "hardBlockRiskThreshold": 90,
88
+ "failClosed": true,
89
+ "adoptLocalhostPortsAllowed": false,
90
+ "baseUrl": "https://my-dashclaw.vercel.app",
91
+ "predictiveRiskEnabled": true,
92
+ "autoScanBlock": true,
93
+ "orgHalt": false
94
+ }
95
+ }
96
+ }
@@ -0,0 +1,252 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "_note": "Every AI coding client the harness can be ported to. Field meanings: engine/harness/README.md. Paths are ~-relative; `home` present on disk means the client is installed. Do not hardcode a client path anywhere else.",
4
+ "targets": [
5
+ {
6
+ "id": "claude",
7
+ "name": "Claude Code",
8
+ "category": "CLI Agent",
9
+ "home": "~/.claude",
10
+ "adapter": "claude",
11
+ "dialect": "claude",
12
+ "rulesFile": "~/.claude/agnostic-rules.md",
13
+ "traitsFile": "~/.claude/SOUL.md",
14
+ "hooksConfigFile": "~/.claude/settings.json",
15
+ "skillsDir": "~/.claude/skills",
16
+ "sharedSkillDirs": [
17
+ "~/.agents/skills"
18
+ ],
19
+ "agentsDir": "~/.claude/agents",
20
+ "commandsDir": "~/.claude/commands",
21
+ "mcpConfigFile": "~/.claude.json",
22
+ "mcpFormat": "mcpServers-json",
23
+ "preamble": "# Agnostic Rules — Global Working Agreement (Claude Code)\n\nGENERATED by agnostic-ai. Source of truth is the captured harness (or core/rules/). Loaded via an @import line in ~/.claude/CLAUDE.md.\n"
24
+ },
25
+ {
26
+ "id": "codex",
27
+ "name": "Codex CLI",
28
+ "category": "CLI Agent",
29
+ "home": "~/.codex",
30
+ "adapter": "codex",
31
+ "dialect": "codex",
32
+ "rulesFile": "~/.codex/AGENTS.md",
33
+ "hooksConfigFile": "~/.codex/config.toml",
34
+ "skillsDir": "~/.codex/skills",
35
+ "sharedSkillDirs": [
36
+ "~/.agents/skills"
37
+ ],
38
+ "agentsDir": "~/.codex/agents",
39
+ "commandsDir": "~/.codex/prompts",
40
+ "mcpConfigFile": "~/.codex/config.toml",
41
+ "mcpFormat": "codex-toml",
42
+ "permissionsFile": "~/.codex/rules/agnostic.rules",
43
+ "preamble": "# AGENTS.md — Global Working Agreement (Codex CLI)\n\nGENERATED by agnostic-ai from the captured harness. Do not hand-edit; edit the source client and run `npm run port`.\n"
44
+ },
45
+ {
46
+ "id": "gemini",
47
+ "name": "Gemini CLI",
48
+ "category": "CLI Agent",
49
+ "home": "~/.gemini",
50
+ "adapter": "gemini",
51
+ "dialect": "gemini",
52
+ "rulesFile": "~/.gemini/GEMINI.md",
53
+ "traitsFile": "~/.gemini/config/SOUL.md",
54
+ "hooksConfigFile": "~/.gemini/settings.json",
55
+ "skillsDir": "~/.gemini/skills",
56
+ "commandsDir": "~/.gemini/commands",
57
+ "mcpConfigFile": "~/.gemini/settings.json",
58
+ "mcpFormat": "mcpServers-json",
59
+ "preamble": "# GEMINI.md — Global Working Agreement (Gemini CLI)\n\nGENERATED by agnostic-ai from the captured harness. Do not hand-edit; edit the source client and run `npm run port`.\n"
60
+ },
61
+ {
62
+ "id": "agy",
63
+ "name": "Antigravity CLI",
64
+ "category": "CLI Agent",
65
+ "home": "~/.gemini/antigravity-cli",
66
+ "adapter": "agy",
67
+ "dialect": "agy",
68
+ "rulesFile": "~/.gemini/GEMINI.md",
69
+ "traitsFile": "~/.gemini/config/SOUL.md",
70
+ "hooksConfigFile": "~/.gemini/config/hooks.json",
71
+ "skillsDir": "~/.gemini/config/skills",
72
+ "agentsDir": "~/.gemini/config/agents",
73
+ "commandsDir": "~/.gemini/config/commands",
74
+ "mcpConfigFile": "~/.gemini/config/mcp_config.json",
75
+ "mcpFormat": "mcpServers-json",
76
+ "preamble": "# GEMINI.md — Global Working Agreement (Gemini CLI)\n\nGENERATED by agnostic-ai from the captured harness. Do not hand-edit; edit the source client and run `npm run port`.\n"
77
+ },
78
+ {
79
+ "id": "cursor",
80
+ "name": "Cursor",
81
+ "category": "Agentic IDE",
82
+ "home": "~/.cursor",
83
+ "adapter": "cursor",
84
+ "dialect": "cursor",
85
+ "rulesFile": "~/.cursor/rules/global-rules.mdc",
86
+ "hooksConfigFile": "~/.cursor/hooks.json",
87
+ "skillsDir": "~/.cursor/skills",
88
+ "agentsDir": "~/.cursor/agents",
89
+ "commandsDir": "~/.cursor/commands",
90
+ "mcpConfigFile": "~/.cursor/mcp.json",
91
+ "mcpFormat": "mcpServers-json",
92
+ "preamble": "---\ndescription: \"Global Working Agreement and safety rules\"\nglobs: \"*\"\nalwaysApply: true\n---\n\n# Cursor Rules — Global Working Agreement\n\nGENERATED by agnostic-ai from the captured harness.\n"
93
+ },
94
+ {
95
+ "id": "windsurf",
96
+ "name": "Windsurf (Cascade)",
97
+ "category": "Agentic IDE",
98
+ "home": "~/.windsurf",
99
+ "dialect": "windsurf",
100
+ "rulesFile": "~/.windsurf/rules/global-rules.md",
101
+ "skillsDir": "~/.windsurf/skills",
102
+ "mcpConfigFile": "~/.codeium/windsurf/mcp_config.json",
103
+ "mcpFormat": "mcpServers-json",
104
+ "preamble": "# Windsurf Rules — Global Working Agreement\n\nGENERATED by agnostic-ai from the captured harness.\n"
105
+ },
106
+ {
107
+ "id": "copilot",
108
+ "name": "GitHub Copilot",
109
+ "category": "VS Code Extension",
110
+ "home": "~/.copilot",
111
+ "dialect": "copilot",
112
+ "rulesFile": "~/.github/copilot-instructions.md",
113
+ "skillsDir": "~/.copilot/skills",
114
+ "preamble": "# GitHub Copilot Global Instructions\n\nGENERATED by agnostic-ai from the captured harness.\n"
115
+ },
116
+ {
117
+ "id": "cline",
118
+ "name": "Cline",
119
+ "category": "VS Code Extension",
120
+ "home": "~/.cline",
121
+ "dialect": "cline",
122
+ "rulesFile": "~/.cline/prompts/global-rules.md",
123
+ "skillsDir": "~/.cline/skills",
124
+ "mcpConfigFile": "~/.cline/cline_mcp_settings.json",
125
+ "mcpFormat": "mcpServers-json",
126
+ "preamble": "# Cline — Global Working Agreement\n\nGENERATED by agnostic-ai from the captured harness.\n"
127
+ },
128
+ {
129
+ "id": "aider",
130
+ "name": "Aider",
131
+ "category": "CLI Agent",
132
+ "home": "~/.aider",
133
+ "dialect": "aider",
134
+ "rulesFile": "~/.aider.conventions.md",
135
+ "preamble": "# Aider — Conventions & Global Working Agreement\n\nGENERATED by agnostic-ai from the captured harness.\n"
136
+ },
137
+ {
138
+ "id": "openhands",
139
+ "name": "OpenHands",
140
+ "category": "Autonomous Agent",
141
+ "home": "~/.openhands",
142
+ "dialect": "openhands",
143
+ "rulesFile": "~/.openhands/AGENTS.md",
144
+ "skillsDir": "~/.openhands/skills",
145
+ "sharedSkillDirs": [
146
+ "~/.agents/skills"
147
+ ],
148
+ "preamble": "# AGENTS.md — OpenHands Global Agreement\n\nGENERATED by agnostic-ai from the captured harness.\n"
149
+ },
150
+ {
151
+ "id": "goose",
152
+ "name": "Goose (Block)",
153
+ "category": "Autonomous Agent",
154
+ "home": "~/.config/goose",
155
+ "dialect": "goose",
156
+ "rulesFile": "~/.config/goose/.goosehints",
157
+ "skillsDir": "~/.config/goose/skills",
158
+ "preamble": "# .goosehints — Goose Global System Hints\n\nGENERATED by agnostic-ai from the captured harness.\n"
159
+ },
160
+ {
161
+ "id": "continue",
162
+ "name": "Continue.dev",
163
+ "category": "VS Code Extension",
164
+ "home": "~/.continue",
165
+ "dialect": "continue",
166
+ "rulesFile": "~/.continue/rules/global-rules.md",
167
+ "skillsDir": "~/.continue/skills",
168
+ "preamble": "# Continue.dev — Global Agent Rules\n\nGENERATED by agnostic-ai from the captured harness.\n"
169
+ },
170
+ {
171
+ "id": "zed",
172
+ "name": "Zed AI",
173
+ "category": "Agentic IDE",
174
+ "home": "~/.config/zed",
175
+ "dialect": "zed",
176
+ "rulesFile": "~/.config/zed/AGENTS.md",
177
+ "preamble": "# AGENTS.md — Zed AI Global Instructions\n\nGENERATED by agnostic-ai from the captured harness.\n"
178
+ },
179
+ {
180
+ "id": "opencode",
181
+ "name": "OpenCode",
182
+ "category": "CLI Agent",
183
+ "home": "~/.config/opencode",
184
+ "dialect": "opencode",
185
+ "rulesFile": "~/.config/opencode/AGENTS.md",
186
+ "skillsDir": "~/.config/opencode/skills",
187
+ "sharedSkillDirs": [
188
+ "~/.agents/skills"
189
+ ],
190
+ "commandsDir": "~/.config/opencode/commands",
191
+ "mcpConfigFile": "~/.config/opencode/opencode.json",
192
+ "mcpFormat": "opencode-json",
193
+ "preamble": "# AGENTS.md — OpenCode Global Instructions\n\nGENERATED by agnostic-ai from the captured harness.\n"
194
+ },
195
+ {
196
+ "id": "trae",
197
+ "name": "Trae (ByteDance)",
198
+ "category": "Agentic IDE",
199
+ "home": "~/.trae",
200
+ "dialect": "trae",
201
+ "rulesFile": "~/.trae/user_rules/user_rules.md",
202
+ "skillsDir": "~/.trae/skills",
203
+ "preamble": "# user_rules.md — Trae User Rules\n\nGENERATED by agnostic-ai from the captured harness.\n"
204
+ },
205
+ {
206
+ "id": "amazonq",
207
+ "name": "Amazon Q Developer",
208
+ "category": "Enterprise Assistant",
209
+ "home": "~/.amazonq",
210
+ "dialect": "amazonq",
211
+ "rulesFile": "~/.amazonq/rules/global-rules.md",
212
+ "preamble": "# Amazon Q Developer — Global Rules\n\nGENERATED by agnostic-ai from the captured harness.\n"
213
+ },
214
+ {
215
+ "id": "cody",
216
+ "name": "Sourcegraph Cody",
217
+ "category": "Enterprise Assistant",
218
+ "home": "~/.sourcegraph",
219
+ "dialect": "cody",
220
+ "rulesFile": "~/.sourcegraph/rules/global-rules.rule.md",
221
+ "preamble": "# Sourcegraph Cody — Global Rules\n\nGENERATED by agnostic-ai from the captured harness.\n"
222
+ },
223
+ {
224
+ "id": "openclaw",
225
+ "name": "OpenClaw",
226
+ "category": "Autonomous Agent",
227
+ "home": "~/.openclaw",
228
+ "dialect": "openclaw",
229
+ "rulesFile": "~/.openclaw/SYSTEM.md",
230
+ "skillsDir": "~/.openclaw/skills",
231
+ "preamble": "# SYSTEM.md — OpenClaw Agent System Prompt\n\nGENERATED by agnostic-ai from the captured harness.\n"
232
+ },
233
+ {
234
+ "id": "hermes",
235
+ "name": "Hermes Agent",
236
+ "category": "Autonomous Agent",
237
+ "home": "~/.hermes",
238
+ "dialect": "hermes",
239
+ "rulesFile": "~/.hermes/agent_system.md",
240
+ "skillsDir": "~/.hermes/skills",
241
+ "preamble": "# agent_system.md — Hermes System Prompt\n\nGENERATED by agnostic-ai from the captured harness.\n"
242
+ },
243
+ {
244
+ "id": "generic",
245
+ "name": "Generic LLM System Card",
246
+ "category": "Local / API",
247
+ "dialect": "generic",
248
+ "rulesFile": "storage/compiled/system_prompt.md",
249
+ "preamble": "# System Prompt for Autonomous LLM Agents\n\nGENERATED by agnostic-ai from the captured harness.\n"
250
+ }
251
+ ]
252
+ }
@@ -0,0 +1,199 @@
1
+ # engine/harness — the port engine
2
+
3
+ One harness, captured from the client you actually use, applied to every other
4
+ client so it behaves the same. This directory is the contract every adapter is
5
+ written against. Read it before adding a source or a target.
6
+
7
+ ```
8
+ capture (sources/<client>.cjs) -> harness/ bundle -> apply (targets/<client>.cjs)
9
+ ```
10
+
11
+ - `capture.cjs` reads the live config of ONE client (the *source*) into the
12
+ client-neutral bundle under `<repo>/harness/`.
13
+ - `apply.cjs` renders the bundle into every other installed client (the
14
+ *targets*), one guarded write per file, and records what it dropped and why.
15
+ - `status.cjs` reads every target from disk and reports, per component, whether
16
+ it is in sync. Nothing here is asserted from memory.
17
+ - `cli.cjs` `capture | apply | port | status | explain`.
18
+ - `index.cjs` the engine as a library: the entry a host product requires (see
19
+ "Embedding" below).
20
+ - `bundle.cjs` load / save / validate the bundle; secret scan.
21
+ - `common.cjs` shared helpers (paths, guarded writes, links, managed regions,
22
+ frontmatter, TOML strings).
23
+
24
+ ## Embedding
25
+
26
+ A host product (Leg, github.com/ucsandman/legcli, is the first) runs this
27
+ directory as a library and owns everything around it. Nothing in the engine
28
+ reads a repo file when the host passes the equivalent:
29
+
30
+ ```js
31
+ const engine = require('agnostic-ai/engine/harness/index.cjs');
32
+ engine.configure({
33
+ brand: { id: 'host', mark: 'GENERATED by Host', region: 'host' }, // the ownership claim in every generated file
34
+ secretPatterns: [/* regexes; replaces core/safety/guards.json */],
35
+ shimPath: '/abs/path/to/the/hook/shim', // what non-Codex hooks point at
36
+ importRoots: [], // dirs besides home an @import may resolve into
37
+ });
38
+ const registry = engine.loadRegistry(home, { targets: myTargets }); // replaces core/templates/targets.json
39
+ const port = { ...myPolicy, baseDir: myDir }; // replaces core/port.json; addenda resolve against baseDir
40
+ const { bundle, warnings } = engine.capture({ from: 'claude', home, outDir, registry, port });
41
+ const report = engine.apply({ bundle, home, registry, port, storageDir, to: ['codex'] });
42
+ engine.status({ bundle, home, registry, port, storageDir });
43
+ ```
44
+
45
+ `bundle.fingerprint(b)` is a content hash of everything a target renders from
46
+ (the manifest's timestamp excluded); `save()` records it in
47
+ `manifest.fingerprint`, so a host can answer "did the harness change since the
48
+ last apply" without diffing files. `common.GENERATED_MARK` is a live getter
49
+ over the configured brand. Node's ESM loader imports `index.cjs` directly.
50
+ Regression: `engine/tests/reg-harness.cjs` section 10.
51
+
52
+ ## The bundle (`<repo>/harness/`)
53
+
54
+ Client-neutral, human-readable, gitignored by default (it holds machine paths).
55
+ The Claude Code dialect is the canonical one for hooks and tool names because
56
+ Codex adopted a near-clone of it and every other client is mapped from it.
57
+
58
+ | File | Shape |
59
+ |---|---|
60
+ | `manifest.json` | `{ version, source, sourceHome, capturedAt, components: {rules, hooks, skills, agents, commands, mcp, permissions} }` (counts) |
61
+ | `rules.md` | The global working agreement with every `@import` inlined and the source client's own header stripped. Plain markdown, no client name in the prose that the port can avoid. |
62
+ | `identity.md` | Optional. SOUL.md / traits. |
63
+ | `hooks.json` | `{ "dialect": "claude", "events": { "<Event>": [ { "matcher"?: "A\|B", "hooks": [ { "type": "command", "command": "...", "timeout"?: 10, "statusMessage"?: "...", "async"?: false } ] } ] } }`. Events and matcher tokens use Claude Code names (`PreToolUse`, `Bash`, `Edit`, ...). |
64
+ | `mcp.json` | `{ "servers": { "<name>": { "transport": "stdio" \| "http" \| "sse", "command"?, "args"?: [], "env"?: {K: V}, "cwd"?, "url"?, "headers"?: {} } } }`. A secret-looking env value is replaced at capture by `"${NAME}"` and reported; targets render that as "read NAME from the environment". |
65
+ | `agents/<name>.md` | Frontmatter `name`, `description`, `model` (`fable` \| `opus` \| `sonnet` \| `haiku` \| `inherit` \| raw id), `tools` (comma list, Claude names), `readonly` (`true` when no edit tool is listed); body = the agent's instructions. |
66
+ | `commands/<name>.md` | Frontmatter `description`, `argument-hint`; body = the prompt. |
67
+ | `skills.json` | `{ "sourceDir": "<abs>", "skills": [ { "name", "path": "<abs dir with SKILL.md>" } ] }`. Skills are never copied: targets link each one, so an edit at the source is live everywhere. |
68
+ | `permissions.json` | `{ "allow": [...], "deny": [...], "ask": [...] }` in Claude pattern syntax (`Bash(git *)`). Targets map what they can and drop the rest with a reason. |
69
+
70
+ `bundle.cjs` validates the shape and refuses to save a bundle that carries a
71
+ value matching `core/safety/guards.json -> sensitivePatterns`.
72
+
73
+ ## `core/port.json`
74
+
75
+ The user-editable port policy (tracked; a template user commits their own):
76
+
77
+ ```jsonc
78
+ {
79
+ "source": "auto", // "auto" | "claude" | "codex"
80
+ "targets": "installed", // "installed" | "all" | ["codex", "gemini"]
81
+ "rules": { "dropSectionsForTargets": ["Delegation and Model Routing"] },
82
+ "hooks": {
83
+ "exclude": [ { "match": "<regex on the command>", "reason": "..." } ],
84
+ "extra": { "<targetId>": { "<Event>": [ <group> ] } } // target-only hooks appended after the ported ones
85
+ },
86
+ "skills": { "exclude": { "<name>": "reason" } },
87
+ "mcp": { "exclude": { "<name>": "reason" } },
88
+ "agents": { "modelLadder": { "<targetId>": { "fable": ["<model>", "<effort>"], "opus": [...], "sonnet": [...], "haiku": [...] } } }
89
+ }
90
+ ```
91
+
92
+ ## Target registry (`core/templates/targets.json`)
93
+
94
+ Every client is one entry. Paths are `~`-relative and expanded with
95
+ `common.expandPath`. Presence of `home` on disk means "installed".
96
+
97
+ | Field | Meaning |
98
+ |---|---|
99
+ | `id`, `name`, `category` | identity |
100
+ | `home` | the client's config dir; absent on disk = not installed = skipped by `"targets": "installed"` |
101
+ | `adapter` | which `targets/<adapter>.cjs` renders it (`generic` when omitted) |
102
+ | `rulesFile`, `preamble`, `traitsFile` | rules surface (every target has one) |
103
+ | `hooksConfigFile` | hook config the adapter manages (omit = client has no hooks) |
104
+ | `skillsDir`, `sharedSkillDirs` | where skills are linked; dirs the client already reads natively (a skill living there is not linked twice) |
105
+ | `agentsDir`, `commandsDir` | subagent and slash-command surfaces |
106
+ | `mcpConfigFile`, `mcpFormat` | MCP surface; `mcpFormat` is `mcpServers-json` (Claude/Cursor/Windsurf/Cline/Gemini shape), `codex-toml`, `opencode-json` |
107
+ | `dialect` | hook payload dialect for `engine/hooks/shim.cjs` (`claude`, `codex`, `gemini`, `agy`, `cursor`) |
108
+
109
+ ## Adapter contract
110
+
111
+ ### `sources/<id>.cjs`
112
+
113
+ ```js
114
+ module.exports = {
115
+ id: 'claude',
116
+ // Returns { bundle, warnings: [string] }. Never throws on a missing optional
117
+ // surface (no agents dir = zero agents). Throws only when the rules file is missing.
118
+ capture({ home, target, port }) {}
119
+ };
120
+ ```
121
+
122
+ `bundle` is the in-memory shape of the directory above: `{ manifest, rules,
123
+ identity, hooks, mcp, agents: [{name, meta, body}], commands: [{name, meta,
124
+ body}], skills, permissions }`. Use `bundle.createBundle()` to get an empty one.
125
+
126
+ ### `targets/<id>.cjs`
127
+
128
+ ```js
129
+ module.exports = {
130
+ id: 'codex',
131
+ components: ['rules', 'identity', 'hooks', 'skills', 'agents', 'commands', 'mcp', 'permissions'],
132
+ rules(ctx) {}, identity(ctx) {}, hooks(ctx) {}, skills(ctx) {}, agents(ctx) {}, commands(ctx) {}, mcp(ctx) {}, permissions(ctx) {}
133
+ };
134
+ ```
135
+
136
+ Every component function receives the same `ctx`:
137
+
138
+ | Field | What |
139
+ |---|---|
140
+ | `target` | the registry entry with every path already expanded to an absolute path |
141
+ | `bundle` | the loaded bundle |
142
+ | `port` | `core/port.json` |
143
+ | `home` | home dir being written (tests pass a temp dir; production passes `os.homedir()`) |
144
+ | `check` | true = report only, write nothing |
145
+ | `dryRun` | true = print what would change, write nothing (same as check but verbose) |
146
+ | `force` | true = overwrite hand-edited files |
147
+ | `state` | mutable object persisted to `storage/harness-state.json`; use `ctx.state.files[path] = sha` via `ctx.write` and `ctx.state.owned[targetId]` for links / entries the adapter created so it can prune them later |
148
+ | `backup(path)` | copy a file into the backups dir before an adapter prunes it; returns the backup path |
149
+ | `write(path, content, opts)` | the guarded writer: backs up, refuses hand edits unless `force`, honours `check`; returns `{ action: 'written' \| 'unchanged' \| 'would-write' \| 'skipped-hand-edited', backup }` |
150
+ | `link(src, dest)` | junction / symlink one directory; returns `{ action }`; never replaces a real directory |
151
+ | `log(line)` | human line for the CLI |
152
+
153
+ And returns a `ComponentResult`:
154
+
155
+ ```js
156
+ {
157
+ status: 'synced' | 'written' | 'stale' | 'skipped' | 'unsupported' | 'error',
158
+ files: [ { path, action } ], // every file touched or inspected
159
+ dropped: [ { item, reason } ], // what was NOT ported and why (shown by `explain`)
160
+ note: 'one line for the status table', // optional
161
+ error: 'message' // when status === 'error'
162
+ }
163
+ ```
164
+
165
+ Rules for every adapter:
166
+
167
+ 1. **One writer per file.** A file the adapter manages carries
168
+ `GENERATED by agnostic-ai` within its first five lines (the preamble; a
169
+ title line may come first) or a marked region
170
+ `# >>> agnostic-ai <component> start` / `# <<< agnostic-ai <component> end`
171
+ (files the user also edits, like `config.toml` and `settings.json` arrays).
172
+ Everything outside the region is preserved byte for byte.
173
+ 2. **Idempotent.** A second run with no source change writes nothing and
174
+ `--check` exits 0. Regions are rebuilt deterministically.
175
+ 3. **Never destroy user content.** `ctx.write` backs up before overwriting and
176
+ refuses a hand-edited file without `force`; links never replace a real
177
+ directory; prune only what `ctx.state.owned` says we created.
178
+ 4. **Every drop is explained.** A hook, skill, agent or server that cannot be
179
+ ported lands in `dropped` with a reason a stranger understands.
180
+ 5. **Facts come from disk.** A model id, a hook trust hash, a skill list is read
181
+ from the target's own files, never asserted from memory. Where a hash scheme
182
+ is reproduced (Codex hook trust), keep a self-test against a value the client
183
+ itself wrote.
184
+ 6. **Secrets never move.** An env value that looks like a token is written as an
185
+ environment reference, and the user is told which variable to export.
186
+
187
+ ## Hook dialect shim (`engine/hooks/shim.cjs`)
188
+
189
+ Hooks are written once, in the Claude Code dialect (JSON on stdin, decision on
190
+ stdout / exit 2). Clients that speak another dialect run them through the shim:
191
+
192
+ ```
193
+ node engine/hooks/shim.cjs --client cursor --event preToolUse -- node ~/.claude/hooks/rm-guard.cjs ++ node ~/.claude/hooks/scope-lock.cjs
194
+ ```
195
+
196
+ The shim translates the incoming payload to the Claude shape, runs each command
197
+ in the chain (first deny wins), and translates the decision back. Fail-open: a
198
+ crashed guard or unparseable payload yields "no opinion", never a deny that wedges
199
+ the client. Codex needs no shim.