@moretea-labs/forge 1.7.0 → 1.7.1

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 (175) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +13 -0
  3. package/CLAUDE.md +2 -0
  4. package/THIRD_PARTY_NOTICES.md +1 -1
  5. package/agents/openai.yaml +1 -1
  6. package/assets/partials-agents/02-operating-mode.partial.md +2 -0
  7. package/assets/plugin-registry.v1.json +3 -3
  8. package/assets/skill-version.json +2 -2
  9. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  10. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  11. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  12. package/assets/templates/helpers/workflow-contract.ts +5 -1
  13. package/docs/architecture/CURRENT.md +12 -4
  14. package/docs/forge-plugin-management.md +1 -1
  15. package/docs/operations/controller-performance-and-502.md +19 -0
  16. package/docs/operations/homebrew.md +2 -2
  17. package/package.json +5 -2
  18. package/scripts/architecture-event.ts +2 -820
  19. package/scripts/architecture-queue.sh +2 -645
  20. package/scripts/archive-architecture-request.sh +2 -242
  21. package/scripts/archive-workflow.sh +1 -291
  22. package/scripts/benchmark-work-efficiency.ts +0 -0
  23. package/scripts/capability-config.ts +2 -392
  24. package/scripts/capability-resolver.ts +2 -503
  25. package/scripts/capture-plan.sh +1 -332
  26. package/scripts/check-agent-tooling.sh +2 -1531
  27. package/scripts/check-architecture-sync.sh +2 -302
  28. package/scripts/check-brain-manifest.sh +2 -253
  29. package/scripts/check-context-files.sh +2 -47
  30. package/scripts/check-deploy-sql-order.sh +2 -115
  31. package/scripts/check-runtime-architecture.mjs +243 -16
  32. package/scripts/check-skill-version.ts +3 -191
  33. package/scripts/check-source-duplication.mjs +61 -0
  34. package/scripts/check-stable-baseline.ts +0 -0
  35. package/scripts/check-task-sync.sh +2 -63
  36. package/scripts/check-task-workflow.sh +2 -1000
  37. package/scripts/codex-handoff-resume.sh +2 -267
  38. package/scripts/context-contract-sync.sh +2 -514
  39. package/scripts/contract-run.ts +2 -463
  40. package/scripts/contract-worktree.sh +1 -702
  41. package/scripts/ensure-task-workflow.sh +1 -1192
  42. package/scripts/inspect-project-state.ts +4 -279
  43. package/scripts/lib/project-init-lib.sh +7 -2
  44. package/scripts/lib/run-forge-helper.sh +19 -0
  45. package/scripts/lib/run-forge-helper.ts +26 -0
  46. package/scripts/migrate-project-template.sh +1 -33
  47. package/scripts/migrate-workflow-docs.ts +4 -412
  48. package/scripts/new-plan.sh +1 -180
  49. package/scripts/new-spec.sh +1 -47
  50. package/scripts/new-sprint.sh +1 -46
  51. package/scripts/plan-to-todo.sh +1 -771
  52. package/scripts/prepare-codex-handoff.sh +2 -201
  53. package/scripts/prepare-handoff.sh +1 -78
  54. package/scripts/refresh-current-status.sh +2 -467
  55. package/scripts/run-governed-gate.ts +3 -0
  56. package/scripts/select-agent-context-blocks.sh +2 -64
  57. package/scripts/ship-worktrees.sh +1 -531
  58. package/scripts/sprint-backlog.sh +1 -834
  59. package/scripts/stage-runtime-release.ts +2 -1
  60. package/scripts/summarize-failures.sh +2 -123
  61. package/scripts/switch-plan.sh +1 -150
  62. package/scripts/sync-brain-docs.sh +2 -379
  63. package/scripts/sync-generated-authority.mjs +175 -0
  64. package/scripts/verify-contract.sh +2 -593
  65. package/scripts/verify-sprint.sh +1 -500
  66. package/scripts/workflow-contract.ts +4 -201
  67. package/scripts/workstream-sync.sh +2 -261
  68. package/src/cli/agent-jobs/executor-health.ts +1 -1
  69. package/src/cli/commands/plugin.ts +12 -3
  70. package/src/cli/controller/check-runner.ts +7 -2
  71. package/src/cli/controller/context-pack.ts +46 -21
  72. package/src/cli/github/contracts.ts +16 -0
  73. package/src/cli/github/github.ts +2 -16
  74. package/src/cli/github/session.ts +2 -1
  75. package/src/cli/local-bridge/facade-api.ts +37 -47
  76. package/src/cli/local-bridge/server.ts +2 -2
  77. package/src/cli/mcp/legacy-tool-service.ts +10 -33
  78. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  79. package/src/cli/mcp/repository-tools.ts +85 -89
  80. package/src/cli/mcp/server.ts +57 -13
  81. package/src/cli/mcp/tool-contract.ts +14 -0
  82. package/src/cli/mcp/toolset.ts +2 -2
  83. package/src/cli/mcp/transports/http.ts +23 -0
  84. package/src/cli/repositories/command-classifier.ts +2 -2
  85. package/src/cli/repositories/command-executor.ts +24 -6
  86. package/src/cli/repositories/repository-snapshot.ts +5 -2
  87. package/src/cli/repositories/structured-git.ts +57 -3
  88. package/src/cli/repository/inspector.ts +11 -3
  89. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  90. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  91. package/src/runtime/context/semantic-navigation.ts +9 -61
  92. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +18 -25
  93. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  94. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  95. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  96. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  97. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  98. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  99. package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
  100. package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
  101. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  102. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  103. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  104. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  105. package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
  106. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
  107. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  108. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  109. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
  110. package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
  111. package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
  112. package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
  113. package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
  114. package/src/runtime/control-plane/facade/index.ts +2 -0
  115. package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
  116. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  117. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  118. package/src/runtime/control-plane/facade/types.ts +68 -5
  119. package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
  120. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  121. package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
  122. package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
  123. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
  124. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
  125. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
  126. package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
  127. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  128. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  129. package/src/runtime/control-plane/routing/route-policy.ts +18 -6
  130. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  131. package/src/runtime/evidence/process-check-execution.ts +18 -0
  132. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  133. package/src/runtime/execution/process-runtime/check-result.ts +53 -18
  134. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  135. package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
  136. package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
  137. package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
  138. package/src/runtime/execution/process-runtime/runtime.ts +10 -0
  139. package/src/runtime/execution/process-runtime/types.ts +2 -12
  140. package/src/runtime/execution/work-activity.ts +21 -0
  141. package/src/runtime/execution/workers/executor.ts +18 -13
  142. package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
  143. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  144. package/src/runtime/gateway/mcp/router.ts +2 -2
  145. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
  146. package/src/runtime/gateway/mcp/runtime-tools.ts +805 -1009
  147. package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
  148. package/src/runtime/plugins/browser-adapter.ts +256 -1083
  149. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  150. package/src/runtime/plugins/browser-handoff.ts +18 -3
  151. package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
  152. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  153. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  154. package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
  155. package/src/runtime/plugins/browser-runtime.ts +10 -0
  156. package/src/runtime/plugins/browser-session-store.ts +121 -0
  157. package/src/runtime/plugins/browser-session-types.ts +98 -0
  158. package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
  159. package/src/runtime/plugins/external-adapter.ts +92 -58
  160. package/src/runtime/plugins/macos-capability-broker.ts +0 -8
  161. package/src/runtime/plugins/store.ts +66 -9
  162. package/src/runtime/plugins/types.ts +3 -1
  163. package/src/runtime/recovery/maintenance-executor.ts +61 -25
  164. package/src/runtime/root/package-connector-service.ts +46 -4
  165. package/src/runtime/root/package-runtime-release.ts +1 -1
  166. package/src/runtime/root/package-runtime-service.ts +33 -2
  167. package/src/runtime/root/release-manifest.ts +137 -16
  168. package/src/runtime/root/release-materialize.ts +109 -35
  169. package/src/runtime/root/types.ts +10 -0
  170. package/src/runtime/standalone-recovery/core.ts +425 -194
  171. package/src/runtime/standalone-recovery/entry.ts +1 -1
  172. package/src/runtime/standalone-recovery/installer.ts +1 -1
  173. package/src/runtime/workflow/schedules/engine.ts +113 -87
  174. package/src/runtime/workflow/schedules/settlement.ts +26 -23
  175. package/src/runtime/workflow/schedules/store.ts +79 -10
@@ -1,1533 +1,4 @@
1
1
  #!/bin/bash
2
2
  set -euo pipefail
