@on-par/factory-cli 0.9.4 → 0.9.5-next.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/README.md +3 -3
- package/dist/cli/doctor.d.ts +34 -3
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +180 -42
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/github-stub.d.ts +38 -0
- package/dist/cli/github-stub.d.ts.map +1 -0
- package/dist/cli/github-stub.js +206 -0
- package/dist/cli/github-stub.js.map +1 -0
- package/dist/cli/index.d.ts +15 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +219 -49
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/stop-sentinel.js +3 -3
- package/dist/cli/stop-sentinel.js.map +1 -1
- package/dist/cli/sweep-heartbeat.js +5 -5
- package/dist/cli/sweep-heartbeat.js.map +1 -1
- package/dist/entry.d.ts +4 -1
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +14 -3
- package/dist/entry.js.map +1 -1
- package/dist/node-version.d.ts +5 -1
- package/dist/node-version.d.ts.map +1 -1
- package/dist/node-version.js +11 -3
- package/dist/node-version.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ On PAR Dev's Software Factory is a local CLI that takes a GitHub issue through p
|
|
|
6
6
|
|
|
7
7
|
Built in TypeScript. Routes work across Claude and Codex, with experimental local models through Ollama, and logs cost per task.
|
|
8
8
|
|
|
9
|
-

|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ factory --version
|
|
|
16
16
|
factory doctor
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Also requires `git`, one supported coding agent CLI (see [docs/agents.md](https://github.com/on-par/software-factory/blob/main/docs/agents.md)), signed in, and the GitHub CLI signed in with `gh auth login`. The factory reads `GITHUB_TOKEN`, then `GH_TOKEN`, and falls back to `gh auth token`. `factory doctor` checks all of this and prints a fix for anything missing. It also checks your git identity and the sandbox runtime, and it runs outside a repo.
|
|
19
|
+
Also requires `git`, one supported coding agent CLI (see [docs/agents.md](https://github.com/on-par/software-factory/blob/main/docs/agents.md)), signed in, and the GitHub CLI signed in with `gh auth login`. The factory reads `GITHUB_TOKEN`, then `GH_TOKEN`, and falls back to `gh auth token`. `factory doctor` checks all of this and prints a fix for anything missing. It also checks your git identity and the sandbox runtime, and it runs outside a repo. Global or repo-local `git config` for `user.name` and `user.email` both work. On Linux, install `firejail` first: without it, Codex runs with `--sandbox danger-full-access`.
|
|
20
20
|
|
|
21
21
|
`factory init` creates a local-only `.factory/` (git-excluded) with a `default-code` constitution. See the main README quick start.
|
|
22
22
|
|
|
@@ -30,7 +30,7 @@ Agents run with their permission prompts off (Claude Code with `--permission-mod
|
|
|
30
30
|
npm install -g @on-par/factory-cli@latest
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Automatic updates are planned: [#
|
|
33
|
+
`factory self-update --check` reports whether a newer release is on npm. It works in 0.9.3 and later. Automatic updates are planned: [#2355](https://github.com/on-par/software-factory/issues/2355), [#1939](https://github.com/on-par/software-factory/issues/1939).
|
|
34
34
|
|
|
35
35
|
## Documentation
|
|
36
36
|
|
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DeclaredHarnessProbe } from '@on-par/factory-core';
|
|
2
|
+
import type { ConfigLintInput } from '@on-par/factory-core/internal';
|
|
2
3
|
export interface DoctorCheck {
|
|
3
4
|
name: string;
|
|
4
5
|
ok: boolean;
|
|
@@ -18,6 +19,14 @@ export interface DoctorExecOptions {
|
|
|
18
19
|
/** Kill the command after this many ms; the probe then returns null. */
|
|
19
20
|
timeoutMs?: number;
|
|
20
21
|
}
|
|
22
|
+
/** A declared, agentic command harness the repo's models.registry uses (#2691). */
|
|
23
|
+
export interface ConfiguredCommandHarness {
|
|
24
|
+
harness: string;
|
|
25
|
+
/** The declared `command` template, verbatim. */
|
|
26
|
+
command: string;
|
|
27
|
+
/** First token of `command` to look up on PATH; null when it cannot be parsed safely. */
|
|
28
|
+
executable: string | null;
|
|
29
|
+
}
|
|
21
30
|
export interface DoctorEnvProbes {
|
|
22
31
|
commandAvailable: (cmd: string) => boolean;
|
|
23
32
|
envPresent: (key: string) => boolean;
|
|
@@ -36,12 +45,16 @@ export interface DoctorEnvProbes {
|
|
|
36
45
|
claudeLoggedIn?: () => boolean;
|
|
37
46
|
/** Whether a stored opencode auth login exists (#2556); reads local state only, never runs inference. */
|
|
38
47
|
opencodeLoggedIn?: () => boolean;
|
|
48
|
+
/** Repo-configured command harnesses (#2691); absent or [] means none. */
|
|
49
|
+
commandHarnesses?: () => ConfiguredCommandHarness[];
|
|
39
50
|
}
|
|
51
|
+
/** Section for rows about repo-configured command harnesses (#2691). */
|
|
52
|
+
export declare const COMMAND_HARNESS_SECTION = "Command harness";
|
|
40
53
|
/** Repo wired-up rows, sectioned only inside a git repo (#2557). */
|
|
41
54
|
export declare const REPO_SECTION = "Repo";
|
|
42
55
|
export declare const NOT_IN_REPO_HINT = "cd into your repo, then run factory doctor again";
|
|
43
56
|
/** Detail for every repo wired-up row when doctor runs outside a git repository (#2554). */
|
|
44
|
-
export declare const NOT_IN_REPO_SKIP_DETAIL = "skipped
|
|
57
|
+
export declare const NOT_IN_REPO_SKIP_DETAIL = "skipped: not inside a git repository; cd into your repo, then run factory doctor again";
|
|
45
58
|
export declare function hasSandboxRuntime(platform: NodeJS.Platform, commandAvailable: (cmd: string) => boolean): boolean;
|
|
46
59
|
/** Reason the sandboxed claude auth probe is skipped before a sandbox policy is resolved,
|
|
47
60
|
* or null when cmdDoctor should resolve the policy as before (#2383). */
|
|
@@ -71,6 +84,8 @@ export declare const GIT_AUTH_FIX = "run `gh auth setup-git` so git uses your gh
|
|
|
71
84
|
export declare function gitAuthCheck(repoRoot: string | null, tryExec: (cmd: string, opts?: DoctorExecOptions) => string | null): DoctorCheck;
|
|
72
85
|
export interface HarnessSetup {
|
|
73
86
|
section: string;
|
|
87
|
+
/** HARNESS_CATALOG id whose CLI probe decides whether this harness is installed (#2631). */
|
|
88
|
+
harnessId: string;
|
|
74
89
|
cli: string;
|
|
75
90
|
install: string;
|
|
76
91
|
/** Exact shell command that signs the harness in (#2556); also the row's `next` command. */
|
|
@@ -82,11 +97,24 @@ export interface HarnessSetup {
|
|
|
82
97
|
export declare const HARNESS_SETUP: readonly HarnessSetup[];
|
|
83
98
|
/** One optional `<cli> installed` row per harness, with the CLI version when found and the install command when not (#2555). */
|
|
84
99
|
export declare function harnessInstallChecks(commandAvailable: (cmd: string) => boolean, tryExec: (cmd: string) => string | null): DoctorCheck[];
|
|
85
|
-
/** One `<cli> signed in` row per harness (#2556), judged only from local evidence: an env key,
|
|
100
|
+
/** One `<cli> signed in` row per installed harness CLI (#2556, #2631), judged only from local evidence: an env key,
|
|
86
101
|
* stored credentials, or `codex login status`. Never runs inference and never prints a secret. */
|
|
87
102
|
export declare function harnessSignInChecks(probes: DoctorEnvProbes): DoctorCheck[];
|
|
103
|
+
/** The executable (first token) of a command template, or null when it cannot be parsed safely (#2691).
|
|
104
|
+
* Guards `isCommandAvailable`, which interpolates into `command -v` unescaped. */
|
|
105
|
+
export declare function commandExecutable(command: string): string | null;
|
|
106
|
+
/** Declared, agentic command harnesses that at least one repo `models.registry` entry uses (#2691). */
|
|
107
|
+
export declare function repoCommandHarnesses(harnesses: Readonly<Record<string, {
|
|
108
|
+
command: string;
|
|
109
|
+
agentic: boolean;
|
|
110
|
+
}>> | undefined, registry: Readonly<Record<string, {
|
|
111
|
+
harness?: string;
|
|
112
|
+
}>> | undefined): ConfiguredCommandHarness[];
|
|
113
|
+
/** One optional `command harness <id>` row per configured command harness (#2691). */
|
|
114
|
+
export declare function commandHarnessChecks(harnesses: readonly ConfiguredCommandHarness[], commandAvailable: (cmd: string) => boolean): DoctorCheck[];
|
|
88
115
|
/** Coding agent prerequisites: at least one agent CLI is required; each CLI is then reported
|
|
89
|
-
* on its own optional line, with sign-in reported separately from installation (#1269, #2422, #2555, #2556).
|
|
116
|
+
* on its own optional line, with sign-in reported separately from installation (#1269, #2422, #2555, #2556).
|
|
117
|
+
* A repo-configured command harness whose executable is on PATH also counts as a coding agent (#2691). */
|
|
90
118
|
export declare function agentCliChecks(probes: DoctorEnvProbes): DoctorCheck[];
|
|
91
119
|
export declare function runDoctorChecks(probes: DoctorEnvProbes): DoctorCheck[];
|
|
92
120
|
/** Row icon (#2550): ❌ only for a failure (counted in "N failed"), ➖ for a skipped row, ⚠️ for an optional warning. */
|
|
@@ -210,4 +238,7 @@ export declare function declaredHarnessChecks(probes: DeclaredHarnessProbe[]): D
|
|
|
210
238
|
export declare function modelsRegistryChecks(configPath: string, registry: Readonly<Record<string, {
|
|
211
239
|
harness?: string;
|
|
212
240
|
}>> | undefined, validHarnesses: readonly string[]): DoctorCheck[];
|
|
241
|
+
/** `config` rows from the shared pre-PLAN lint (#2612): one failing row per finding, so doctor fails exactly when
|
|
242
|
+
* the run preflight would escalate or park. A single ok row when clean. Read-only. */
|
|
243
|
+
export declare function configLintChecks(input: ConfigLintInput, loadError?: string): DoctorCheck[];
|
|
213
244
|
//# sourceMappingURL=doctor.d.ts.map
|
package/dist/cli/doctor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/doctor.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
// packages/cli/src/cli/doctor.ts — `factory doctor` environment preflight
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
2
3
|
import { resolve } from 'node:path';
|
|
3
4
|
import { detectSandboxRuntime } from '@on-par/factory-core';
|
|
5
|
+
import { findConfigSchemaErrors, isHarnessCliPresent, isTemplateToken, lintFactoryConfig, listBuiltInCheckers, } from '@on-par/factory-core/internal';
|
|
4
6
|
import { MIN_NODE_MAJOR } from '../node-version.js';
|
|
5
7
|
import { availableAgentClis, CLAUDE_CODE_URL, CODEX_CLI_URL, GITHUB_TOKENS_URL } from './first-run.js';
|
|
8
|
+
/** Section for rows about repo-configured command harnesses (#2691). */
|
|
9
|
+
export const COMMAND_HARNESS_SECTION = 'Command harness';
|
|
6
10
|
/** Repo wired-up rows, sectioned only inside a git repo (#2557). */
|
|
7
11
|
export const REPO_SECTION = 'Repo';
|
|
8
12
|
export const NOT_IN_REPO_HINT = 'cd into your repo, then run factory doctor again';
|
|
9
13
|
/** Detail for every repo wired-up row when doctor runs outside a git repository (#2554). */
|
|
10
|
-
export const NOT_IN_REPO_SKIP_DETAIL = `skipped
|
|
14
|
+
export const NOT_IN_REPO_SKIP_DETAIL = `skipped: not inside a git repository; ${NOT_IN_REPO_HINT}`;
|
|
11
15
|
export function hasSandboxRuntime(platform, commandAvailable) {
|
|
12
16
|
return detectSandboxRuntime(platform, commandAvailable) !== 'none';
|
|
13
17
|
}
|
|
@@ -15,9 +19,9 @@ export function hasSandboxRuntime(platform, commandAvailable) {
|
|
|
15
19
|
* or null when cmdDoctor should resolve the policy as before (#2383). */
|
|
16
20
|
export function sandboxAuthSkipDetail(opts) {
|
|
17
21
|
if (!opts.runtimeAvailable)
|
|
18
|
-
return 'skipped
|
|
22
|
+
return 'skipped: no sandbox runtime (sandbox-exec/firejail) on this host';
|
|
19
23
|
if (!opts.hostOk)
|
|
20
|
-
return 'skipped
|
|
24
|
+
return 'skipped: host claude auth was not verified';
|
|
21
25
|
if (!opts.inRepo)
|
|
22
26
|
return NOT_IN_REPO_SKIP_DETAIL;
|
|
23
27
|
return null;
|
|
@@ -70,7 +74,7 @@ export function gitAuthCheck(repoRoot, tryExec) {
|
|
|
70
74
|
}
|
|
71
75
|
const origin = tryExec('git remote get-url origin');
|
|
72
76
|
if (!origin) {
|
|
73
|
-
return { name: GIT_AUTH_CHECK, ok: true, optional: true, detail: 'skipped
|
|
77
|
+
return { name: GIT_AUTH_CHECK, ok: true, optional: true, detail: 'skipped: no origin remote configured' };
|
|
74
78
|
}
|
|
75
79
|
// An empty string is exit 0 with no HEAD ref (an empty repo): still authenticated.
|
|
76
80
|
const probe = tryExec(GIT_AUTH_PROBE_CMD, { env: { ...GIT_AUTH_PROBE_ENV }, timeoutMs: GIT_AUTH_PROBE_TIMEOUT_MS });
|
|
@@ -89,6 +93,7 @@ export function gitAuthCheck(repoRoot, tryExec) {
|
|
|
89
93
|
export const HARNESS_SETUP = [
|
|
90
94
|
{
|
|
91
95
|
section: 'Claude Code',
|
|
96
|
+
harnessId: 'claude-cli',
|
|
92
97
|
cli: 'claude',
|
|
93
98
|
install: 'npm install -g @anthropic-ai/claude-code',
|
|
94
99
|
login: 'claude /login',
|
|
@@ -96,6 +101,7 @@ export const HARNESS_SETUP = [
|
|
|
96
101
|
},
|
|
97
102
|
{
|
|
98
103
|
section: 'Codex',
|
|
104
|
+
harnessId: 'codex-cli',
|
|
99
105
|
cli: 'codex',
|
|
100
106
|
install: 'npm install -g @openai/codex',
|
|
101
107
|
login: 'codex login',
|
|
@@ -103,6 +109,7 @@ export const HARNESS_SETUP = [
|
|
|
103
109
|
},
|
|
104
110
|
{
|
|
105
111
|
section: 'OpenCode',
|
|
112
|
+
harnessId: 'opencode',
|
|
106
113
|
cli: 'opencode',
|
|
107
114
|
install: 'npm install -g opencode-ai',
|
|
108
115
|
login: 'opencode auth login',
|
|
@@ -148,16 +155,13 @@ function safe(fn) {
|
|
|
148
155
|
return false;
|
|
149
156
|
}
|
|
150
157
|
}
|
|
151
|
-
/** One `<cli> signed in` row per harness (#2556), judged only from local evidence: an env key,
|
|
158
|
+
/** One `<cli> signed in` row per installed harness CLI (#2556, #2631), judged only from local evidence: an env key,
|
|
152
159
|
* stored credentials, or `codex login status`. Never runs inference and never prints a secret. */
|
|
153
160
|
export function harnessSignInChecks(probes) {
|
|
154
161
|
const { commandAvailable, envPresent, tryExec } = probes;
|
|
155
162
|
const found = availableAgentClis(commandAvailable);
|
|
156
|
-
return HARNESS_SETUP.map(({ section, cli, login, envKeys }) => {
|
|
163
|
+
return HARNESS_SETUP.filter(({ harnessId }) => isHarnessCliPresent(harnessId, commandAvailable)).map(({ section, cli, login, envKeys }) => {
|
|
157
164
|
const name = `${cli} signed in`;
|
|
158
|
-
if (!commandAvailable(cli)) {
|
|
159
|
-
return { name, section, ok: true, optional: true, detail: `skipped — ${cli} not installed` };
|
|
160
|
-
}
|
|
161
165
|
const fix = `sign in to ${section}: ${login}`;
|
|
162
166
|
if (cli === 'codex') {
|
|
163
167
|
const key = envKeys.find((k) => envPresent(k));
|
|
@@ -212,29 +216,98 @@ export function harnessSignInChecks(probes) {
|
|
|
212
216
|
ok: false,
|
|
213
217
|
// Required only when this is the sole agent: then nothing else can run.
|
|
214
218
|
optional: found.length > 1,
|
|
215
|
-
detail: `unverified
|
|
219
|
+
detail: `unverified: no sign-in evidence (${SIGN_IN_EVIDENCE[cli]})`,
|
|
216
220
|
fix,
|
|
217
221
|
next: login,
|
|
218
222
|
};
|
|
219
223
|
});
|
|
220
224
|
}
|
|
225
|
+
function safeList(fn) {
|
|
226
|
+
if (!fn)
|
|
227
|
+
return [];
|
|
228
|
+
try {
|
|
229
|
+
return fn();
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** The executable (first token) of a command template, or null when it cannot be parsed safely (#2691).
|
|
236
|
+
* Guards `isCommandAvailable`, which interpolates into `command -v` unescaped. */
|
|
237
|
+
export function commandExecutable(command) {
|
|
238
|
+
const tokens = command.trim().split(/\s+/);
|
|
239
|
+
while (tokens.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[0]))
|
|
240
|
+
tokens.shift();
|
|
241
|
+
let token = tokens[0];
|
|
242
|
+
if (!token)
|
|
243
|
+
return null;
|
|
244
|
+
const quoted = /^(['"])(.*)\1$/.exec(token);
|
|
245
|
+
if (quoted)
|
|
246
|
+
token = quoted[2];
|
|
247
|
+
return /^[\w./@+~-]+$/.test(token) ? token : null;
|
|
248
|
+
}
|
|
249
|
+
/** Declared, agentic command harnesses that at least one repo `models.registry` entry uses (#2691). */
|
|
250
|
+
export function repoCommandHarnesses(harnesses, registry) {
|
|
251
|
+
const used = new Set(Object.values(registry ?? {}).map((def) => def.harness));
|
|
252
|
+
return Object.entries(harnesses ?? {})
|
|
253
|
+
.filter(([id, decl]) => decl.agentic === true && used.has(id))
|
|
254
|
+
.map(([id, decl]) => ({ harness: id, command: decl.command, executable: commandExecutable(decl.command) }));
|
|
255
|
+
}
|
|
256
|
+
/** One optional `command harness <id>` row per configured command harness (#2691). */
|
|
257
|
+
export function commandHarnessChecks(harnesses, commandAvailable) {
|
|
258
|
+
return harnesses.map((h) => {
|
|
259
|
+
const name = `command harness ${h.harness}`;
|
|
260
|
+
if (h.executable !== null && commandAvailable(h.executable)) {
|
|
261
|
+
return {
|
|
262
|
+
name,
|
|
263
|
+
section: COMMAND_HARNESS_SECTION,
|
|
264
|
+
optional: true,
|
|
265
|
+
ok: true,
|
|
266
|
+
detail: `${h.executable} found on PATH (command: ${h.command})`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
name,
|
|
271
|
+
section: COMMAND_HARNESS_SECTION,
|
|
272
|
+
optional: true,
|
|
273
|
+
ok: false,
|
|
274
|
+
detail: `configured command \`${h.command}\`: ${h.executable === null ? 'cannot parse an executable from it' : `${h.executable} not found on PATH`}`,
|
|
275
|
+
fix: installFix(h),
|
|
276
|
+
};
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
function installFix(h) {
|
|
280
|
+
return `install ${h.executable ?? 'the agent CLI'} on PATH or fix "command" for harness ${h.harness} in the models config harnesses section`;
|
|
281
|
+
}
|
|
221
282
|
/** Coding agent prerequisites: at least one agent CLI is required; each CLI is then reported
|
|
222
|
-
* on its own optional line, with sign-in reported separately from installation (#1269, #2422, #2555, #2556).
|
|
283
|
+
* on its own optional line, with sign-in reported separately from installation (#1269, #2422, #2555, #2556).
|
|
284
|
+
* A repo-configured command harness whose executable is on PATH also counts as a coding agent (#2691). */
|
|
223
285
|
export function agentCliChecks(probes) {
|
|
224
286
|
const { commandAvailable, tryExec } = probes;
|
|
225
287
|
const found = availableAgentClis(commandAvailable);
|
|
288
|
+
const configured = safeList(probes.commandHarnesses);
|
|
289
|
+
const runnable = configured.filter((h) => h.executable !== null && commandAvailable(h.executable));
|
|
290
|
+
const agents = [...found, ...runnable.map((h) => `${h.harness} (command harness)`)];
|
|
291
|
+
const describe = (h) => `command harness ${h.harness} runs \`${h.command}\`, but ${h.executable ?? 'its executable'} is not on PATH`;
|
|
292
|
+
const missingRow = configured.length === 0
|
|
293
|
+
? {
|
|
294
|
+
name: 'coding agent',
|
|
295
|
+
ok: false,
|
|
296
|
+
detail: 'no coding agent CLI on PATH (claude, codex or opencode)',
|
|
297
|
+
fix: `install Claude Code (${CLAUDE_CODE_URL}) or the Codex CLI (${CODEX_CLI_URL})`,
|
|
298
|
+
next: HARNESS_SETUP[0].install,
|
|
299
|
+
}
|
|
300
|
+
: {
|
|
301
|
+
name: 'coding agent',
|
|
302
|
+
ok: false,
|
|
303
|
+
detail: `no coding agent CLI on PATH (claude, codex or opencode); ${configured.map(describe).join('; ')}`,
|
|
304
|
+
fix: installFix(configured[0]),
|
|
305
|
+
};
|
|
226
306
|
const checks = [
|
|
227
|
-
|
|
228
|
-
? { name: 'coding agent', ok: true, detail: `found ${found.join(', ')}` }
|
|
229
|
-
: {
|
|
230
|
-
name: 'coding agent',
|
|
231
|
-
ok: false,
|
|
232
|
-
detail: 'no coding agent CLI on PATH (claude, codex or opencode)',
|
|
233
|
-
fix: `install Claude Code (${CLAUDE_CODE_URL}) or the Codex CLI (${CODEX_CLI_URL})`,
|
|
234
|
-
next: HARNESS_SETUP[0].install,
|
|
235
|
-
},
|
|
307
|
+
agents.length > 0 ? { name: 'coding agent', ok: true, detail: `found ${agents.join(', ')}` } : missingRow,
|
|
236
308
|
];
|
|
237
309
|
checks.push(...harnessInstallChecks(commandAvailable, tryExec));
|
|
310
|
+
checks.push(...commandHarnessChecks(configured, commandAvailable));
|
|
238
311
|
checks.push(...harnessSignInChecks(probes));
|
|
239
312
|
return checks;
|
|
240
313
|
}
|
|
@@ -248,7 +321,7 @@ export function runDoctorChecks(probes) {
|
|
|
248
321
|
name: 'git repository',
|
|
249
322
|
ok: false,
|
|
250
323
|
optional: true,
|
|
251
|
-
detail: 'not inside a git repository
|
|
324
|
+
detail: 'not inside a git repository; repo checks skipped',
|
|
252
325
|
fix: NOT_IN_REPO_HINT,
|
|
253
326
|
});
|
|
254
327
|
const repoSection = repoRoot === null ? {} : { section: REPO_SECTION };
|
|
@@ -353,14 +426,14 @@ export function runDoctorChecks(probes) {
|
|
|
353
426
|
ok: true,
|
|
354
427
|
optional: true,
|
|
355
428
|
detail: sandboxRuntime === 'none'
|
|
356
|
-
? 'codex CLI found
|
|
429
|
+
? 'codex CLI found; no sandbox runtime, so codex runs with danger-full-access'
|
|
357
430
|
: 'codex CLI found (sandboxed builds available)',
|
|
358
431
|
}
|
|
359
432
|
: {
|
|
360
433
|
name: 'codex sandbox',
|
|
361
434
|
ok: false,
|
|
362
435
|
optional: true,
|
|
363
|
-
detail: 'codex CLI not found
|
|
436
|
+
detail: 'codex CLI not found; codex build routes unavailable',
|
|
364
437
|
fix: 'install the codex CLI, or rely on claude routes',
|
|
365
438
|
});
|
|
366
439
|
checks.push(sandboxRuntime !== 'none'
|
|
@@ -369,7 +442,7 @@ export function runDoctorChecks(probes) {
|
|
|
369
442
|
name: 'sandbox runtime',
|
|
370
443
|
ok: false,
|
|
371
444
|
optional: true,
|
|
372
|
-
detail: 'no sandbox-exec (macOS) or firejail (Linux) on PATH
|
|
445
|
+
detail: 'no sandbox-exec (macOS) or firejail (Linux) on PATH; agents will run uncontained',
|
|
373
446
|
fix: 'install firejail (Linux) or run on macOS; see README Safety',
|
|
374
447
|
});
|
|
375
448
|
if (repoRoot === null) {
|
|
@@ -441,7 +514,7 @@ export function formatDoctorChecks(checks, opts = {}) {
|
|
|
441
514
|
}
|
|
442
515
|
currentSection = check.section;
|
|
443
516
|
const icon = doctorCheckIcon(check);
|
|
444
|
-
lines.push(` ${icon} ${check.name}
|
|
517
|
+
lines.push(` ${icon} ${check.name} - ${check.detail}`);
|
|
445
518
|
if (!check.ok && check.fix) {
|
|
446
519
|
lines.push(` fix: ${check.fix}`);
|
|
447
520
|
}
|
|
@@ -481,15 +554,15 @@ export function claudeKeychainCheck(probe) {
|
|
|
481
554
|
return {
|
|
482
555
|
name: 'claude keychain (macOS)',
|
|
483
556
|
ok: false,
|
|
484
|
-
detail: `security find-generic-password -s '${service}'${profile} failed inside tmux (SecKeychainSearchCopyNext)
|
|
485
|
-
fix: 'run the factory from a context with login-keychain access
|
|
557
|
+
detail: `security find-generic-password -s '${service}'${profile} failed inside tmux (SecKeychainSearchCopyNext); Claude OAuth refresh will fail with local_auth`,
|
|
558
|
+
fix: 'run the factory from a context with login-keychain access, such as a LaunchAgent in the gui domain (launchctl bootstrap gui/$(id -u) …) or a login Terminal, not a raw tmux pane; see docs/runbooks/macos-keychain-launchagent.md',
|
|
486
559
|
};
|
|
487
560
|
}
|
|
488
561
|
return {
|
|
489
562
|
name: 'claude keychain (macOS)',
|
|
490
563
|
ok: false,
|
|
491
564
|
optional: true,
|
|
492
|
-
detail: `security find-generic-password -s '${service}'${profile} failed
|
|
565
|
+
detail: `security find-generic-password -s '${service}'${profile} failed; either never logged in, or this launch context cannot read the login keychain`,
|
|
493
566
|
fix: `run ${loginFix(probe)} and complete /login (or export ANTHROPIC_API_KEY); if already logged in, see docs/runbooks/macos-keychain-launchagent.md`,
|
|
494
567
|
};
|
|
495
568
|
}
|
|
@@ -499,7 +572,7 @@ export function keychainPreflightError(probe) {
|
|
|
499
572
|
if (probe.status !== 'unreadable' || !probe.inTmux)
|
|
500
573
|
return null;
|
|
501
574
|
const service = probe.service ?? DEFAULT_KEYCHAIN_SERVICE;
|
|
502
|
-
return `Claude Code credentials are not readable from this tmux session (security find-generic-password -s '${service}' failed / SecKeychainSearchCopyNext). OAuth refresh will fail and every claimed issue would spuriously park as local_auth. Launch the factory from a context with login-keychain access
|
|
575
|
+
return `Claude Code credentials are not readable from this tmux session (security find-generic-password -s '${service}' failed / SecKeychainSearchCopyNext). OAuth refresh will fail and every claimed issue would spuriously park as local_auth. Launch the factory from a context with login-keychain access, such as a LaunchAgent in the gui domain (launchctl bootstrap gui/$(id -u) …) or a login Terminal, or export ANTHROPIC_API_KEY. See docs/runbooks/macos-keychain-launchagent.md.`;
|
|
503
576
|
}
|
|
504
577
|
const HOST_AUTH_CHECK = 'claude auth (host)';
|
|
505
578
|
const SANDBOX_AUTH_CHECK = 'claude auth (sandboxed)';
|
|
@@ -528,7 +601,7 @@ export function sandboxClaudeAuthChecks(result) {
|
|
|
528
601
|
sandboxed = {
|
|
529
602
|
name: SANDBOX_AUTH_CHECK,
|
|
530
603
|
ok: false,
|
|
531
|
-
detail: `${result.sandboxDetail}
|
|
604
|
+
detail: `${result.sandboxDetail}; host claude auth is healthy, so the sandbox is blocking Claude credential refresh`,
|
|
532
605
|
fix: 'check the sandbox write allowlist (~/.claude.json*, $CLAUDE_CONFIG_DIR when set, ~/Library/Keychains); as a stopgap run with FACTORY_SANDBOX=0',
|
|
533
606
|
};
|
|
534
607
|
}
|
|
@@ -537,7 +610,7 @@ export function sandboxClaudeAuthChecks(result) {
|
|
|
537
610
|
name: SANDBOX_AUTH_CHECK,
|
|
538
611
|
ok: false,
|
|
539
612
|
optional: true,
|
|
540
|
-
detail: `${result.sandboxDetail}
|
|
613
|
+
detail: `${result.sandboxDetail}; host claude auth is not healthy either, so this is not attributable to the sandbox`,
|
|
541
614
|
fix: 'fix host claude auth first, then re-run `factory doctor`',
|
|
542
615
|
};
|
|
543
616
|
}
|
|
@@ -568,7 +641,7 @@ export function leaseChecks(rows) {
|
|
|
568
641
|
name: `port lease :${row.port}`,
|
|
569
642
|
ok: true,
|
|
570
643
|
optional: true,
|
|
571
|
-
detail: `${row.worktreeId}
|
|
644
|
+
detail: `${row.worktreeId}: port ${row.port}, pid ${row.pid}, live`,
|
|
572
645
|
};
|
|
573
646
|
}
|
|
574
647
|
const squatted = row.portSquatted ? '; port still in use by another process' : '';
|
|
@@ -576,7 +649,7 @@ export function leaseChecks(rows) {
|
|
|
576
649
|
name: `port lease :${row.port}`,
|
|
577
650
|
ok: false,
|
|
578
651
|
optional: true,
|
|
579
|
-
detail: `${row.worktreeId}
|
|
652
|
+
detail: `${row.worktreeId}: port ${row.port}, pid ${row.pid}, stale (${row.reason})${squatted}`,
|
|
580
653
|
fix: 'run `factory doctor --reconcile` to reclaim stale leases',
|
|
581
654
|
};
|
|
582
655
|
});
|
|
@@ -615,7 +688,7 @@ export function eventLogCheck(integrity) {
|
|
|
615
688
|
ok: false,
|
|
616
689
|
optional: true,
|
|
617
690
|
detail: `${integrity.unparseable} of ${integrity.total} lines unparseable (${pct}%)`,
|
|
618
|
-
fix: 'historical corruption from pre-lock parallel writes; appends are now serialized
|
|
691
|
+
fix: 'historical corruption from pre-lock parallel writes; appends are now serialized; metrics over this file carry this error bar',
|
|
619
692
|
};
|
|
620
693
|
}
|
|
621
694
|
export function orphanContainerChecks(names) {
|
|
@@ -646,7 +719,7 @@ export function formatContainerReconcileReport(reaped) {
|
|
|
646
719
|
return reaped
|
|
647
720
|
.map((r) => r.removed
|
|
648
721
|
? `reconcile: removed container ${r.name}`
|
|
649
|
-
: `reconcile: failed to remove container ${r.name}
|
|
722
|
+
: `reconcile: failed to remove container ${r.name}: ${r.detail}`)
|
|
650
723
|
.join('\n');
|
|
651
724
|
}
|
|
652
725
|
export function formatMicroVmReconcileReport(reaped) {
|
|
@@ -655,7 +728,7 @@ export function formatMicroVmReconcileReport(reaped) {
|
|
|
655
728
|
return reaped
|
|
656
729
|
.map((r) => r.removed
|
|
657
730
|
? `reconcile: removed sbx VM ${r.name} (sbx rm --force)`
|
|
658
|
-
: `reconcile: failed to remove sbx VM ${r.name}
|
|
731
|
+
: `reconcile: failed to remove sbx VM ${r.name}: ${r.detail}`)
|
|
659
732
|
.join('\n');
|
|
660
733
|
}
|
|
661
734
|
export function formatReconcileReport(reaped) {
|
|
@@ -682,7 +755,7 @@ export function formatClaimReconcileReport(released) {
|
|
|
682
755
|
return released
|
|
683
756
|
.map((c) => c.released
|
|
684
757
|
? `reconcile: released issue #${c.issue} back to factory:queued (lease expired at ${c.expiresAt})`
|
|
685
|
-
: `reconcile: failed to release issue #${c.issue} (lease expired at ${c.expiresAt})
|
|
758
|
+
: `reconcile: failed to release issue #${c.issue} (lease expired at ${c.expiresAt}): ${c.detail ?? 'unknown error'}`)
|
|
686
759
|
.join('\n');
|
|
687
760
|
}
|
|
688
761
|
const GREEN_PR_CHECK = 'green PRs awaiting merge';
|
|
@@ -696,7 +769,7 @@ export function unmergedGreenPrChecks(result) {
|
|
|
696
769
|
name: GREEN_PR_CHECK,
|
|
697
770
|
ok: false,
|
|
698
771
|
optional: true,
|
|
699
|
-
detail: `scan failed
|
|
772
|
+
detail: `scan failed: ${result.detail}`,
|
|
700
773
|
fix: 'check `gh auth status` and network access, then re-run `factory doctor`',
|
|
701
774
|
},
|
|
702
775
|
];
|
|
@@ -708,8 +781,8 @@ export function unmergedGreenPrChecks(result) {
|
|
|
708
781
|
name: `green PR #${row.number}`,
|
|
709
782
|
ok: false,
|
|
710
783
|
optional: true,
|
|
711
|
-
detail: `${row.issue === null ? 'owning issue unknown' : `issue #${row.issue}`}
|
|
712
|
-
fix: `review and merge #${row.number} (or close it)
|
|
784
|
+
detail: `${row.issue === null ? 'owning issue unknown' : `issue #${row.issue}`}: green and ready on ${row.branch}, open ${row.ageHours}h, not merged`,
|
|
785
|
+
fix: `review and merge #${row.number} (or close it); the factory never auto-merges an orphaned PR`,
|
|
713
786
|
}));
|
|
714
787
|
}
|
|
715
788
|
/** One doctor check per user-declared harness, from its `probe` result. */
|
|
@@ -722,7 +795,7 @@ export function declaredHarnessChecks(probes) {
|
|
|
722
795
|
return {
|
|
723
796
|
name,
|
|
724
797
|
ok: false,
|
|
725
|
-
detail: `unavailable
|
|
798
|
+
detail: `unavailable: probe \`${p.probe}\` failed: ${p.detail}`,
|
|
726
799
|
fix: 'install the agent CLI on PATH or fix the "probe" command in the models config harnesses section',
|
|
727
800
|
};
|
|
728
801
|
}
|
|
@@ -730,7 +803,7 @@ export function declaredHarnessChecks(probes) {
|
|
|
730
803
|
name,
|
|
731
804
|
ok: false,
|
|
732
805
|
optional: true,
|
|
733
|
-
detail: 'unverified
|
|
806
|
+
detail: 'unverified: no probe configured',
|
|
734
807
|
fix: 'add a "probe" command (exit 0 = reachable) to the harness in the models config',
|
|
735
808
|
};
|
|
736
809
|
});
|
|
@@ -768,4 +841,69 @@ export function modelsRegistryChecks(configPath, registry, validHarnesses) {
|
|
|
768
841
|
const n = entries.length;
|
|
769
842
|
return [{ name: REGISTRY_CHECK, ok: true, detail: `${n} entr${n === 1 ? 'y' : 'ies'} valid (${configPath})` }];
|
|
770
843
|
}
|
|
844
|
+
/** `config` rows from the shared pre-PLAN lint (#2612): one failing row per finding, so doctor fails exactly when
|
|
845
|
+
* the run preflight would escalate or park. A single ok row when clean. Read-only. */
|
|
846
|
+
export function configLintChecks(input, loadError) {
|
|
847
|
+
const schemaRows = (input.configFile ? findConfigSchemaErrors(input.configFile) : []).map((f) => ({
|
|
848
|
+
name: 'config',
|
|
849
|
+
ok: false,
|
|
850
|
+
section: REPO_SECTION,
|
|
851
|
+
detail: `${f.file}${f.line ? `:${f.line}` : ''}: schema error at '${f.path}': ${f.message}`,
|
|
852
|
+
fix: f.fix,
|
|
853
|
+
}));
|
|
854
|
+
if (loadError !== undefined) {
|
|
855
|
+
return [
|
|
856
|
+
{
|
|
857
|
+
name: 'config',
|
|
858
|
+
ok: false,
|
|
859
|
+
section: REPO_SECTION,
|
|
860
|
+
detail: loadError,
|
|
861
|
+
fix: 'fix the constitution frontmatter YAML',
|
|
862
|
+
},
|
|
863
|
+
...schemaRows,
|
|
864
|
+
];
|
|
865
|
+
}
|
|
866
|
+
const { findings } = lintFactoryConfig(input);
|
|
867
|
+
if (findings.length > 0) {
|
|
868
|
+
const names = [...listBuiltInCheckers().map((c) => c.name), 'custom_*'].join(', ');
|
|
869
|
+
const lintRows = findings.map((f) => {
|
|
870
|
+
const where = `${f.file}${f.line ? `:${f.line}` : ''}`;
|
|
871
|
+
if (f.rule === 'unknown-checker') {
|
|
872
|
+
const kind = isTemplateToken(f.token) ? 'placeholder' : 'unknown';
|
|
873
|
+
return {
|
|
874
|
+
name: 'config',
|
|
875
|
+
ok: false,
|
|
876
|
+
section: REPO_SECTION,
|
|
877
|
+
detail: `${where}: ${kind} checker '${f.token}'`,
|
|
878
|
+
fix: `replace '${f.token}' with a real checker name (${names}) or delete the line`,
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
return {
|
|
882
|
+
name: 'config',
|
|
883
|
+
ok: false,
|
|
884
|
+
section: REPO_SECTION,
|
|
885
|
+
detail: `${where}: unfilled template token '${f.token}'`,
|
|
886
|
+
fix: `replace '${f.token}' with a real value`,
|
|
887
|
+
};
|
|
888
|
+
});
|
|
889
|
+
return [...lintRows, ...schemaRows];
|
|
890
|
+
}
|
|
891
|
+
if (schemaRows.length > 0)
|
|
892
|
+
return schemaRows;
|
|
893
|
+
const scanned = [];
|
|
894
|
+
if (input.constitution?.repoInstance)
|
|
895
|
+
scanned.push(input.constitution.repoInstance.relPath);
|
|
896
|
+
if (input.configFile && existsSync(input.configFile.absPath))
|
|
897
|
+
scanned.push(input.configFile.relPath);
|
|
898
|
+
return [
|
|
899
|
+
{
|
|
900
|
+
name: 'config',
|
|
901
|
+
ok: true,
|
|
902
|
+
section: REPO_SECTION,
|
|
903
|
+
detail: scanned.length > 0
|
|
904
|
+
? `no unfilled placeholders, unknown checkers or schema errors in ${scanned.join(', ')}`
|
|
905
|
+
: 'no constitution or config file to scan',
|
|
906
|
+
},
|
|
907
|
+
];
|
|
908
|
+
}
|
|
771
909
|
//# sourceMappingURL=doctor.js.map
|