@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.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +2 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +17 -0
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- package/docs/architecture/CURRENT.md +12 -4
- package/docs/forge-plugin-management.md +1 -1
- package/docs/operations/controller-performance-and-502.md +19 -0
- package/docs/operations/homebrew.md +2 -2
- package/package.json +5 -2
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- package/scripts/benchmark-work-efficiency.ts +0 -0
- package/scripts/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1531
- package/scripts/check-architecture-sync.sh +2 -302
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-runtime-architecture.mjs +243 -16
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +0 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -702
- package/scripts/ensure-task-workflow.sh +1 -1192
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/lib/project-init-lib.sh +7 -2
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -33
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -771
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +3 -0
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -531
- package/scripts/sprint-backlog.sh +1 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -593
- package/scripts/verify-sprint.sh +1 -500
- package/scripts/workflow-contract.ts +4 -201
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/plugin.ts +12 -3
- package/src/cli/controller/check-runner.ts +7 -2
- package/src/cli/controller/context-pack.ts +46 -21
- package/src/cli/github/contracts.ts +16 -0
- package/src/cli/github/github.ts +2 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/local-bridge/facade-api.ts +37 -47
- package/src/cli/local-bridge/server.ts +2 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -33
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +85 -89
- package/src/cli/mcp/server.ts +57 -13
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset.ts +2 -2
- package/src/cli/mcp/transports/http.ts +23 -0
- package/src/cli/repositories/command-classifier.ts +2 -2
- package/src/cli/repositories/command-executor.ts +24 -6
- package/src/cli/repositories/repository-snapshot.ts +5 -2
- package/src/cli/repositories/structured-git.ts +57 -3
- package/src/cli/repository/inspector.ts +11 -3
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +9 -61
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +18 -25
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
- package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
- package/src/runtime/control-plane/facade/index.ts +2 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- package/src/runtime/control-plane/facade/types.ts +68 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
- package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +18 -6
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- package/src/runtime/execution/process-runtime/check-result.ts +53 -18
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
- package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
- package/src/runtime/execution/process-runtime/runtime.ts +10 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -12
- package/src/runtime/execution/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -13
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/router.ts +2 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +805 -1009
- package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
- package/src/runtime/plugins/browser-adapter.ts +256 -1083
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +18 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +10 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
- package/src/runtime/plugins/external-adapter.ts +92 -58
- package/src/runtime/plugins/macos-capability-broker.ts +0 -8
- package/src/runtime/plugins/store.ts +66 -9
- package/src/runtime/plugins/types.ts +3 -1
- package/src/runtime/recovery/maintenance-executor.ts +61 -25
- package/src/runtime/root/package-connector-service.ts +46 -4
- package/src/runtime/root/package-runtime-release.ts +1 -1
- package/src/runtime/root/package-runtime-service.ts +33 -2
- package/src/runtime/root/release-manifest.ts +137 -16
- package/src/runtime/root/release-materialize.ts +109 -35
- package/src/runtime/root/types.ts +10 -0
- package/src/runtime/standalone-recovery/core.ts +425 -194
- package/src/runtime/standalone-recovery/entry.ts +1 -1
- package/src/runtime/standalone-recovery/installer.ts +1 -1
- package/src/runtime/workflow/schedules/engine.ts +113 -87
- package/src/runtime/workflow/schedules/settlement.ts +26 -23
- package/src/runtime/workflow/schedules/store.ts +79 -10
|
@@ -1,1533 +1,4 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
|
|
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" "$@"
|