agentweaver 0.1.12 → 0.1.14

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 (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -18
  3. package/dist/doctor/checks/agentweaver-home.js +57 -0
  4. package/dist/doctor/checks/category.js +9 -0
  5. package/dist/doctor/checks/cwd-context.js +69 -0
  6. package/dist/doctor/checks/env-diagnostics.js +171 -0
  7. package/dist/doctor/checks/executors.js +106 -0
  8. package/dist/doctor/checks/flow-readiness.js +305 -0
  9. package/dist/doctor/checks/node-version.js +79 -0
  10. package/dist/doctor/checks/register.js +18 -0
  11. package/dist/doctor/checks/system.js +91 -0
  12. package/dist/doctor/index.js +4 -0
  13. package/dist/doctor/orchestrator.js +78 -0
  14. package/dist/doctor/registry.js +50 -0
  15. package/dist/doctor/runner.js +89 -0
  16. package/dist/doctor/types.js +12 -0
  17. package/dist/index.js +23 -27
  18. package/dist/interactive-ui.js +25 -25
  19. package/dist/pipeline/declarative-flows.js +1 -0
  20. package/dist/pipeline/flow-specs/auto-common.json +1 -0
  21. package/dist/pipeline/flow-specs/auto-golang.json +2 -1
  22. package/dist/pipeline/flow-specs/bugz/bug-analyze.json +1 -0
  23. package/dist/pipeline/flow-specs/bugz/bug-fix.json +1 -0
  24. package/dist/pipeline/flow-specs/git-commit.json +1 -0
  25. package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +3 -2
  26. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +3 -2
  27. package/dist/pipeline/flow-specs/gitlab/mr-description.json +1 -0
  28. package/dist/pipeline/flow-specs/go/run-go-linter-loop.json +3 -2
  29. package/dist/pipeline/flow-specs/go/run-go-tests-loop.json +3 -2
  30. package/dist/pipeline/flow-specs/implement.json +1 -0
  31. package/dist/pipeline/flow-specs/plan.json +2 -1
  32. package/dist/pipeline/flow-specs/review/review-fix.json +1 -0
  33. package/dist/pipeline/flow-specs/review/review-loop.json +1 -0
  34. package/dist/pipeline/flow-specs/review/review-project.json +1 -0
  35. package/dist/pipeline/flow-specs/review/review.json +2 -1
  36. package/dist/pipeline/flow-specs/task-describe.json +7 -6
  37. package/dist/pipeline/nodes/git-commit-node.js +1 -1
  38. package/dist/pipeline/nodes/git-status-node.js +1 -1
  39. package/dist/pipeline/nodes/review-findings-form-node.js +8 -8
  40. package/dist/pipeline/nodes/user-input-node.js +2 -2
  41. package/dist/runtime/process-runner.js +2 -2
  42. package/dist/scope.js +2 -2
  43. package/package.json +6 -3
  44. package/dist/pipeline/flow-model-settings.js +0 -77
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 seko99
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -69,24 +69,25 @@ This keeps workflow design in JSON while keeping implementation details in typed
69
69
 
70
70
  User-invokable built-in commands currently map to these flow specs:
71
71
 
72
- - `plan`
73
- - `task-describe`
74
- - `implement`
75
- - `review`
76
- - `review-fix`
77
- - `review-loop`
78
- - `bug-analyze`
79
- - `bug-fix`
80
- - `git-commit`
81
- - `gitlab-diff-review`
82
- - `gitlab-review`
83
- - `mr-description`
84
- - `run-go-tests-loop`
85
- - `run-go-linter-loop`
86
- - `auto-golang`
87
- - `auto-common`
88
-
89
- There are also built-in nested/helper flows that are loaded declaratively but are not direct top-level CLI commands, for example `review-project`.
72
+ - `plan` — fetches Jira task with attachments, generates clarifying questions for the developer, collects answers, and produces design, implementation plan, and QA plan as structured JSON and markdown artifacts
73
+ - `task-describe` — generates a brief task description from a Jira issue or from manual input; when Jira is provided, fetches the issue and summarizes it; otherwise accepts free-form text and analyzes the codebase to produce a richer description
74
+ - `implement` — runs LLM-backed implementation based on previously approved design and plan artifacts; executes code changes locally in the project working directory
75
+ - `review` — performs code review of current changes against the task design and plan; produces structured review findings with severity levels and a ready-to-merge verdict
76
+ - `review-fix` — takes review findings, auto-selects blockers and criticals (or lets the developer pick manually), builds a targeted fix prompt, and applies fixes locally; runs mandatory checks after modifications
77
+ - `review-loop` — iteratively runs review → review-fix cycles up to 5 times; stops early when ready-to-merge is achieved; each iteration auto-selects blockers and critical findings for fixing
78
+ - `bug-analyze` — fetches a Bug-type Jira issue, validates the issue type, generates or reuses a cached task summary, and produces structured bug analysis: root cause hypothesis, fix design, and step-by-step fix plan
79
+ - `bug-fix` — applies the fix designed in bug-analyze; uses the root cause hypothesis, fix design, and fix plan artifacts as the source of truth to implement code changes locally
80
+ - `git-commit` — four-phase commit workflow: collects git status and diff, generates a commit message via LLM, presents a file selection form, then shows the editable message for confirmation and executes the commit
81
+ - `gitlab-diff-review` — prompts for a GitLab merge request URL, fetches the MR diff via GitLab API, and runs LLM-backed code review producing structured findings with severity levels and a ready-to-merge verdict
82
+ - `gitlab-review` — prompts for a GitLab merge request URL, fetches existing code review comments via GitLab API, assesses which findings are fair and which can be dismissed, then runs review-fix to apply fixes for the accepted findings
83
+ - `mr-description` — generates a concise merge request description based on the task context and current code changes; produces both markdown and structured JSON artifacts
84
+ - `run-go-tests-loop` — runs `run_go_tests.py` and analyzes failures; if tests fail, sends the error output to LLM for a fix and retries; repeats up to 5 attempts, stopping early on success
85
+ - `run-go-linter-loop` — runs `run_go_linter.py` and analyzes output; if the linter reports issues, sends them to LLM for a fix and retries; repeats up to 5 attempts, stopping early on success
86
+ - `auto-golang` — end-to-end resumable pipeline for Go projects: plan → implement → linter loop → test loop → review loop → final linter loop → final test loop; supports `--from` to restart from a specific phase and `auto-status`/`auto-reset` for state management
87
+ - `auto-common` — end-to-end resumable pipeline without language-specific checks: plan → implement → review loop; simplified alternative to auto-golang for projects that do not need Go linter/test loops
88
+ - `doctor` — diagnostics command that runs system, executor, and flow readiness health checks; supports filtering by category or check ID and JSON output
89
+
90
+ There are also built-in nested/helper flows that are loaded declaratively but are not direct top-level CLI commands, for example `review-project` (project-level code review used internally when no prior design/plan artifacts are present).
90
91
 
91
92
  ## Requirements
92
93
 
@@ -215,6 +216,9 @@ agentweaver run-go-tests-loop DEMO-1234
215
216
  agentweaver run-go-linter-loop DEMO-1234
216
217
  agentweaver auto-golang DEMO-1234
217
218
  agentweaver auto-common DEMO-1234
219
+ agentweaver doctor
220
+ agentweaver doctor --json
221
+ agentweaver doctor <category>|<check-id>
218
222
  ```
219
223
 
220
224
  From a source checkout:
@@ -237,6 +241,8 @@ agentweaver auto-common --help-phases
237
241
  agentweaver auto-golang --from <phase> DEMO-1234
238
242
  agentweaver auto-status DEMO-1234
239
243
  agentweaver auto-reset DEMO-1234
244
+ agentweaver doctor
245
+ agentweaver doctor --json
240
246
  ```
241
247
 
242
248
  Notes:
@@ -0,0 +1,57 @@
1
+ import { accessSync, constants, existsSync } from "node:fs";
2
+ import { DoctorStatus } from "../types.js";
3
+ import { CATEGORY } from "./category.js";
4
+ import { agentweaverHome } from "../../runtime/agentweaver-home.js";
5
+ const PACKAGE_ROOT = process.cwd();
6
+ export const agentweaverHomeCheck = {
7
+ id: "agentweaver-home-01",
8
+ category: CATEGORY.AGENTWEAVER_HOME,
9
+ title: "agentweaver-home",
10
+ dependencies: [],
11
+ execute: async () => {
12
+ const homePath = agentweaverHome(PACKAGE_ROOT);
13
+ if (!existsSync(homePath)) {
14
+ return {
15
+ id: "agentweaver-home-01",
16
+ status: DoctorStatus.Fail,
17
+ title: "agentweaver-home",
18
+ message: `Directory does not exist: ${homePath}`,
19
+ hint: "Create the ~/.agentweaver directory or set AGENTWEAVER_HOME environment variable",
20
+ details: `resolved path: ${homePath}`,
21
+ };
22
+ }
23
+ try {
24
+ accessSync(homePath, constants.R_OK | constants.W_OK);
25
+ return {
26
+ id: "agentweaver-home-01",
27
+ status: DoctorStatus.Ok,
28
+ title: "agentweaver-home",
29
+ message: `Directory accessible and writable: ${homePath}`,
30
+ details: `resolved path: ${homePath}`,
31
+ };
32
+ }
33
+ catch {
34
+ try {
35
+ accessSync(homePath, constants.R_OK);
36
+ return {
37
+ id: "agentweaver-home-01",
38
+ status: DoctorStatus.Warn,
39
+ title: "agentweaver-home",
40
+ message: `Directory exists but is not writable: ${homePath}`,
41
+ hint: "Check directory permissions; AgentWeaver needs write access to this directory",
42
+ details: `resolved path: ${homePath}`,
43
+ };
44
+ }
45
+ catch {
46
+ return {
47
+ id: "agentweaver-home-01",
48
+ status: DoctorStatus.Fail,
49
+ title: "agentweaver-home",
50
+ message: `Directory exists but is not readable: ${homePath}`,
51
+ hint: "Check directory permissions",
52
+ details: `resolved path: ${homePath}`,
53
+ };
54
+ }
55
+ }
56
+ },
57
+ };
@@ -0,0 +1,9 @@
1
+ export const CATEGORY = {
2
+ SYSTEM: "system",
3
+ NODE_VERSION: "node-version",
4
+ AGENTWEAVER_HOME: "agentweaver-home",
5
+ CWD_CONTEXT: "cwd-context",
6
+ EXECUTORS: "executors",
7
+ ENV_DIAGNOSTICS: "env-diagnostics",
8
+ FLOW_READINESS: "flow-readiness",
9
+ };
@@ -0,0 +1,69 @@
1
+ import { accessSync, constants, existsSync } from "node:fs";
2
+ import { DoctorStatus } from "../types.js";
3
+ import { CATEGORY } from "./category.js";
4
+ const SOFT_CHECK_ID = "cwd-context-01";
5
+ export const cwdContextCheck = {
6
+ id: "cwd-context-01",
7
+ category: CATEGORY.CWD_CONTEXT,
8
+ title: "cwd-context",
9
+ dependencies: [],
10
+ execute: async () => {
11
+ const cwd = process.cwd();
12
+ let permissionStatus;
13
+ let permissionMessage;
14
+ let permissionHint;
15
+ try {
16
+ accessSync(cwd, constants.R_OK | constants.W_OK);
17
+ permissionStatus = DoctorStatus.Ok;
18
+ permissionMessage = "Working directory is readable and writable";
19
+ permissionHint = undefined;
20
+ }
21
+ catch {
22
+ try {
23
+ accessSync(cwd, constants.R_OK);
24
+ permissionStatus = DoctorStatus.Warn;
25
+ permissionMessage = "Working directory is readable but not writable";
26
+ permissionHint = "Some operations may fail if write access is required";
27
+ }
28
+ catch {
29
+ permissionStatus = DoctorStatus.Fail;
30
+ permissionMessage = "Working directory is not readable";
31
+ permissionHint = "Check directory permissions";
32
+ }
33
+ }
34
+ const gitRepoExists = existsSync(".git");
35
+ const gitStatusMessage = gitRepoExists ? "git repository detected" : "not a git repository";
36
+ const gitStatus = gitRepoExists ? DoctorStatus.Ok : DoctorStatus.Warn;
37
+ if (permissionStatus === DoctorStatus.Ok && gitStatus === DoctorStatus.Ok) {
38
+ return {
39
+ id: "cwd-context-01",
40
+ status: DoctorStatus.Ok,
41
+ title: "cwd-context",
42
+ message: `${permissionMessage}; ${gitStatusMessage}`,
43
+ details: `cwd: ${cwd}, git: ${gitStatusMessage}`,
44
+ };
45
+ }
46
+ if (permissionStatus === DoctorStatus.Ok && gitStatus === DoctorStatus.Warn) {
47
+ return {
48
+ id: "cwd-context-01",
49
+ status: DoctorStatus.Warn,
50
+ title: "cwd-context",
51
+ message: `${permissionMessage}; ${gitStatusMessage} (soft warning)`,
52
+ hint: "Git repository is recommended but not required",
53
+ details: `cwd: ${cwd}, git: ${gitStatusMessage}`,
54
+ };
55
+ }
56
+ const result = {
57
+ id: "cwd-context-01",
58
+ status: permissionStatus,
59
+ title: "cwd-context",
60
+ message: permissionMessage,
61
+ details: `cwd: ${cwd}, git: ${gitStatusMessage}`,
62
+ };
63
+ if (permissionHint) {
64
+ result.hint = permissionHint;
65
+ }
66
+ return result;
67
+ },
68
+ };
69
+ export const SOFT_CHECK_IDS = [SOFT_CHECK_ID];
@@ -0,0 +1,171 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { DoctorStatus } from "../types.js";
5
+ import { CATEGORY } from "./category.js";
6
+ const MONITORED_KEYS = [
7
+ "JIRA_API_KEY",
8
+ "JIRA_USERNAME",
9
+ "JIRA_AUTH_MODE",
10
+ "JIRA_BASE_URL",
11
+ "GITLAB_TOKEN",
12
+ "AGENTWEAVER_HOME",
13
+ "CODEX_BIN",
14
+ "CODEX_MODEL",
15
+ "OPENCODE_BIN",
16
+ "OPENCODE_MODEL",
17
+ ];
18
+ const SECRET_KEYS = new Set(["JIRA_API_KEY", "GITLAB_TOKEN"]);
19
+ const JIRA_AUTH_MODE_ALLOWED_VALUES = ["auto", "basic", "bearer"];
20
+ function maskSecret(value) {
21
+ if (value.length <= 6) {
22
+ if (value.length <= 3) {
23
+ return "***";
24
+ }
25
+ const start = value.slice(0, Math.ceil(value.length / 2));
26
+ const end = value.slice(Math.ceil(value.length / 2));
27
+ return `${start}***${end}`;
28
+ }
29
+ const start = value.slice(0, 3);
30
+ const end = value.slice(-3);
31
+ return `${start}***${end}`;
32
+ }
33
+ function globalConfigDir() {
34
+ return path.join(os.homedir(), ".agentweaver");
35
+ }
36
+ function parseEnvFileRaw(envFilePath) {
37
+ const result = {};
38
+ if (!existsSync(envFilePath)) {
39
+ return result;
40
+ }
41
+ const lines = readFileSync(envFilePath, "utf8").split(/\r?\n/);
42
+ for (const rawLine of lines) {
43
+ let line = rawLine.trim();
44
+ if (!line || line.startsWith("#")) {
45
+ continue;
46
+ }
47
+ if (line.startsWith("export ")) {
48
+ line = line.slice(7).trim();
49
+ }
50
+ const separatorIndex = line.indexOf("=");
51
+ if (separatorIndex < 0) {
52
+ continue;
53
+ }
54
+ const key = line.slice(0, separatorIndex).trim();
55
+ if (!key) {
56
+ continue;
57
+ }
58
+ let value = line.slice(separatorIndex + 1).trim();
59
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
60
+ value = value.slice(1, -1);
61
+ }
62
+ result[key] = value;
63
+ }
64
+ return result;
65
+ }
66
+ function getProjectEnvPath() {
67
+ return path.join(process.cwd(), ".agentweaver", ".env");
68
+ }
69
+ function getGlobalEnvPath() {
70
+ return path.join(globalConfigDir(), ".env");
71
+ }
72
+ function determineSource(key, shellSnapshot, currentValue) {
73
+ if (currentValue === null) {
74
+ return "missing";
75
+ }
76
+ const isInShellSnapshot = shellSnapshot.hasOwnProperty(key);
77
+ if (isInShellSnapshot) {
78
+ return "shell";
79
+ }
80
+ const globalEnv = parseEnvFileRaw(getGlobalEnvPath());
81
+ if (globalEnv.hasOwnProperty(key)) {
82
+ return "global";
83
+ }
84
+ const projectEnv = parseEnvFileRaw(getProjectEnvPath());
85
+ if (projectEnv.hasOwnProperty(key)) {
86
+ return "project-local";
87
+ }
88
+ return "shell";
89
+ }
90
+ function getDefaultValue(key) {
91
+ switch (key) {
92
+ case "AGENTWEAVER_HOME":
93
+ return path.join(os.homedir(), ".agentweaver");
94
+ default:
95
+ return null;
96
+ }
97
+ }
98
+ function validateJiraAuthMode(value) {
99
+ if (value === null) {
100
+ return true;
101
+ }
102
+ return JIRA_AUTH_MODE_ALLOWED_VALUES.includes(value);
103
+ }
104
+ function checkEnvDiagnostics() {
105
+ const shellSnapshot = {};
106
+ for (const key of Object.keys(process.env)) {
107
+ shellSnapshot[key] = process.env[key];
108
+ }
109
+ const projectEnv = parseEnvFileRaw(getProjectEnvPath());
110
+ const globalEnv = parseEnvFileRaw(getGlobalEnvPath());
111
+ const keyInfos = [];
112
+ let hasWarnings = false;
113
+ for (const key of MONITORED_KEYS) {
114
+ const currentValue = process.env[key] ?? null;
115
+ const source = determineSource(key, shellSnapshot, currentValue);
116
+ const isSecret = SECRET_KEYS.has(key);
117
+ let maskedValue = null;
118
+ if (currentValue !== null && isSecret) {
119
+ maskedValue = maskSecret(currentValue);
120
+ }
121
+ else if (currentValue !== null) {
122
+ maskedValue = currentValue;
123
+ }
124
+ const keyInfo = {
125
+ key,
126
+ source,
127
+ value: currentValue,
128
+ maskedValue,
129
+ isSecret,
130
+ };
131
+ keyInfos.push(keyInfo);
132
+ if (source === "missing") {
133
+ hasWarnings = true;
134
+ }
135
+ if (key === "JIRA_AUTH_MODE" && currentValue !== null) {
136
+ if (!validateJiraAuthMode(currentValue)) {
137
+ hasWarnings = true;
138
+ }
139
+ }
140
+ }
141
+ const status = hasWarnings ? DoctorStatus.Warn : DoctorStatus.Ok;
142
+ const keyCount = keyInfos.length;
143
+ const missingCount = keyInfos.filter(k => k.source === "missing").length;
144
+ const secretCount = keyInfos.filter(k => k.isSecret).length;
145
+ const summaryParts = [];
146
+ summaryParts.push(`${keyCount} keys checked`);
147
+ if (missingCount > 0) {
148
+ summaryParts.push(`${missingCount} missing`);
149
+ }
150
+ if (secretCount > 0) {
151
+ summaryParts.push(`${secretCount} secrets`);
152
+ }
153
+ const result = {
154
+ id: "env-diagnostics-01",
155
+ status,
156
+ title: "env-config",
157
+ message: summaryParts.join(", "),
158
+ ...(missingCount > 0 ? { hint: `${missingCount} configuration keys are missing` } : {}),
159
+ details: JSON.stringify(keyInfos),
160
+ };
161
+ return result;
162
+ }
163
+ export const envDiagnosticsCheck = {
164
+ id: "env-diagnostics-01",
165
+ category: CATEGORY.ENV_DIAGNOSTICS,
166
+ title: "env-config",
167
+ dependencies: [],
168
+ execute: async () => {
169
+ return checkEnvDiagnostics();
170
+ },
171
+ };
@@ -0,0 +1,106 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { DoctorStatus } from "../types.js";
3
+ import { CATEGORY } from "./category.js";
4
+ import { ALLOWED_MODELS_BY_EXECUTOR } from "../../pipeline/launch-profile-config.js";
5
+ import { findCmdPath, isExecutable } from "../../runtime/command-resolution.js";
6
+ function getEnvVarName(executorId) {
7
+ return executorId === "codex" ? "CODEX_BIN" : "OPENCODE_BIN";
8
+ }
9
+ function getCommandName(executorId) {
10
+ return executorId === "codex" ? "codex" : "opencode";
11
+ }
12
+ function resolveBinaryPath(executorId) {
13
+ const envVarName = getEnvVarName(executorId);
14
+ const commandName = getCommandName(executorId);
15
+ const configuredPath = process.env[envVarName];
16
+ if (configuredPath) {
17
+ if (isExecutable(configuredPath)) {
18
+ return { path: configuredPath, source: "env-override" };
19
+ }
20
+ return { path: null, source: "not-found" };
21
+ }
22
+ const foundPath = findCmdPath(commandName, "");
23
+ if (foundPath) {
24
+ return { path: foundPath, source: "PATH" };
25
+ }
26
+ return { path: null, source: "not-found" };
27
+ }
28
+ function runSmokeCheck(executorPath) {
29
+ const result = spawnSync(executorPath, ["--version"], { encoding: "utf8", stdio: "pipe" });
30
+ if (result.status === 0 && result.stdout) {
31
+ return result.stdout.trim();
32
+ }
33
+ return null;
34
+ }
35
+ function buildDetailsObj(executorId, resolution, versionOutput) {
36
+ return {
37
+ path: resolution.path,
38
+ source: resolution.source,
39
+ executable: resolution.path !== null && isExecutable(resolution.path),
40
+ versionOutput,
41
+ };
42
+ }
43
+ function createResult(executorId, status, message, hint, details, resolution, versionOutput) {
44
+ const result = {
45
+ id: `${executorId}-executor-01`,
46
+ status,
47
+ title: executorId,
48
+ message,
49
+ };
50
+ if (hint) {
51
+ result.hint = hint;
52
+ }
53
+ if (details) {
54
+ result.details = details;
55
+ }
56
+ result.detailsObj = buildDetailsObj(executorId, resolution, versionOutput);
57
+ return result;
58
+ }
59
+ function checkExecutor(executorId) {
60
+ const resolution = resolveBinaryPath(executorId);
61
+ if (resolution.path === null) {
62
+ const hint = executorId === "codex"
63
+ ? "Set CODEX_BIN environment variable to point to the codex binary"
64
+ : "Set OPENCODE_BIN environment variable to point to the opencode binary";
65
+ return createResult(executorId, DoctorStatus.Fail, `${executorId} binary not found`, hint, `source: ${resolution.source}`, resolution, null);
66
+ }
67
+ if (!isExecutable(resolution.path)) {
68
+ const hint = `Binary at ${resolution.path} is not executable`;
69
+ return createResult(executorId, DoctorStatus.Fail, `${executorId} binary is not executable`, hint, `path: ${resolution.path}`, resolution, null);
70
+ }
71
+ const versionOutput = runSmokeCheck(resolution.path);
72
+ if (versionOutput === null) {
73
+ return createResult(executorId, DoctorStatus.Fail, `${executorId} --version check failed`, `${executorId} --version did not produce expected output`, `path: ${resolution.path}, source: ${resolution.source}`, resolution, null);
74
+ }
75
+ const allowedModels = ALLOWED_MODELS_BY_EXECUTOR[executorId];
76
+ const modelWarnings = [];
77
+ for (const model of allowedModels) {
78
+ const modelResult = spawnSync(resolution.path, ["--model", model, "--version"], { encoding: "utf8", stdio: "pipe" });
79
+ if (modelResult.status !== 0) {
80
+ modelWarnings.push(`Model '${model}' validation failed (exit code ${modelResult.status})`);
81
+ }
82
+ }
83
+ const message = versionOutput;
84
+ if (modelWarnings.length > 0) {
85
+ return createResult(executorId, DoctorStatus.Warn, message, `Some models failed validation: ${modelWarnings.join("; ")}`, `source: ${resolution.source}, models validated: ${allowedModels.join(", ")}`, resolution, versionOutput);
86
+ }
87
+ return createResult(executorId, DoctorStatus.Ok, message, undefined, `source: ${resolution.source}`, resolution, versionOutput);
88
+ }
89
+ export const codexExecutorCheck = {
90
+ id: "codex-executor-01",
91
+ category: CATEGORY.EXECUTORS,
92
+ title: "codex",
93
+ dependencies: [],
94
+ execute: async () => {
95
+ return checkExecutor("codex");
96
+ },
97
+ };
98
+ export const opencodeExecutorCheck = {
99
+ id: "opencode-executor-01",
100
+ category: CATEGORY.EXECUTORS,
101
+ title: "opencode",
102
+ dependencies: [],
103
+ execute: async () => {
104
+ return checkExecutor("opencode");
105
+ },
106
+ };