3
-
4
- if command -v node >/dev/null 2>&1; then
5
- RUNTIME_BIN="$(command -v node)"
6
- elif command -v bun >/dev/null 2>&1; then
7
- RUNTIME_BIN="$(command -v bun)"
8
- elif [[ -x "${HOME}/.bun/bin/bun" ]]; then
9
- RUNTIME_BIN="${HOME}/.bun/bin/bun"
10
- else
11
- echo "check-agent-tooling.sh requires node or bun" >&2
12
- exit 1
13
- fi
14
-
15
- exec "$RUNTIME_BIN" - "$@" <<'NODE_EOF'
16
- const fs = require("fs");
17
- const crypto = require("crypto");
18
- const os = require("os");
19
- const path = require("path");
20
- const { spawnSync } = require("child_process");
21
-
22
- const argv = process.argv.slice(2);
23
- let jsonOutput = false;
24
- let checkUpdates = false;
25
- let strictReadiness = false;
26
- let hostMode = "both";
27
-
28
- function usage() {
29
- console.log(`Usage: scripts/check-agent-tooling.sh [--json] [--check-updates] [--strict-readiness] [--host claude|codex|both]`);
30
- }
31
-
32
- for (let index = 0; index < argv.length; index += 1) {
33
- const arg = argv[index];
34
- if (arg === "--json") {
35
- jsonOutput = true;
36
- continue;
37
- }
38
- if (arg === "--check-updates") {
39
- checkUpdates = true;
40
- continue;
41
- }
42
- if (arg === "--strict-readiness") {
43
- strictReadiness = true;
44
- continue;
45
- }
46
- if (arg === "--host") {
47
- const next = argv[index + 1];
48
- if (!next) {
49
- console.error("--host requires claude, codex, or both");
50
- process.exit(1);
51
- }
52
- hostMode = next;
53
- index += 1;
54
- continue;
55
- }
56
- if (arg === "--help" || arg === "-h") {
57
- usage();
58
- process.exit(0);
59
- }
60
- console.error(`Unknown argument: ${arg}`);
61
- usage();
62
- process.exit(1);
63
- }
64
-
65
- if (!["claude", "codex", "both"].includes(hostMode)) {
66
- console.error(`Unsupported host: ${hostMode}`);
67
- process.exit(1);
68
- }
69
-
70
- const HOME = os.homedir();
71
- const REPO_ROOT = process.cwd();
72
- const SELECTED_HOSTS = hostMode === "both" ? ["claude", "codex"] : [hostMode];
73
- const WAZA_SOURCE_REPO = "tw93/Waza";
74
- const WAZA_SOURCE_URL = "https://github.com/tw93/Waza.git";
75
- const WAZA_RAW_BASE_URL = "https://raw.githubusercontent.com/tw93/Waza/main";
76
- const WAZA_MANAGED_SKILLS = ["think", "hunt", "check", "health"];
77
- const WAZA_SHARED_RULES = ["anti-patterns.md", "chinese.md", "durable-context.md", "english.md"];
78
- const CODEX_AUTOMATION_SKILLS = ["health", "check", "mermaid"];
79
- const CODEGRAPH_PACKAGE = "@colbymchenry/codegraph";
80
- const CODEGRAPH_GLOBAL_INSTALL_COMMAND = `bun add -g ${CODEGRAPH_PACKAGE} && forge tools configure codegraph --target codex --location global`;
81
- const GBRAIN_INSTALL_COMMAND = "bun install -g github:garrytan/gbrain";
82
- const GBRAIN_INSTALL_NOTE =
83
- "Install from GitHub; npm registry package gbrain is an unrelated GPU library and does not ship this CLI.";
84
- const CODEGRAPH_MCP_CONFIGURE_COMMAND = "forge tools configure codegraph --target <codex|claude|both> --location global";
85
- const CODEGRAPH_LOCAL_INSTALL_COMMAND = "bun install";
86
- const CODEGRAPH_ENSURE_COMMAND = [
87
- ".ai/harness/scripts/ensure-codegraph.sh",
88
- "scripts/ensure-codegraph.sh",
89
- ].find((relPath) => fs.existsSync(path.join(REPO_ROOT, relPath)));
90
- const CODEGRAPH_ENSURE_BASH_COMMAND = CODEGRAPH_ENSURE_COMMAND
91
- ? `bash ${CODEGRAPH_ENSURE_COMMAND}`
92
- : null;
93
- const WAZA_STAGING_ROOT = path.join(HOME, ".agents");
94
- const WAZA_STAGING_DIR = path.join(WAZA_STAGING_ROOT, "skills");
95
- const WAZA_STAGING_RULES_DIR = path.join(WAZA_STAGING_ROOT, "rules");
96
- let timeoutBin;
97
- const HOSTS = {
98
- claude: {
99
- label: "Claude Code",
100
- agentLabel: "Claude Code",
101
- skillsDir: path.join(HOME, ".claude", "skills"),
102
- gstackDir: path.join(HOME, ".claude", "skills", "gstack"),
103
- configPath: path.join(HOME, ".claude", "settings.json"),
104
- },
105
- codex: {
106
- label: "Codex",
107
- agentLabel: "Codex",
108
- skillsDir: path.join(HOME, ".codex", "skills"),
109
- gstackDir: path.join(HOME, ".codex", "skills", "gstack"),
110
- configPath: path.join(HOME, ".codex", "config.toml"),
111
- },
112
- };
113
-
114
- function readText(filePath) {
115
- try {
116
- return fs.readFileSync(filePath, "utf8");
117
- } catch (_error) {
118
- return "";
119
- }
120
- }
121
-
122
- function readJson(filePath) {
123
- try {
124
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
125
- } catch (_error) {
126
- return null;
127
- }
128
- }
129
-
130
- function fileIsExecutable(filePath) {
131
- try {
132
- fs.accessSync(filePath, fs.constants.X_OK);
133
- return true;
134
- } catch (_error) {
135
- return false;
136
- }
137
- }
138
-
139
- function detectTimeoutBin() {
140
- if (timeoutBin !== undefined) return timeoutBin;
141
- timeoutBin = resolvePathCommand("timeout") || "";
142
- return timeoutBin;
143
- }
144
-
145
- function commandCapability(command, requiredFor, owner, required = false) {
146
- const binPath = resolvePathCommand(command);
147
- return {
148
- name: command,
149
- status: binPath ? "present" : "missing",
150
- path: binPath,
151
- owner,
152
- required,
153
- required_for: requiredFor,
154
- };
155
- }
156
-
157
- function detectSymlinkCapability() {
158
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "forge-symlink-check-"));
159
- const source = path.join(tmpDir, "source");
160
- const link = path.join(tmpDir, "link");
161
- try {
162
- fs.writeFileSync(source, "ok\n");
163
- fs.symlinkSync(source, link);
164
- return {
165
- name: "symlink",
166
- status: "supported",
167
- path: null,
168
- owner: "platform-filesystem",
169
- required: false,
170
- required_for: "installed-copy link mode and host skill aliasing; copy mode remains the fallback",
171
- };
172
- } catch (error) {
173
- return {
174
- name: "symlink",
175
- status: "unsupported",
176
- path: null,
177
- owner: "platform-filesystem",
178
- required: false,
179
- required_for: "installed-copy link mode and host skill aliasing; copy mode remains the fallback",
180
- reason: String(error?.message || error),
181
- };
182
- } finally {
183
- fs.rmSync(tmpDir, { recursive: true, force: true });
184
- }
185
- }
186
-
187
- function run(command, args, options = {}) {
188
- const timeoutMs = options.timeoutMs ?? 0;
189
- const externalTimeout = timeoutMs > 0 ? detectTimeoutBin() : "";
190
- const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1000));
191
- const spawnCommand = externalTimeout || command;
192
- const spawnArgs = externalTimeout ? ["--kill-after=1s", `${timeoutSeconds}s`, command, ...args] : args;
193
- const result = spawnSync(spawnCommand, spawnArgs, {
194
- cwd: options.cwd ?? REPO_ROOT,
195
- encoding: "utf8",
196
- env: { ...process.env, ...(options.env ?? {}) },
197
- timeout: externalTimeout ? timeoutMs + 1000 : timeoutMs,
198
- });
199
-
200
- return {
201
- ok: result.status === 0 && !result.error,
202
- status: result.status,
203
- stdout: result.stdout ?? "",
204
- stderr: result.stderr ?? "",
205
- error: result.error ? String(result.error.message || result.error) : "",
206
- timed_out: result.error?.code === "ETIMEDOUT" || result.status === 124,
207
- };
208
- }
209
-
210
- function parseJson(text) {
211
- try {
212
- return JSON.parse(text);
213
- } catch (_error) {
214
- return null;
215
- }
216
- }
217
-
218
- function sha1(text) {
219
- return crypto.createHash("sha1").update(text).digest("hex");
220
- }
221
-
222
- function sha1Buffer(buffer) {
223
- return crypto.createHash("sha1").update(buffer).digest("hex");
224
- }
225
-
226
- function parseSkillVersion(text) {
227
- const match = text.match(/^\s*version:\s*["']?([^"'\n]+)["']?/m);
228
- return match ? match[1].trim() : null;
229
- }
230
-
231
- function resolveRealPath(filePath) {
232
- try {
233
- return fs.realpathSync(filePath);
234
- } catch (_error) {
235
- return null;
236
- }
237
- }
238
-
239
- function readSkillFile(filePath) {
240
- const content = readText(filePath);
241
- if (!content) {
242
- return {
243
- exists: false,
244
- version: null,
245
- hash: null,
246
- };
247
- }
248
-
249
- return {
250
- exists: true,
251
- version: parseSkillVersion(content),
252
- hash: sha1(content),
253
- };
254
- }
255
-
256
- function readFileHash(filePath) {
257
- try {
258
- return {
259
- exists: true,
260
- hash: sha1Buffer(fs.readFileSync(filePath)),
261
- };
262
- } catch (_error) {
263
- return {
264
- exists: false,
265
- hash: null,
266
- };
267
- }
268
- }
269
-
270
- function collectDirectoryHashes(dirPath) {
271
- try {
272
- if (!fs.statSync(dirPath).isDirectory()) return null;
273
- } catch (_error) {
274
- return null;
275
- }
276
-
277
- const files = {};
278
- function visit(currentDir, relativeDir) {
279
- let entries = [];
280
- try {
281
- entries = fs.readdirSync(currentDir, { withFileTypes: true });
282
- } catch (_error) {
283
- return;
284
- }
285
- for (const entry of entries) {
286
- const absolutePath = path.join(currentDir, entry.name);
287
- const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
288
- let stat;
289
- try {
290
- stat = fs.statSync(absolutePath);
291
- } catch (_error) {
292
- continue;
293
- }
294
- if (stat.isDirectory()) {
295
- visit(absolutePath, relativePath);
296
- } else if (stat.isFile()) {
297
- files[relativePath] = sha1Buffer(fs.readFileSync(absolutePath));
298
- }
299
- }
300
- }
301
-
302
- visit(dirPath, "");
303
- return files;
304
- }
305
-
306
- function compareFileMaps(localFiles, referenceFiles) {
307
- const missing = [];
308
- const extra = [];
309
- const changed = [];
310
- const localKeys = new Set(Object.keys(localFiles || {}));
311
- const referenceKeys = new Set(Object.keys(referenceFiles || {}));
312
-
313
- for (const key of [...referenceKeys].sort()) {
314
- if (!localKeys.has(key)) {
315
- missing.push(key);
316
- } else if (localFiles[key] !== referenceFiles[key]) {
317
- changed.push(key);
318
- }
319
- }
320
-
321
- for (const key of [...localKeys].sort()) {
322
- if (!referenceKeys.has(key)) {
323
- extra.push(key);
324
- }
325
- }
326
-
327
- return { missing, extra, changed };
328
- }
329
-
330
- function inspectDirectorySync(localDir, stagingDir) {
331
- const localFiles = collectDirectoryHashes(localDir);
332
- const stagingFiles = collectDirectoryHashes(stagingDir);
333
-
334
- if (!localFiles && !stagingFiles) {
335
- return {
336
- status: "unknown",
337
- missing_files: [],
338
- extra_files: [],
339
- changed_files: [],
340
- };
341
- }
342
-
343
- if (!localFiles && stagingFiles) {
344
- return {
345
- status: "missing-local",
346
- missing_files: Object.keys(stagingFiles).sort(),
347
- extra_files: [],
348
- changed_files: [],
349
- };
350
- }
351
-
352
- if (localFiles && !stagingFiles) {
353
- return {
354
- status: "unknown",
355
- missing_files: [],
356
- extra_files: [],
357
- changed_files: [],
358
- };
359
- }
360
-
361
- const diff = compareFileMaps(localFiles, stagingFiles);
362
- const clean = diff.missing.length === 0 && diff.extra.length === 0 && diff.changed.length === 0;
363
- return {
364
- status: clean ? "synced" : "drift",
365
- missing_files: diff.missing,
366
- extra_files: diff.extra,
367
- changed_files: diff.changed,
368
- };
369
- }
370
-
371
- function summarizeStatus(hostStatuses) {
372
- const values = Object.values(hostStatuses);
373
- const presentCount = values.filter((entry) => entry.present).length;
374
- if (presentCount === 0) return "missing";
375
- if (presentCount === values.length) return "present";
376
- return "partial";
377
- }
378
-
379
- function detectRepoGstackTeamMode() {
380
- const claudeMd = readText(path.join(REPO_ROOT, "CLAUDE.md"));
381
- const settings = readText(path.join(REPO_ROOT, ".claude", "settings.json"));
382
- const hookPath = path.join(REPO_ROOT, ".claude", "hooks", "check-gstack.sh");
383
-
384
- if (settings.includes("check-gstack.sh") || fs.existsSync(hookPath) || claudeMd.includes("## gstack (REQUIRED")) {
385
- return {
386
- status: "required",
387
- reason: "Repo has gstack enforcement traces (required CLAUDE.md section or check-gstack hook).",
388
- };
389
- }
390
-
391
- if (claudeMd.includes("## gstack")) {
392
- return {
393
- status: "optional",
394
- reason: "Repo has a gstack guidance section in CLAUDE.md but no enforcement hook.",
395
- };
396
- }
397
-
398
- return {
399
- status: "not-detected",
400
- reason: "No repo-local gstack team-mode traces detected in CLAUDE.md or the shared .ai/hooks/ layer.",
401
- };
402
- }
403
-
404
- function detectGstack() {
405
- const hostStatuses = {};
406
-
407
- for (const host of SELECTED_HOSTS) {
408
- const meta = HOSTS[host];
409
- const present = fs.existsSync(meta.gstackDir);
410
- const versionFile = path.join(meta.gstackDir, "VERSION");
411
- const gitDir = path.join(meta.gstackDir, ".git");
412
- const version = present && fs.existsSync(versionFile) ? readText(versionFile).trim() : "";
413
- let updateStatus = checkUpdates ? "unknown" : "not-checked";
414
- let origin = "";
415
- let head = "";
416
- let remoteHead = "";
417
- let updateReason = "";
418
-
419
- if (present && checkUpdates && fs.existsSync(gitDir)) {
420
- const originResult = run("git", ["-C", meta.gstackDir, "remote", "get-url", "origin"], { timeoutMs: 1000 });
421
- if (originResult.ok) {
422
- origin = originResult.stdout.trim();
423
- }
424
-
425
- const headResult = run("git", ["-C", meta.gstackDir, "rev-parse", "HEAD"], { timeoutMs: 1000 });
426
- if (headResult.ok) {
427
- head = headResult.stdout.trim();
428
- }
429
-
430
- const remoteResult = run("git", ["-C", meta.gstackDir, "ls-remote", "--symref", "origin", "HEAD"], { timeoutMs: 1500 });
431
- if (remoteResult.ok) {
432
- const match = remoteResult.stdout.match(/^([0-9a-f]+)\s+HEAD$/m);
433
- remoteHead = match ? match[1] : "";
434
- }
435
-
436
- if (head && remoteHead) {
437
- updateStatus = head === remoteHead ? "up-to-date" : "update-available";
438
- updateReason = head === remoteHead
439
- ? "Local gstack matches origin/HEAD."
440
- : "Local gstack HEAD differs from origin/HEAD."
441
- } else if (origin || head) {
442
- updateStatus = "unknown";
443
- updateReason = remoteResult.timed_out
444
- ? "Timed out while checking gstack origin/HEAD."
445
- : "Unable to resolve both local and remote HEAD for gstack."
446
- }
447
- } else if (present) {
448
- updateStatus = checkUpdates ? "unknown" : "not-checked";
449
- updateReason = fs.existsSync(gitDir)
450
- ? "Update checks were skipped."
451
- : "gstack install is present but not a full git checkout in this host path.";
452
- }
453
-
454
- hostStatuses[host] = {
455
- label: meta.label,
456
- present,
457
- path: meta.gstackDir,
458
- version: version || null,
459
- origin: origin || null,
460
- head: head || null,
461
- remote_head: remoteHead || null,
462
- update_status: updateStatus,
463
- reason: present
464
- ? (updateReason || `Detected gstack at ${meta.gstackDir}.`)
465
- : `Missing gstack at ${meta.gstackDir}.`,
466
- install_command: host === "claude"
467
- ? "git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup"
468
- : `${fs.existsSync(HOSTS.claude.gstackDir) ? "cd ~/.claude/skills/gstack" : "git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack"} && ./setup --host codex`,
469
- upgrade_command: host === "claude"
470
- ? "cd ~/.claude/skills/gstack && git pull && ./setup"
471
- : "cd ~/.claude/skills/gstack && git pull && ./setup --host codex",
472
- };
473
- }
474
-
475
- const repoTeamMode = detectRepoGstackTeamMode();
476
- const status = summarizeStatus(hostStatuses);
477
- const selectedMeta = Object.values(hostStatuses);
478
- const installCommand = SELECTED_HOSTS.length === 2
479
- ? "git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup && ./setup --host codex"
480
- : selectedMeta[0].install_command;
481
- const upgradeCommand = SELECTED_HOSTS.length === 2
482
- ? "cd ~/.claude/skills/gstack && git pull && ./setup && ./setup --host codex"
483
- : selectedMeta[0].upgrade_command;
484
-
485
- return {
486
- name: "gstack",
487
- status,
488
- reason: status === "present"
489
- ? `Detected gstack in all requested hosts (${SELECTED_HOSTS.join(", ")}).`
490
- : status === "partial"
491
- ? `Detected gstack in ${selectedMeta.filter((entry) => entry.present).length}/${selectedMeta.length} requested hosts.`
492
- : "gstack is missing from all requested hosts.",
493
- hosts: hostStatuses,
494
- repo_team_mode: repoTeamMode,
495
- install_command: installCommand,
496
- upgrade_command: upgradeCommand,
497
- impact: {
498
- complex_tasks: status === "present" ? "full" : status === "partial" ? "degraded" : "missing",
499
- simple_tasks: "unaffected",
500
- knowledge_tasks: "unaffected",
501
- },
502
- };
503
- }
504
-
505
- function summarizeWazaStatus(hostStatuses) {
506
- const values = Object.values(hostStatuses);
507
- const fullCount = values.filter((entry) => entry.status === "present").length;
508
- const installedCount = values.reduce((count, entry) => count + entry.installed_skills.length, 0);
509
- if (fullCount === values.length) return "present";
510
- if (installedCount > 0) return "partial";
511
- return "missing";
512
- }
513
-
514
- function fetchWazaUpstreamSkills() {
515
- if (!checkUpdates) {
516
- return {
517
- status: "not-checked",
518
- reason: "Update checks were skipped.",
519
- skills: {},
520
- rules: {},
521
- };
522
- }
523
-
524
- const skills = {};
525
- const rules = {};
526
- const failures = [];
527
-
528
- for (const skill of WAZA_MANAGED_SKILLS) {
529
- const url = `${WAZA_RAW_BASE_URL}/skills/${skill}/SKILL.md`;
530
- const result = run("curl", ["-fsSL", "--max-time", "5", url], { timeoutMs: 7000 });
531
- if (!result.ok || !result.stdout) {
532
- failures.push(`skills/${skill}/SKILL.md`);
533
- continue;
534
- }
535
-
536
- skills[skill] = {
537
- version: parseSkillVersion(result.stdout),
538
- hash: sha1(result.stdout),
539
- source_url: url,
540
- };
541
- }
542
-
543
- for (const rule of WAZA_SHARED_RULES) {
544
- const url = `${WAZA_RAW_BASE_URL}/rules/${rule}`;
545
- const result = run("curl", ["-fsSL", "--max-time", "5", url], { timeoutMs: 7000 });
546
- if (!result.ok || !result.stdout) {
547
- failures.push(`rules/${rule}`);
548
- continue;
549
- }
550
-
551
- rules[rule] = {
552
- hash: sha1(result.stdout),
553
- source_url: url,
554
- };
555
- }
556
-
557
- if (failures.length > 0) {
558
- return {
559
- status: "unknown",
560
- reason: `Unable to fetch upstream Waza files for: ${failures.join(", ")}.`,
561
- skills,
562
- rules,
563
- };
564
- }
565
-
566
- return {
567
- status: "fetched",
568
- reason: "Fetched upstream Waza SKILL.md and shared rule files from GitHub raw URLs.",
569
- skills,
570
- rules,
571
- };
572
- }
573
-
574
- function hostUsesStagingSkillSymlinks(host) {
575
- const meta = HOSTS[host];
576
- const presentSkillDirs = WAZA_MANAGED_SKILLS
577
- .map((skill) => path.join(meta.skillsDir, skill))
578
- .filter((skillDir) => fs.existsSync(skillDir));
579
-
580
- if (presentSkillDirs.length === 0) return false;
581
-
582
- const stagingRealPath = resolveRealPath(WAZA_STAGING_DIR) || WAZA_STAGING_DIR;
583
- return presentSkillDirs.every((skillDir) => {
584
- const realPath = resolveRealPath(skillDir);
585
- return realPath
586
- ? realPath.startsWith(`${WAZA_STAGING_DIR}${path.sep}`) || realPath.startsWith(`${stagingRealPath}${path.sep}`)
587
- : false;
588
- });
589
- }
590
-
591
- function resolveWazaRulePath(host, rule) {
592
- const meta = HOSTS[host];
593
- const hostRulesPath = path.join(path.dirname(meta.skillsDir), "rules", rule);
594
- if (fs.existsSync(hostRulesPath)) return hostRulesPath;
595
- if (hostUsesStagingSkillSymlinks(host)) {
596
- return path.join(WAZA_STAGING_RULES_DIR, rule);
597
- }
598
- return hostRulesPath;
599
- }
600
-
601
- function inspectWazaSharedRule(host, rule, upstreamRules) {
602
- const localFile = resolveWazaRulePath(host, rule);
603
- const stagingFile = path.join(WAZA_STAGING_RULES_DIR, rule);
604
- const local = readFileHash(localFile);
605
- const staging = readFileHash(stagingFile);
606
- const upstream = upstreamRules[rule] || null;
607
-
608
- return {
609
- name: rule,
610
- path: localFile,
611
- real_path: resolveRealPath(localFile),
612
- present: local.exists,
613
- hash: local.hash,
614
- staging_present: staging.exists,
615
- staging_hash: staging.hash,
616
- staging_sync: local.exists && staging.exists
617
- ? (local.hash === staging.hash ? "synced" : "drift")
618
- : staging.exists
619
- ? "missing-local"
620
- : "unknown",
621
- upstream_hash: upstream?.hash || null,
622
- stale_status: !checkUpdates
623
- ? "not-checked"
624
- : upstream?.hash && local.exists
625
- ? (local.hash === upstream.hash ? "up-to-date" : "stale")
626
- : upstream?.hash
627
- ? "missing-local"
628
- : "unknown",
629
- };
630
- }
631
-
632
- function inspectWazaSkill(host, skill, skillLock, skillItems, upstreamSkills) {
633
- const meta = HOSTS[host];
634
- const skillDir = path.join(meta.skillsDir, skill);
635
- const skillFile = path.join(skillDir, "SKILL.md");
636
- const stagingFile = path.join(WAZA_STAGING_DIR, skill, "SKILL.md");
637
- const stagingDir = path.join(WAZA_STAGING_DIR, skill);
638
- const local = readSkillFile(skillFile);
639
- const staging = readSkillFile(stagingFile);
640
- const directorySync = inspectDirectorySync(skillDir, stagingDir);
641
- const upstream = upstreamSkills[skill] || null;
642
- let symlinkTarget = null;
643
-
644
- try {
645
- const stat = fs.lstatSync(skillDir);
646
- if (stat.isSymbolicLink()) {
647
- symlinkTarget = fs.readlinkSync(skillDir);
648
- }
649
- } catch (_error) {
650
- symlinkTarget = null;
651
- }
652
-
653
- const skillCliItem = skillItems.find((item) => item.name === skill);
654
- const skillCliAgents = Array.isArray(skillCliItem?.agents) ? skillCliItem.agents : [];
655
- const sourceLock = skillLock?.skills?.[skill] || null;
656
-
657
- return {
658
- name: skill,
659
- path: skillFile,
660
- real_path: resolveRealPath(skillFile),
661
- symlink_target: symlinkTarget,
662
- present: local.exists,
663
- version: local.version,
664
- hash: local.hash,
665
- source_locked: sourceLock?.source === WAZA_SOURCE_REPO,
666
- source_repo: sourceLock?.source || null,
667
- skills_cli_agents: skillCliAgents,
668
- staging_present: staging.exists,
669
- staging_version: staging.version,
670
- staging_hash: staging.hash,
671
- staging_sync: directorySync.status,
672
- staging_missing_files: directorySync.missing_files,
673
- staging_extra_files: directorySync.extra_files,
674
- staging_changed_files: directorySync.changed_files,
675
- upstream_version: upstream?.version || null,
676
- upstream_hash: upstream?.hash || null,
677
- stale_status: !checkUpdates
678
- ? "not-checked"
679
- : upstream?.hash && local.exists
680
- ? (local.hash === upstream.hash ? "up-to-date" : "stale")
681
- : upstream?.hash
682
- ? "missing-local"
683
- : "unknown",
684
- };
685
- }
686
-
687
- function detectWaza() {
688
- const skillLockPath = path.join(HOME, ".agents", ".skill-lock.json");
689
- const skillLock = readJson(skillLockPath);
690
- const skillsBin = resolvePathCommand("skills");
691
- const skillsResult = skillsBin
692
- ? run(skillsBin, ["ls", "-g", "--json"], { timeoutMs: 1500 })
693
- : { ok: false, status: null, stdout: "", stderr: "", error: "skills CLI is not installed", timed_out: false };
694
- const skillItems = skillsResult.ok ? parseJson(skillsResult.stdout) || [] : [];
695
- const wazaEntries = Object.entries(skillLock?.skills || {}).filter(([, meta]) => meta?.source === WAZA_SOURCE_REPO);
696
- const upstream = fetchWazaUpstreamSkills();
697
- const hostStatuses = {};
698
-
699
- for (const host of SELECTED_HOSTS) {
700
- const skills = WAZA_MANAGED_SKILLS.map((skill) => inspectWazaSkill(host, skill, skillLock, skillItems, upstream.skills));
701
- const sharedRules = WAZA_SHARED_RULES.map((rule) => inspectWazaSharedRule(host, rule, upstream.rules));
702
- const installedSkills = skills.filter((entry) => entry.present).map((entry) => entry.name);
703
- const missingSkills = skills.filter((entry) => !entry.present).map((entry) => entry.name);
704
- const driftSkills = skills.filter((entry) => entry.staging_sync === "drift").map((entry) => entry.name);
705
- const unsyncedSkills = skills
706
- .filter((entry) => entry.staging_sync === "drift" || entry.staging_sync === "missing-local")
707
- .map((entry) => entry.name);
708
- const staleSkills = skills.filter((entry) => entry.stale_status === "stale").map((entry) => entry.name);
709
- const installedSharedRules = sharedRules.filter((entry) => entry.present).map((entry) => entry.name);
710
- const missingSharedRules = sharedRules.filter((entry) => !entry.present).map((entry) => entry.name);
711
- const driftSharedRules = sharedRules.filter((entry) => entry.staging_sync === "drift").map((entry) => entry.name);
712
- const unsyncedSharedRules = sharedRules
713
- .filter((entry) => entry.staging_sync === "drift" || entry.staging_sync === "missing-local")
714
- .map((entry) => entry.name);
715
- const staleSharedRules = sharedRules
716
- .filter((entry) => entry.stale_status === "stale" || entry.stale_status === "missing-local")
717
- .map((entry) => entry.name);
718
- const status = missingSkills.length === 0 ? "present" : installedSkills.length > 0 ? "partial" : "missing";
719
- const stagingSync = status === "missing"
720
- ? "missing"
721
- : unsyncedSkills.length > 0 || unsyncedSharedRules.length > 0
722
- ? "drift"
723
- : skills.every((entry) => entry.staging_sync === "synced") && sharedRules.every((entry) => entry.staging_sync === "synced")
724
- ? "synced"
725
- : "unknown";
726
- const staleStatus = !checkUpdates
727
- ? "not-checked"
728
- : staleSkills.length > 0 || staleSharedRules.length > 0
729
- ? "stale"
730
- : skills.every((entry) => entry.stale_status === "up-to-date") && sharedRules.every((entry) => entry.stale_status === "up-to-date")
731
- ? "up-to-date"
732
- : "unknown";
733
- const sharedRulesStagingSync = unsyncedSharedRules.length > 0
734
- ? "drift"
735
- : sharedRules.every((entry) => entry.staging_sync === "synced")
736
- ? "synced"
737
- : "unknown";
738
- const sharedRulesStaleStatus = !checkUpdates
739
- ? "not-checked"
740
- : staleSharedRules.length > 0
741
- ? "stale"
742
- : sharedRules.every((entry) => entry.stale_status === "up-to-date")
743
- ? "up-to-date"
744
- : "unknown";
745
-
746
- hostStatuses[host] = {
747
- label: HOSTS[host].label,
748
- path: HOSTS[host].skillsDir,
749
- status,
750
- present: status === "present",
751
- installed_skills: installedSkills,
752
- missing_skills: missingSkills,
753
- drift_skills: driftSkills,
754
- stale_skills: staleSkills,
755
- shared_rules: installedSharedRules,
756
- missing_shared_rules: missingSharedRules,
757
- drift_shared_rules: driftSharedRules,
758
- stale_shared_rules: staleSharedRules,
759
- shared_rules_staging_sync: sharedRulesStagingSync,
760
- shared_rules_stale_status: sharedRulesStaleStatus,
761
- versions: Object.fromEntries(skills.filter((entry) => entry.present).map((entry) => [entry.name, entry.version])),
762
- staging_sync: stagingSync,
763
- stale_status: staleStatus,
764
- skills,
765
- shared_rule_details: sharedRules,
766
- reason: status === "present"
767
- ? `Detected all ${WAZA_MANAGED_SKILLS.length} Waza skills for ${HOSTS[host].label} from the real host skill path.`
768
- : status === "partial"
769
- ? `Detected ${installedSkills.length}/${WAZA_MANAGED_SKILLS.length} Waza skills for ${HOSTS[host].label}; missing ${missingSkills.join(", ")}.`
770
- : `No Waza skills detected at ${HOSTS[host].skillsDir}.`,
771
- };
772
- }
773
-
774
- const staleSkillSet = new Set();
775
- const staleRuleSet = new Set();
776
- for (const host of Object.values(hostStatuses)) {
777
- for (const skill of host.stale_skills) staleSkillSet.add(skill);
778
- for (const rule of host.stale_shared_rules) staleRuleSet.add(rule);
779
- }
780
- const updateStatus = !checkUpdates
781
- ? "not-checked"
782
- : upstream.status === "unknown"
783
- ? "unknown"
784
- : staleSkillSet.size > 0 || staleRuleSet.size > 0
785
- ? "update-available"
786
- : "up-to-date";
787
- const updateReason = !checkUpdates
788
- ? "Update checks were skipped."
789
- : upstream.status === "unknown"
790
- ? upstream.reason
791
- : staleSkillSet.size > 0 || staleRuleSet.size > 0
792
- ? `Upstream Waza files differ for: ${[
793
- ...[...staleSkillSet].sort().map((skill) => `skills/${skill}/SKILL.md`),
794
- ...[...staleRuleSet].sort().map((rule) => `rules/${rule}`),
795
- ].join(", ")}.`
796
- : "Local Waza SKILL.md and shared rule files match upstream GitHub raw content.";
797
-
798
- const status = summarizeWazaStatus(hostStatuses);
799
- const installCommand = `npx -y skills add tw93/Waza -g -a ${
800
- hostMode === "both" ? "claude-code codex" : hostMode === "claude" ? "claude-code" : "codex"
801
- } -s think hunt check health -y`;
802
- const rulesList = WAZA_SHARED_RULES.join(" ");
803
- const syncCommand = `for d in ${WAZA_MANAGED_SKILLS.join(" ")}; do rsync -a --delete ~/.agents/skills/$d/ ~/.codex/skills/$d/; done; mkdir -p ~/.codex/rules; for f in ${rulesList}; do cp ~/.agents/rules/$f ~/.codex/rules/$f; done`;
804
-
805
- return {
806
- name: "waza",
807
- status,
808
- reason: status === "present"
809
- ? `Detected Waza in all requested real host paths (${SELECTED_HOSTS.join(", ")}).`
810
- : status === "partial"
811
- ? "Waza is installed for some requested host paths or only partially installed."
812
- : "No managed Waza skills were found in the requested real host paths.",
813
- source_lock_file: fs.existsSync(skillLockPath) ? skillLockPath : null,
814
- source_repo: WAZA_SOURCE_REPO,
815
- source_url: WAZA_SOURCE_URL,
816
- managed_skills: WAZA_MANAGED_SKILLS,
817
- shared_rules: WAZA_SHARED_RULES,
818
- primary_host: "codex",
819
- codex_primary_path: path.join(HOME, ".codex", "skills"),
820
- staging_cache_path: WAZA_STAGING_DIR,
821
- staging_rules_path: WAZA_STAGING_RULES_DIR,
822
- sync_mode: "codex-first-copy-from-staging",
823
- host_drift_policy: "report-per-host-directory-rule-staging-and-upstream-drift",
824
- skills_cli_status: skillsResult.ok ? "available" : skillsResult.timed_out ? "timed-out" : "unavailable",
825
- source_lock_entries: wazaEntries.map(([name]) => name).sort(),
826
- upstream_status: upstream.status,
827
- upstream_reason: upstream.reason,
828
- upstream_skills: upstream.skills,
829
- upstream_rules: upstream.rules,
830
- hosts: hostStatuses,
831
- update_status: updateStatus,
832
- update_reason: updateReason,
833
- install_command: installCommand,
834
- stage_command: "npx -y skills update",
835
- sync_command: syncCommand,
836
- verify_command: `for d in ${WAZA_MANAGED_SKILLS.join(" ")}; do diff -qr ~/.agents/skills/$d ~/.codex/skills/$d; done; for f in ${rulesList}; do cmp -s ~/.agents/rules/$f ~/.codex/rules/$f; done`,
837
- upgrade_command: `npx -y skills update && ${syncCommand}`,
838
- impact: {
839
- complex_tasks: "unaffected",
840
- simple_tasks: status === "present" ? "full" : status === "partial" ? "degraded" : "missing",
841
- knowledge_tasks: "unaffected",
842
- },
843
- };
844
- }
845
-
846
- function detectRuntimeCapabilities(waza) {
847
- return {
848
- bun: commandCapability(
849
- "bun",
850
- "forge-owned global installs, local package dependency install, and test/runtime execution",
851
- "forge",
852
- true
853
- ),
854
- npm: commandCapability(
855
- "npm",
856
- "npm registry readbacks, publish gates, and opt-in update checks; not used for forge-owned global install repair",
857
- "npm-registry",
858
- false
859
- ),
860
- npx: commandCapability(
861
- "npx",
862
- "external Skills CLI bootstrap/update commands for Waza and Mermaid",
863
- "external-skills-cli",
864
- false
865
- ),
866
- skills_cli: {
867
- name: "skills_cli",
868
- status: waza.skills_cli_status === "available" ? "available" : waza.skills_cli_status,
869
- path: null,
870
- owner: "external-skills-cli",
871
- required: false,
872
- required_for: "Waza/Mermaid external skill bootstrap; forge reports this as an explicit exception boundary",
873
- command: "skills ls -g --json",
874
- },
875
- bash: commandCapability(
876
- "bash",
877
- "forge helper scripts, migration, setup checks, and contract verification wrappers",
878
- "forge",
879
- true
880
- ),
881
- rsync: commandCapability(
882
- "rsync",
883
- "Waza staging-to-Codex sync and installed-copy runtime mirroring",
884
- "platform-filesystem",
885
- false
886
- ),
887
- symlink: detectSymlinkCapability(),
888
- };
889
- }
890
-
891
- function inspectCodexAutomationSkill(skill) {
892
- const skillFile = path.join(HOSTS.codex.skillsDir, skill, "SKILL.md");
893
- const local = readSkillFile(skillFile);
894
-
895
- return {
896
- name: skill,
897
- path: skillFile,
898
- real_path: resolveRealPath(skillFile),
899
- present: local.exists,
900
- version: local.version,
901
- hash: local.hash,
902
- };
903
- }
904
-
905
- function detectCodexAutomationProfile() {
906
- const skills = CODEX_AUTOMATION_SKILLS.map((skill) => inspectCodexAutomationSkill(skill));
907
- const installedSkills = skills.filter((entry) => entry.present).map((entry) => entry.name);
908
- const missingSkills = skills.filter((entry) => !entry.present).map((entry) => entry.name);
909
- const status = missingSkills.length === 0 ? "present" : installedSkills.length > 0 ? "partial" : "missing";
910
-
911
- return {
912
- name: "codex_automation_profile",
913
- status,
914
- reason: status === "present"
915
- ? "Detected all required Codex automation skills from the Codex runtime path."
916
- : status === "partial"
917
- ? `Detected ${installedSkills.length}/${CODEX_AUTOMATION_SKILLS.length} required Codex automation skills; missing ${missingSkills.join(", ")}.`
918
- : "No required Codex automation skills were found in the Codex runtime path.",
919
- required_skills: CODEX_AUTOMATION_SKILLS,
920
- optional_skills: [],
921
- mode: "codex-runtime-reference",
922
- source: HOSTS.codex.skillsDir,
923
- routes: {
924
- workflow_health: "waza:health",
925
- review_gate: "waza:check",
926
- architecture_diagram: "mermaid",
927
- },
928
- vendoring_policy: "do-not-vendor-skill-body",
929
- installed_skills: installedSkills,
930
- missing_skills: missingSkills,
931
- skills,
932
- };
933
- }
934
-
935
- function detectGbrainMcp(host) {
936
- const meta = HOSTS[host];
937
- const content = readText(meta.configPath);
938
- if (!content) {
939
- return {
940
- status: "disabled",
941
- reason: `No ${meta.label} config found at ${meta.configPath}.`,
942
- };
943
- }
944
-
945
- if (host === "codex") {
946
- if (/\[mcp_servers\.(gbrain|gbrain_http)\]/.test(content)) {
947
- return {
948
- status: "configured",
949
- reason: "Codex config contains a gbrain MCP server entry.",
950
- };
951
- }
952
-
953
- return {
954
- status: "disabled",
955
- reason: "Codex config does not contain a gbrain MCP server entry.",
956
- };
957
- }
958
-
959
- if (/gbrain/i.test(content)) {
960
- return {
961
- status: "configured",
962
- reason: "Claude settings contain a gbrain reference.",
963
- };
964
- }
965
-
966
- return {
967
- status: "disabled",
968
- reason: "Claude settings do not contain a gbrain MCP configuration.",
969
- };
970
- }
971
-
972
- function isGbrainFastOnlyConnectionSkip(doctorJson) {
973
- if (!doctorJson || doctorJson.status !== "warnings") return false;
974
- if (!Array.isArray(doctorJson.checks)) return false;
975
- const warnings = doctorJson.checks.filter((entry) => entry?.status === "warn" || entry?.status === "warning");
976
- if (warnings.length !== 1) return false;
977
- const warning = warnings[0];
978
- const message = String(warning.message || "");
979
- return warning.name === "connection"
980
- && (/Skipping DB checks \((--fast mode|"--fast mode)/i.test(message) || /fast mode skipped DB checks/i.test(message));
981
- }
982
-
983
- function detectGbrain() {
984
- const gbrainBin = resolvePathCommand("gbrain");
985
- let versionResult = gbrainBin
986
- ? run(gbrainBin, ["--version"], { timeoutMs: 1000 })
987
- : { ok: false, stdout: "", timed_out: false };
988
- if (!versionResult.ok && versionResult.timed_out) {
989
- versionResult = run(gbrainBin, ["--version"], { timeoutMs: 1000 });
990
- }
991
- const present = versionResult.ok;
992
- const version = present ? versionResult.stdout.trim().replace(/^gbrain\s+/i, "") : null;
993
- let doctorCommand = ["doctor", "--json", "--fast"];
994
- let doctorResult = present ? run(gbrainBin, doctorCommand, { timeoutMs: 1500 }) : null;
995
- let doctorJson = doctorResult?.ok ? parseJson(doctorResult.stdout) : null;
996
- if (present && !doctorJson) {
997
- doctorCommand = ["doctor", "--json"];
998
- doctorResult = run(gbrainBin, doctorCommand, { timeoutMs: 1500 });
999
- doctorJson = doctorResult?.ok ? parseJson(doctorResult.stdout) : null;
1000
- }
1001
- const checkUpdateResult = present && checkUpdates ? run(gbrainBin, ["check-update", "--json"], { timeoutMs: 1500 }) : null;
1002
- const checkUpdateJson = checkUpdateResult?.ok ? parseJson(checkUpdateResult.stdout) : null;
1003
- const integrationsResult = present ? run(gbrainBin, ["integrations", "list", "--json"], { timeoutMs: 1500 }) : null;
1004
- const integrationsJson = integrationsResult?.ok ? parseJson(integrationsResult.stdout) : null;
1005
- const integrationsAvailable = integrationsJson
1006
- ? Object.values(integrationsJson).reduce((count, value) => count + (Array.isArray(value) ? value.length : 0), 0)
1007
- : 0;
1008
- const mcpHosts = {};
1009
-
1010
- for (const host of SELECTED_HOSTS) {
1011
- mcpHosts[host] = {
1012
- label: HOSTS[host].label,
1013
- ...detectGbrainMcp(host),
1014
- };
1015
- }
1016
-
1017
- const mcpConfigured = Object.values(mcpHosts).some((entry) => entry.status === "configured");
1018
- const acceptedFastWarning = doctorCommand.join(" ") === "doctor --json --fast" && isGbrainFastOnlyConnectionSkip(doctorJson);
1019
- const status = !present
1020
- ? "missing"
1021
- : (doctorJson?.status === "ok" || acceptedFastWarning ? "present" : doctorJson?.status === "warnings" ? "warning" : "warning");
1022
- const updateStatus = !checkUpdates
1023
- ? "not-checked"
1024
- : checkUpdateJson?.update_available
1025
- ? "update-available"
1026
- : checkUpdateJson
1027
- ? "up-to-date"
1028
- : "unknown";
1029
-
1030
- return {
1031
- name: "gbrain",
1032
- required: false,
1033
- status,
1034
- reason: !present
1035
- ? "gbrain CLI is not installed; install the official GitHub package, not npm registry package gbrain."
1036
- : acceptedFastWarning
1037
- ? "gbrain CLI is present; fast doctor only skipped DB checks."
1038
- : doctorJson
1039
- ? `gbrain CLI is present; doctor status is ${doctorJson.status}.`
1040
- : "gbrain CLI is present, but doctor output could not be parsed.",
1041
- cli_present: present,
1042
- version,
1043
- doctor_command: present ? `gbrain ${doctorCommand.join(" ")}` : null,
1044
- doctor: doctorJson,
1045
- update_status: updateStatus,
1046
- update_reason: checkUpdateJson?.error
1047
- ? `gbrain check-update returned ${checkUpdateJson.error}.`
1048
- : checkUpdateResult?.timed_out
1049
- ? "gbrain check-update timed out before update status could be determined."
1050
- : updateStatus === "update-available"
1051
- ? "gbrain check-update reports a newer version."
1052
- : updateStatus === "up-to-date"
1053
- ? "gbrain check-update did not find a newer version."
1054
- : "gbrain update status is unknown.",
1055
- integrations_available: integrationsAvailable,
1056
- mcp_hosts: mcpHosts,
1057
- install_command: GBRAIN_INSTALL_COMMAND,
1058
- install_note: GBRAIN_INSTALL_NOTE,
1059
- upgrade_command: checkUpdateJson?.upgrade_command || "gbrain upgrade",
1060
- sync_command: "gbrain sync --repo <path>",
1061
- impact: {
1062
- complex_tasks: "unaffected",
1063
- simple_tasks: "unaffected",
1064
- knowledge_tasks: !present
1065
- ? "missing"
1066
- : mcpConfigured
1067
- ? "full"
1068
- : "manual-only",
1069
- },
1070
- };
1071
- }
1072
-
1073
- function detectCodeGraphMcp(host) {
1074
- const meta = HOSTS[host];
1075
- const content = readText(meta.configPath);
1076
- function claudeEntryResult(entry, source) {
1077
- if (!entry || typeof entry !== "object") return null;
1078
- if (entry.alwaysLoad === true) {
1079
- return {
1080
- status: "configured",
1081
- always_load: true,
1082
- tool_search: "always-load",
1083
- reason: `${source} contains a codegraph MCP server entry with alwaysLoad=true.`,
1084
- };
1085
- }
1086
- return {
1087
- status: "deferred",
1088
- always_load: false,
1089
- tool_search: "deferred",
1090
- reason: `${source} contains a codegraph MCP server entry, but alwaysLoad is not true; Claude Code MCP Tool Search may defer CodeGraph tools.`,
1091
- };
1092
- }
1093
- function claudeTextFallback(source, sourcePath) {
1094
- return {
1095
- status: "deferred",
1096
- always_load: false,
1097
- tool_search: "unknown",
1098
- reason: `${source} contains a codegraph MCP server entry at ${sourcePath}, but alwaysLoad could not be verified.`,
1099
- };
1100
- }
1101
-
1102
- if (!content) {
1103
- if (host === "claude") {
1104
- const claudeRootConfig = path.join(HOME, ".claude.json");
1105
- const rootJson = readJson(claudeRootConfig);
1106
- const rootEntry = claudeEntryResult(rootJson?.mcpServers?.codegraph, `Claude root config at ${claudeRootConfig}`);
1107
- if (rootEntry) return rootEntry;
1108
- const rootContent = readText(claudeRootConfig);
1109
- if (/codegraph/i.test(rootContent)) {
1110
- return claudeTextFallback("Claude root config", claudeRootConfig);
1111
- }
1112
- }
1113
-
1114
- return {
1115
- status: "missing",
1116
- reason: `No ${meta.label} config found at ${meta.configPath}.`,
1117
- };
1118
- }
1119
-
1120
- if (host === "codex") {
1121
- if (/\[mcp_servers\.codegraph\]/.test(content)) {
1122
- return {
1123
- status: "configured",
1124
- reason: "Codex config contains a codegraph MCP server entry.",
1125
- };
1126
- }
1127
-
1128
- return {
1129
- status: "missing",
1130
- reason: "Codex config does not contain a codegraph MCP server entry.",
1131
- };
1132
- }
1133
-
1134
- const settingsJson = readJson(meta.configPath);
1135
- const settingsEntry = claudeEntryResult(settingsJson?.mcpServers?.codegraph, `Claude settings at ${meta.configPath}`);
1136
- if (settingsEntry) return settingsEntry;
1137
- if (/"mcpServers"\s*:\s*{[\s\S]*"codegraph"/i.test(content)) {
1138
- return claudeTextFallback("Claude settings", meta.configPath);
1139
- }
1140
-
1141
- const claudeRootConfig = path.join(HOME, ".claude.json");
1142
- const rootJson = readJson(claudeRootConfig);
1143
- const rootEntry = claudeEntryResult(rootJson?.mcpServers?.codegraph, `Claude root config at ${claudeRootConfig}`);
1144
- if (rootEntry) return rootEntry;
1145
- const rootContent = readText(claudeRootConfig);
1146
- if (/"mcpServers"\s*:\s*{[\s\S]*"codegraph"/i.test(rootContent)) {
1147
- return claudeTextFallback("Claude root config", claudeRootConfig);
1148
- }
1149
-
1150
- return {
1151
- status: "missing",
1152
- reason: "Claude config does not contain a codegraph MCP server entry.",
1153
- };
1154
- }
1155
-
1156
- function parseCodeGraphProjectStatus(output) {
1157
- if (/Not initialized/i.test(output)) return "not-initialized";
1158
- if (/Index is up to date/i.test(output)) return "up-to-date";
1159
- if (/Pending Changes/i.test(output) || /Run "codegraph sync/i.test(output)) return "stale";
1160
- if (/CodeGraph Status/i.test(output)) return "unknown";
1161
- return "unavailable";
1162
- }
1163
-
1164
- function resolvePathCommand(command) {
1165
- const pathValue = process.env.PATH || "";
1166
- for (const dir of pathValue.split(path.delimiter)) {
1167
- if (!dir) continue;
1168
- const candidate = path.join(dir, command);
1169
- if (fileIsExecutable(candidate)) return candidate;
1170
- }
1171
- return null;
1172
- }
1173
-
1174
- function codeGraphPackageDeclared() {
1175
- const pkg = readJson(path.join(REPO_ROOT, "package.json"));
1176
- if (!pkg || typeof pkg !== "object") return false;
1177
- return Boolean(
1178
- pkg.devDependencies?.[CODEGRAPH_PACKAGE] ||
1179
- pkg.dependencies?.[CODEGRAPH_PACKAGE] ||
1180
- pkg.optionalDependencies?.[CODEGRAPH_PACKAGE]
1181
- );
1182
- }
1183
-
1184
- function codeGraphPlatformPackageName() {
1185
- return `${CODEGRAPH_PACKAGE}-${process.platform}-${process.arch}`;
1186
- }
1187
-
1188
- function codeGraphPlatformBundleBin() {
1189
- if (process.platform === "win32") return null;
1190
- return path.join(REPO_ROOT, "node_modules", codeGraphPlatformPackageName(), "bin", "codegraph");
1191
- }
1192
-
1193
- function resolveCodeGraphBinary() {
1194
- const allowRepoLocal = process.env.AGENTIC_DEV_CODEGRAPH_ALLOW_REPO_LOCAL !== "0";
1195
- const allowGlobal = process.env.AGENTIC_DEV_CODEGRAPH_ALLOW_GLOBAL !== "0";
1196
- const localCandidates = [];
1197
- const localOverride = process.env.AGENTIC_DEV_CODEGRAPH_LOCAL_BIN;
1198
- const globalOverride = process.env.AGENTIC_DEV_CODEGRAPH_GLOBAL_BIN;
1199
-
1200
- if (localOverride) localCandidates.push(localOverride);
1201
- if (allowRepoLocal) {
1202
- localCandidates.push(codeGraphPlatformBundleBin());
1203
- localCandidates.push(path.join(REPO_ROOT, "node_modules", ".bin", "codegraph"));
1204
- }
1205
-
1206
- let localBinPath = null;
1207
- for (const candidate of localCandidates) {
1208
- if (candidate && fileIsExecutable(candidate)) {
1209
- localBinPath = candidate;
1210
- break;
1211
- }
1212
- }
1213
-
1214
- let globalBinPath = null;
1215
- if (allowGlobal) {
1216
- if (globalOverride && fileIsExecutable(globalOverride)) {
1217
- globalBinPath = globalOverride;
1218
- } else if (!globalOverride) {
1219
- globalBinPath = resolvePathCommand("codegraph");
1220
- }
1221
- }
1222
-
1223
- if (localBinPath) {
1224
- return {
1225
- source: "local",
1226
- bin_path: localBinPath,
1227
- local_bin_path: localBinPath,
1228
- global_bin_path: globalBinPath,
1229
- global_fallback_used: false,
1230
- };
1231
- }
1232
-
1233
- if (globalBinPath) {
1234
- return {
1235
- source: "global",
1236
- bin_path: globalBinPath,
1237
- local_bin_path: null,
1238
- global_bin_path: globalBinPath,
1239
- global_fallback_used: true,
1240
- };
1241
- }
1242
-
1243
- return {
1244
- source: "missing",
1245
- bin_path: null,
1246
- local_bin_path: null,
1247
- global_bin_path: null,
1248
- global_fallback_used: false,
1249
- };
1250
- }
1251
-
1252
- function codeGraphVersion(binPath) {
1253
- if (!binPath) return null;
1254
- const result = run(binPath, ["--version"], { timeoutMs: 1000 });
1255
- if (result.ok) return result.stdout.trim() || null;
1256
- if (result.timed_out) {
1257
- const retry = run(binPath, ["--version"], { timeoutMs: 1000 });
1258
- if (retry.ok) return retry.stdout.trim() || null;
1259
- }
1260
- return null;
1261
- }
1262
-
1263
- function detectCodeGraph() {
1264
- const resolution = resolveCodeGraphBinary();
1265
- const cliPresent = Boolean(resolution.bin_path);
1266
- const version = codeGraphVersion(resolution.bin_path);
1267
- const globalVersion = resolution.global_bin_path && resolution.global_bin_path !== resolution.bin_path
1268
- ? codeGraphVersion(resolution.global_bin_path)
1269
- : resolution.source === "global"
1270
- ? version
1271
- : null;
1272
- const localVersion = resolution.source === "local" ? version : null;
1273
- const packageDeclared = codeGraphPackageDeclared();
1274
- const mcpHosts = {};
1275
-
1276
- for (const host of SELECTED_HOSTS) {
1277
- mcpHosts[host] = {
1278
- label: HOSTS[host].label,
1279
- ...detectCodeGraphMcp(host),
1280
- };
1281
- }
1282
-
1283
- const selectedMcpConfigured = SELECTED_HOSTS.every((host) => mcpHosts[host]?.status === "configured");
1284
- const statusResult = cliPresent ? run(resolution.bin_path, ["status", "."], { timeoutMs: 1500 }) : null;
1285
- const statusOutput = `${statusResult?.stdout || ""}\n${statusResult?.stderr || ""}`;
1286
- const projectIndexStatus = cliPresent ? parseCodeGraphProjectStatus(statusOutput) : "unavailable";
1287
- const indexInitialized = fs.existsSync(path.join(REPO_ROOT, ".codegraph"))
1288
- || ["up-to-date", "stale", "unknown"].includes(projectIndexStatus);
1289
- const updateResult = cliPresent && checkUpdates
1290
- ? run("npm", ["view", CODEGRAPH_PACKAGE, "version", "--json"], { timeoutMs: 3000 })
1291
- : null;
1292
- const latestVersion = updateResult?.ok ? (parseJson(updateResult.stdout) || updateResult.stdout.trim().replace(/^"|"$/g, "")) : null;
1293
- const updateStatus = !checkUpdates
1294
- ? "not-checked"
1295
- : latestVersion && version
1296
- ? (String(latestVersion) === String(version) ? "up-to-date" : "update-available")
1297
- : "unknown";
1298
- const localDependencyMissing = packageDeclared && resolution.source === "global";
1299
- const status = !cliPresent
1300
- ? "missing"
1301
- : localDependencyMissing || !selectedMcpConfigured || projectIndexStatus === "not-initialized" || projectIndexStatus === "unavailable"
1302
- ? "partial"
1303
- : projectIndexStatus === "stale" || projectIndexStatus === "unknown"
1304
- ? "warning"
1305
- : "present";
1306
-
1307
- return {
1308
- name: "codegraph",
1309
- status,
1310
- reason: !cliPresent
1311
- ? "CodeGraph CLI is not installed."
1312
- : localDependencyMissing
1313
- ? "CodeGraph global fallback is present, but this repo declares a local dev dependency that is not installed."
1314
- : !selectedMcpConfigured
1315
- ? "CodeGraph CLI is present, but one or more selected host MCP configs are missing or deferred."
1316
- : projectIndexStatus === "not-initialized"
1317
- ? "CodeGraph CLI and MCP are present, but this repo has not been indexed."
1318
- : projectIndexStatus === "unavailable"
1319
- ? "CodeGraph CLI and MCP are present, but project index status could not be read."
1320
- : projectIndexStatus === "stale"
1321
- ? "CodeGraph is configured, but this repo index has pending changes."
1322
- : projectIndexStatus === "unknown"
1323
- ? "CodeGraph is configured, but this repo index status is unknown."
1324
- : "CodeGraph CLI, selected host MCP config, and project index are ready.",
1325
- package: CODEGRAPH_PACKAGE,
1326
- primary_host: "codex",
1327
- cli_present: cliPresent,
1328
- source: resolution.source,
1329
- bin_path: resolution.bin_path,
1330
- local_bin_path: resolution.local_bin_path,
1331
- global_bin_path: resolution.global_bin_path,
1332
- global_fallback_used: resolution.global_fallback_used,
1333
- version,
1334
- local_version: localVersion,
1335
- global_version: globalVersion,
1336
- dependency_declared: packageDeclared,
1337
- drift: localVersion && globalVersion && localVersion !== globalVersion
1338
- ? { local: localVersion, global: globalVersion, using: resolution.source }
1339
- : null,
1340
- latest_version: latestVersion,
1341
- update_status: updateStatus,
1342
- update_reason: !checkUpdates
1343
- ? "Update checks were skipped."
1344
- : updateResult?.timed_out
1345
- ? "CodeGraph npm version check timed out."
1346
- : latestVersion && version
1347
- ? (String(latestVersion) === String(version) ? "Local CodeGraph matches npm latest." : "npm reports a newer CodeGraph version.")
1348
- : "CodeGraph update status is unknown.",
1349
- mcp_hosts: mcpHosts,
1350
- project_index: {
1351
- status: projectIndexStatus,
1352
- initialized: indexInitialized,
1353
- path: path.join(REPO_ROOT, ".codegraph"),
1354
- command: "codegraph status .",
1355
- },
1356
- install_command: packageDeclared ? CODEGRAPH_LOCAL_INSTALL_COMMAND : CODEGRAPH_GLOBAL_INSTALL_COMMAND,
1357
- ensure_command: packageDeclared ? CODEGRAPH_ENSURE_BASH_COMMAND : null,
1358
- mcp_install_command: CODEGRAPH_MCP_CONFIGURE_COMMAND,
1359
- init_command: packageDeclared && CODEGRAPH_ENSURE_BASH_COMMAND ? `${CODEGRAPH_ENSURE_BASH_COMMAND} --init` : "codegraph init -i .",
1360
- sync_command: packageDeclared && CODEGRAPH_ENSURE_BASH_COMMAND ? `${CODEGRAPH_ENSURE_BASH_COMMAND} --sync` : "codegraph sync .",
1361
- upgrade_command: packageDeclared && CODEGRAPH_ENSURE_BASH_COMMAND ? `bun update @colbymchenry/codegraph && ${CODEGRAPH_ENSURE_BASH_COMMAND} --sync` : `bun add -g ${CODEGRAPH_PACKAGE}@latest && codegraph sync .`,
1362
- uninstall_command: "codegraph uninstall --target codex --location global --yes",
1363
- readiness: {
1364
- required_for: "codex-agent-code-navigation",
1365
- hook_policy: "do-not-block-hooks",
1366
- user_setup: "one-terminal-command-or-authorized-agent-action",
1367
- },
1368
- impact: {
1369
- code_navigation: status === "present" ? "full" : status === "warning" ? "stale-index" : "missing",
1370
- hook_correctness: "unaffected",
1371
- },
1372
- };
1373
- }
1374
-
1375
- const wazaReport = detectWaza();
1376
- const report = {
1377
- generated_at: new Date().toISOString(),
1378
- repo_root: REPO_ROOT,
1379
- hosts: SELECTED_HOSTS,
1380
- check_updates: checkUpdates,
1381
- runtime_capabilities: detectRuntimeCapabilities(wazaReport),
1382
- tools: {
1383
- gstack: detectGstack(),
1384
- waza: wazaReport,
1385
- codex_automation_profile: detectCodexAutomationProfile(),
1386
- gbrain: detectGbrain(),
1387
- codegraph: detectCodeGraph(),
1388
- },
1389
- };
1390
-
1391
- const strictFailures = [];
1392
- if (strictReadiness && ["missing", "partial"].includes(report.tools.codegraph.status)) {
1393
- strictFailures.push(`CodeGraph readiness is ${report.tools.codegraph.status}: ${report.tools.codegraph.reason}`);
1394
- }
1395
-
1396
- function printText(result) {
1397
- console.log("External Tooling Report");
1398
- console.log(`Hosts: ${result.hosts.join(", ")}`);
1399
- console.log("");
1400
-
1401
- console.log("Runtime capabilities");
1402
- for (const capability of Object.values(result.runtime_capabilities || {})) {
1403
- const required = capability.required ? "required" : "optional";
1404
- const pathBits = capability.path ? ` at ${capability.path}` : "";
1405
- console.log(` - ${capability.name}: ${capability.status} (${required})${pathBits}`);
1406
- console.log(` owner=${capability.owner}; required_for=${capability.required_for}`);
1407
- }
1408
- console.log("");
1409
-
1410
- const gstack = result.tools.gstack;
1411
- console.log(`gstack [${gstack.status}]`);
1412
- for (const host of SELECTED_HOSTS) {
1413
- const entry = gstack.hosts[host];
1414
- const versionBits = entry.version ? ` v${entry.version}` : "";
1415
- const updateBits = entry.update_status && entry.update_status !== "not-checked" ? `, ${entry.update_status}` : "";
1416
- console.log(` - ${entry.label}: ${entry.present ? "present" : "missing"}${versionBits}${updateBits}`);
1417
- }
1418
- console.log(` - Team mode: ${gstack.repo_team_mode.status} (${gstack.repo_team_mode.reason})`);
1419
- console.log(` - Impact: complex=${gstack.impact.complex_tasks}`);
1420
- console.log(` - Install: ${gstack.install_command}`);
1421
- console.log(` - Upgrade: ${gstack.upgrade_command}`);
1422
- console.log("");
1423
-
1424
- const waza = result.tools.waza;
1425
- console.log(`Waza [${waza.status}]`);
1426
- console.log(` - Source lock: ${waza.source_lock_file || "not found"}`);
1427
- console.log(` - Primary: ${waza.primary_host} (${waza.codex_primary_path})`);
1428
- console.log(` - Staging: ${waza.staging_cache_path}`);
1429
- console.log(` - Skills CLI: ${waza.skills_cli_status}`);
1430
- for (const host of SELECTED_HOSTS) {
1431
- const entry = waza.hosts[host];
1432
- const versionBits = Object.entries(entry.versions)
1433
- .map(([name, version]) => `${name}@${version || "unknown"}`)
1434
- .join(", ");
1435
- console.log(` - ${entry.label}: ${entry.status}, ${entry.installed_skills.length}/${waza.managed_skills.length} skills, sync=${entry.staging_sync}, stale=${entry.stale_status}`);
1436
- if (versionBits) {
1437
- console.log(` versions: ${versionBits}`);
1438
- }
1439
- console.log(` shared rules: ${entry.shared_rules.length}/${waza.shared_rules.length}, sync=${entry.shared_rules_staging_sync}, stale=${entry.shared_rules_stale_status}`);
1440
- if (entry.missing_skills.length) {
1441
- console.log(` missing: ${entry.missing_skills.join(", ")}`);
1442
- }
1443
- if (entry.drift_skills.length) {
1444
- console.log(` drift: ${entry.drift_skills.join(", ")}`);
1445
- }
1446
- if (entry.stale_skills.length) {
1447
- console.log(` stale: ${entry.stale_skills.join(", ")}`);
1448
- }
1449
- if (entry.missing_shared_rules.length) {
1450
- console.log(` missing shared rules: ${entry.missing_shared_rules.join(", ")}`);
1451
- }
1452
- if (entry.drift_shared_rules.length) {
1453
- console.log(` drift shared rules: ${entry.drift_shared_rules.join(", ")}`);
1454
- }
1455
- if (entry.stale_shared_rules.length) {
1456
- console.log(` stale shared rules: ${entry.stale_shared_rules.join(", ")}`);
1457
- }
1458
- }
1459
- console.log(` - Updates: ${waza.update_status} (${waza.update_reason})`);
1460
- console.log(` - Impact: simple=${waza.impact.simple_tasks}`);
1461
- console.log(` - Install: ${waza.install_command}`);
1462
- console.log(` - Stage: ${waza.stage_command}`);
1463
- console.log(` - Sync Codex: ${waza.sync_command}`);
1464
- console.log(` - Verify: ${waza.verify_command}`);
1465
- console.log("");
1466
-
1467
- const codexAutomation = result.tools.codex_automation_profile;
1468
- console.log(`Codex automation profile [${codexAutomation.status}]`);
1469
- console.log(` - Required: ${codexAutomation.required_skills.join(", ")}`);
1470
- console.log(` - Source: ${codexAutomation.source}`);
1471
- console.log(` - Mode: ${codexAutomation.mode}`);
1472
- if (codexAutomation.missing_skills.length) {
1473
- console.log(` - Missing: ${codexAutomation.missing_skills.join(", ")}`);
1474
- }
1475
- console.log(` - Routes: health=${codexAutomation.routes.workflow_health}, check=${codexAutomation.routes.review_gate}, diagram=${codexAutomation.routes.architecture_diagram}`);
1476
- console.log(` - Vendoring: ${codexAutomation.vendoring_policy}`);
1477
- console.log("");
1478
-
1479
- const gbrain = result.tools.gbrain;
1480
- console.log(`gbrain [${gbrain.status}]`);
1481
- console.log(` - CLI: ${gbrain.cli_present ? `present${gbrain.version ? ` (v${gbrain.version})` : ""}` : "missing"}`);
1482
- if (gbrain.doctor?.status) {
1483
- console.log(` - Doctor: ${gbrain.doctor.status} (score ${gbrain.doctor.health_score ?? "n/a"})`);
1484
- }
1485
- for (const host of SELECTED_HOSTS) {
1486
- const entry = gbrain.mcp_hosts[host];
1487
- console.log(` - ${entry.label} MCP: ${entry.status}`);
1488
- }
1489
- if (gbrain.integrations_available) {
1490
- console.log(` - Integrations available: ${gbrain.integrations_available}`);
1491
- }
1492
- console.log(` - Updates: ${gbrain.update_status} (${gbrain.update_reason})`);
1493
- console.log(` - Impact: knowledge=${gbrain.impact.knowledge_tasks}`);
1494
- console.log(` - Install: ${gbrain.install_command}`);
1495
- console.log(` - Upgrade: ${gbrain.upgrade_command}`);
1496
- console.log(` - Manual sync: ${gbrain.sync_command}`);
1497
- console.log("");
1498
-
1499
- const codegraph = result.tools.codegraph;
1500
- console.log(`CodeGraph [${codegraph.status}]`);
1501
- console.log(` - CLI: ${codegraph.cli_present ? `present${codegraph.version ? ` (v${codegraph.version})` : ""} via ${codegraph.source}` : "missing"}`);
1502
- if (codegraph.drift) {
1503
- console.log(` - Drift: local=${codegraph.drift.local}, global=${codegraph.drift.global}, using=${codegraph.drift.using}`);
1504
- }
1505
- for (const host of SELECTED_HOSTS) {
1506
- const entry = codegraph.mcp_hosts[host];
1507
- const suffix = entry.tool_search ? ` (${entry.tool_search})` : "";
1508
- console.log(` - ${entry.label} MCP: ${entry.status}${suffix}`);
1509
- }
1510
- console.log(` - Project index: ${codegraph.project_index.status}`);
1511
- console.log(` - Updates: ${codegraph.update_status} (${codegraph.update_reason})`);
1512
- console.log(` - Impact: code-navigation=${codegraph.impact.code_navigation}, hooks=${codegraph.impact.hook_correctness}`);
1513
- console.log(` - Install deps: ${codegraph.install_command}`);
1514
- if (codegraph.ensure_command) {
1515
- console.log(` - Ensure: ${codegraph.ensure_command}`);
1516
- }
1517
- console.log(` - Init index: ${codegraph.init_command}`);
1518
- console.log(` - Sync index: ${codegraph.sync_command}`);
1519
- }
1520
-
1521
- if (jsonOutput) {
1522
- console.log(JSON.stringify(report, null, 2));
1523
- } else {
1524
- printText(report);
1525
- }
1526
-
1527
- if (strictFailures.length > 0) {
1528
- for (const failure of strictFailures) {
1529
- console.error(`[readiness] ${failure}`);
1530
- }
1531
- process.exit(2);
1532
- }
1533
- NODE_EOF
3
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ exec "$SCRIPT_DIR/lib/run-forge-helper.sh" "check-agent-tooling.sh" "$@"