@brainervirus/workit-core 0.11.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/package.json +8 -3
- package/scripts/analyze-release-scope.ts +4 -1
- package/scripts/doctor-check.ts +3 -1
- package/scripts/install-codex-plugin.sh +28 -0
- package/scripts/install-cursor-plugin.sh +1 -0
- package/scripts/install-opencode-plugin.sh +1 -0
- package/scripts/install-pi-package.sh +56 -0
- package/scripts/rewrite-workspace-deps.ts +22 -5
- package/scripts/sync-release-manifests.ts +8 -5
- package/scripts/sync-runtime.sh +7 -3
- package/scripts/validate-cursor-marketplace.ts +16 -62
- package/skills/workit-babysit/SKILL.md +33 -0
- package/skills/workit-behavioral-tdd/SKILL.md +53 -0
- package/skills/workit-blast-radius/SKILL.md +31 -0
- package/skills/workit-challenge/SKILL.md +62 -0
- package/skills/workit-debug/SKILL.md +61 -0
- package/skills/workit-deslop/SKILL.md +36 -0
- package/skills/workit-diagram/SKILL.md +32 -0
- package/skills/workit-green-run/SKILL.md +29 -0
- package/skills/workit-handoff/SKILL.md +43 -0
- package/skills/workit-implement/SKILL.md +46 -0
- package/skills/workit-mockup/SKILL.md +28 -0
- package/skills/workit-plan/SKILL.md +66 -0
- package/skills/workit-review/SKILL.md +60 -0
- package/skills/workit-steer/SKILL.md +32 -0
- package/src/core/authority.ts +918 -0
- package/src/core/boundary.ts +29 -0
- package/src/core/branch.ts +26 -255
- package/src/core/changelog.ts +95 -18
- package/src/core/commit-flavors.ts +65 -0
- package/src/core/config-conversion.ts +183 -0
- package/src/core/config.ts +42 -0
- package/src/core/cutover.ts +721 -0
- package/src/core/detect-hosts.ts +107 -0
- package/src/core/doctor.ts +428 -18
- package/src/core/external-action-effects.ts +1171 -0
- package/src/core/external-action.ts +541 -0
- package/src/core/init.ts +83 -32
- package/src/core/methods.ts +125 -0
- package/src/core/policy-resolver.ts +523 -0
- package/src/core/pr-create.ts +7 -1
- package/src/core/registration.ts +69 -4
- package/src/core/repo-context.ts +59 -1
- package/src/core/setup.ts +28 -0
- package/src/core/skill-manifests.ts +40 -42
- package/src/core/support-matrix.ts +8 -3
- package/src/core/sync-runtime.ts +15 -3
- package/src/core/task-context.ts +179 -0
- package/src/core/task-contract.ts +1136 -0
- package/src/core/task-engine.ts +2562 -0
- package/src/core/task-evaluation.ts +679 -0
- package/src/core/task-store.ts +1071 -0
- package/src/core/tracker-issues.ts +237 -0
- package/src/core/uninstall.ts +67 -3
- package/src/core/vcs-config.ts +53 -22
- package/src/core/workers.ts +166 -0
- package/src/core/workspaces.ts +3 -1
- package/src/core/youtrack-tools.ts +2 -0
- package/src/core/youtrack.ts +145 -10
- package/src/core.ts +126 -0
- package/templates/execution-contract.md +17 -50
- package/templates/plan-template.md +3 -3
- package/templates/spec-template.md +15 -3
- package/templates/workit-contract.md +12 -0
- package/commands/wk-changelog.md +0 -2
- package/commands/wk-commit.md +0 -2
- package/commands/wk-docs-refresh.md +0 -2
- package/commands/wk-handoff.md +0 -2
- package/commands/wk-implement.md +0 -2
- package/commands/wk-init.md +0 -2
- package/commands/wk-issue-update.md +0 -2
- package/commands/wk-meetings.md +0 -2
- package/commands/wk-pr.md +0 -2
- package/commands/wk-release-notes.md +0 -2
- package/commands/wk-status.md +0 -2
- package/commands/wk-verify.md +0 -2
- package/scripts/update-superpowers.sh +0 -82
- package/scripts/vendor-assets.ts +0 -37
- package/skills/wk-changelog/SKILL.md +0 -15
- package/skills/wk-commit/SKILL.md +0 -16
- package/skills/wk-docs-refresh/SKILL.md +0 -15
- package/skills/wk-handoff/SKILL.md +0 -20
- package/skills/wk-implement/SKILL.md +0 -47
- package/skills/wk-init/SKILL.md +0 -31
- package/skills/wk-issue-update/SKILL.md +0 -27
- package/skills/wk-issue-update/references/youtrack-update-style.md +0 -81
- package/skills/wk-meetings/SKILL.md +0 -17
- package/skills/wk-pr/SKILL.md +0 -27
- package/skills/wk-release-notes/SKILL.md +0 -15
- package/skills/wk-status/SKILL.md +0 -16
- package/skills/wk-verify/SKILL.md +0 -16
- package/src/core/detector.ts +0 -239
- package/src/core/flow-state.ts +0 -3308
- package/src/core/handoff-context.ts +0 -136
- package/src/core/handoff-tools.ts +0 -133
- package/src/core/menu.ts +0 -70
- package/src/core/plan-tasks.ts +0 -43
- package/src/core/reminder.ts +0 -124
- package/src/core/sdd.ts +0 -353
- package/src/state.ts +0 -22
- package/templates/superpowers-doc-contract.md +0 -75
- package/vendor/superpowers/skills/brainstorming/SKILL.md +0 -159
- package/vendor/superpowers/skills/brainstorming/scripts/frame-template.html +0 -213
- package/vendor/superpowers/skills/brainstorming/scripts/helper.js +0 -167
- package/vendor/superpowers/skills/brainstorming/scripts/server.cjs +0 -723
- package/vendor/superpowers/skills/brainstorming/scripts/start-server.sh +0 -209
- package/vendor/superpowers/skills/brainstorming/scripts/stop-server.sh +0 -120
- package/vendor/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
- package/vendor/superpowers/skills/brainstorming/visual-companion.md +0 -291
- package/vendor/superpowers/skills/dispatching-parallel-agents/SKILL.md +0 -185
- package/vendor/superpowers/skills/executing-plans/SKILL.md +0 -70
- package/vendor/superpowers/skills/finishing-a-development-branch/SKILL.md +0 -241
- package/vendor/superpowers/skills/receiving-code-review/SKILL.md +0 -213
- package/vendor/superpowers/skills/requesting-code-review/SKILL.md +0 -103
- package/vendor/superpowers/skills/requesting-code-review/code-reviewer.md +0 -172
- package/vendor/superpowers/skills/subagent-driven-development/SKILL.md +0 -428
- package/vendor/superpowers/skills/subagent-driven-development/implementer-prompt.md +0 -139
- package/vendor/superpowers/skills/subagent-driven-development/scripts/review-package +0 -44
- package/vendor/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +0 -22
- package/vendor/superpowers/skills/subagent-driven-development/scripts/task-brief +0 -40
- package/vendor/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +0 -188
- package/vendor/superpowers/skills/systematic-debugging/CREATION-LOG.md +0 -119
- package/vendor/superpowers/skills/systematic-debugging/SKILL.md +0 -296
- package/vendor/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +0 -158
- package/vendor/superpowers/skills/systematic-debugging/condition-based-waiting.md +0 -115
- package/vendor/superpowers/skills/systematic-debugging/defense-in-depth.md +0 -122
- package/vendor/superpowers/skills/systematic-debugging/find-polluter.sh +0 -63
- package/vendor/superpowers/skills/systematic-debugging/root-cause-tracing.md +0 -169
- package/vendor/superpowers/skills/systematic-debugging/test-academic.md +0 -14
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-1.md +0 -58
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-2.md +0 -68
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-3.md +0 -69
- package/vendor/superpowers/skills/test-driven-development/SKILL.md +0 -371
- package/vendor/superpowers/skills/test-driven-development/testing-anti-patterns.md +0 -299
- package/vendor/superpowers/skills/using-git-worktrees/SKILL.md +0 -202
- package/vendor/superpowers/skills/using-superpowers/SKILL.md +0 -62
- package/vendor/superpowers/skills/using-superpowers/references/antigravity-tools.md +0 -23
- package/vendor/superpowers/skills/using-superpowers/references/codex-tools.md +0 -39
- package/vendor/superpowers/skills/using-superpowers/references/pi-tools.md +0 -16
- package/vendor/superpowers/skills/verification-before-completion/SKILL.md +0 -139
- package/vendor/superpowers/skills/writing-plans/SKILL.md +0 -174
- package/vendor/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +0 -49
- package/vendor/superpowers/skills/writing-skills/SKILL.md +0 -689
- package/vendor/superpowers/skills/writing-skills/anthropic-best-practices.md +0 -1150
- package/vendor/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -189
- package/vendor/superpowers/skills/writing-skills/graphviz-conventions.dot +0 -172
- package/vendor/superpowers/skills/writing-skills/persuasion-principles.md +0 -187
- package/vendor/superpowers/skills/writing-skills/render-graphs.js +0 -168
- package/vendor/superpowers/skills/writing-skills/testing-skills-with-subagents.md +0 -384
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Host presence detection for the CLI setup wizard: which tools are installed
|
|
2
|
+
// (CLI on PATH or home config marker — never a subprocess probe) and which of
|
|
3
|
+
// the wizard-managed hosts already carry a workit registration. The configured
|
|
4
|
+
// signal reuses planUninstall's installed bit so detection and uninstall can
|
|
5
|
+
// never disagree; codex/pi are presence-only (their setup runs via cutover).
|
|
6
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { commandOnPath } from "./doctor";
|
|
10
|
+
import { planUninstall, type UninstallPaths } from "./uninstall";
|
|
11
|
+
|
|
12
|
+
export type HostId = "opencode" | "cursor" | "codex" | "pi";
|
|
13
|
+
|
|
14
|
+
export type HostDetection = {
|
|
15
|
+
/** CLI on PATH or a home config marker exists. */
|
|
16
|
+
detected: boolean;
|
|
17
|
+
/** A workit registration is present (opencode/cursor only; always false). */
|
|
18
|
+
configured: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type DetectHostsOptions = UninstallPaths;
|
|
22
|
+
|
|
23
|
+
const HOSTS: HostId[] = ["opencode", "cursor", "codex", "pi"];
|
|
24
|
+
|
|
25
|
+
const HOME_MARKERS: Record<HostId, string[]> = {
|
|
26
|
+
opencode: [path.join(".config", "opencode")],
|
|
27
|
+
cursor: [".cursor"],
|
|
28
|
+
codex: [".codex"],
|
|
29
|
+
pi: [".pi"],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** Hosts the wizard Apply path registers (codex/pi set up via cutover). */
|
|
33
|
+
export const WIZARD_HOSTS: HostId[] = ["opencode", "cursor"];
|
|
34
|
+
|
|
35
|
+
/** Detected hosts the wizard can preselect (presence ∩ wizard-managed). */
|
|
36
|
+
export function preselectedPlatforms(detection: Record<HostId, HostDetection>): string[] {
|
|
37
|
+
return WIZARD_HOSTS.filter((host) => detection[host].detected);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function emptyDetection(): Record<HostId, HostDetection> {
|
|
41
|
+
return {
|
|
42
|
+
opencode: { detected: false, configured: false },
|
|
43
|
+
cursor: { detected: false, configured: false },
|
|
44
|
+
codex: { detected: false, configured: false },
|
|
45
|
+
pi: { detected: false, configured: false },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function detectHosts(options: DetectHostsOptions = {}): Record<HostId, HostDetection> {
|
|
50
|
+
const env = options.env ?? process.env;
|
|
51
|
+
// Same chain as uninstall path resolution: explicit home > env.HOME > homedir.
|
|
52
|
+
const home = options.home ?? env.HOME ?? os.homedir();
|
|
53
|
+
// Pure reader: classifies the installed state, never writes.
|
|
54
|
+
const plan = planUninstall(options);
|
|
55
|
+
const configuredByHost = new Map(plan.hosts.map((h) => [h.host, h.installed]));
|
|
56
|
+
const found = emptyDetection();
|
|
57
|
+
for (const host of HOSTS) {
|
|
58
|
+
const detected =
|
|
59
|
+
cliFound(host, env, home) || HOME_MARKERS[host].some((m) => existsSync(path.join(home, m)));
|
|
60
|
+
found[host] = {
|
|
61
|
+
detected,
|
|
62
|
+
configured: WIZARD_HOSTS.includes(host) && (configuredByHost.get(host) ?? false),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return found;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* CLI lookup beyond ambient PATH: version-manager shims (fnm multishells,
|
|
70
|
+
* nvm, asdf) and ~/.local/bin vanish from bare non-interactive PATHs, so a
|
|
71
|
+
* tool installed under one is still present. Presence-only statSync reads —
|
|
72
|
+
* never a subprocess probe.
|
|
73
|
+
*/
|
|
74
|
+
export function cliFound(name: string, env: NodeJS.ProcessEnv, home: string): boolean {
|
|
75
|
+
if (commandOnPath(name, env)) return true;
|
|
76
|
+
const extra = managerBinDirs(home);
|
|
77
|
+
if (extra.length === 0) return false;
|
|
78
|
+
return commandOnPath(name, {
|
|
79
|
+
...env,
|
|
80
|
+
PATH: [...(env.PATH ?? "").split(path.delimiter), ...extra].join(path.delimiter),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const managerBinDirs = (home: string): string[] => {
|
|
85
|
+
const dirs = [path.join(home, ".local", "bin"), path.join(home, ".asdf", "shims")];
|
|
86
|
+
// One-level layouts: fnm node-versions/<v>/installation/bin, nvm node/<v>/bin.
|
|
87
|
+
const versioned: Array<[base: string, tail: string]> = [
|
|
88
|
+
[path.join(home, ".local", "share", "fnm", "node-versions"), path.join("installation", "bin")],
|
|
89
|
+
[path.join(home, ".nvm", "versions", "node"), "bin"],
|
|
90
|
+
];
|
|
91
|
+
for (const [base, tail] of versioned) {
|
|
92
|
+
let entries: Array<{ name: string; isDirectory: () => boolean }>;
|
|
93
|
+
try {
|
|
94
|
+
entries = readdirSync(base, { withFileTypes: true });
|
|
95
|
+
} catch {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
try {
|
|
100
|
+
if (entry.isDirectory()) dirs.push(path.join(base, entry.name, tail));
|
|
101
|
+
} catch {
|
|
102
|
+
/* keep scanning */
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return dirs;
|
|
107
|
+
};
|
package/src/core/doctor.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
// Shared offline doctor (DG-07/
|
|
1
|
+
// Shared offline doctor (DG-07/CA-09). One host-neutral engine checks the
|
|
2
2
|
// installed Workit surfaces — pins, versions, assets, launchers, runtimes,
|
|
3
3
|
// utilities, registrations, config, workspace match, credential metadata, and
|
|
4
4
|
// log writability — with no network access except the optional registry probe
|
|
5
|
-
// behind the stale-install comparison (CA-04)
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// behind the stale-install comparison (CA-04) and the fail-open identity
|
|
6
|
+
// probes behind github_identity, which fail open. Credential values are never
|
|
7
|
+
// reported: only existence, mode, and a placeholder flag are evaluated, and
|
|
8
|
+
// the identity check reads a token file solely to build an Authorization
|
|
9
|
+
// header for a login lookup — token bytes never enter the report, any fix
|
|
10
|
+
// text, or any log event.
|
|
8
11
|
import { spawnSync } from "node:child_process";
|
|
9
12
|
import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
13
|
import os from "node:os";
|
|
@@ -15,12 +18,21 @@ import { getDiagnosticLogger, isConfigObject } from "./config";
|
|
|
15
18
|
import { packageRoot } from "./package-root";
|
|
16
19
|
import {
|
|
17
20
|
CURSOR_RUNTIME_PACKAGE,
|
|
21
|
+
cursorHookDrift,
|
|
18
22
|
cursorHooksEntry,
|
|
19
23
|
cursorMcpServerEntry,
|
|
20
24
|
isWorkitPlugin,
|
|
21
25
|
} from "./registration";
|
|
22
26
|
import { resolveWorkspaceFrom } from "./workspaces";
|
|
23
|
-
import { validateCursorSkills } from "./skill-manifests";
|
|
27
|
+
import { validateCursorSkills, WORKIT_METHOD_SKILLS } from "./skill-manifests";
|
|
28
|
+
import {
|
|
29
|
+
classifyHostGeneration,
|
|
30
|
+
digestFile,
|
|
31
|
+
readCutoverReceipt,
|
|
32
|
+
readGenerationState,
|
|
33
|
+
type CutoverHost,
|
|
34
|
+
type SessionObservation,
|
|
35
|
+
} from "./cutover";
|
|
24
36
|
|
|
25
37
|
// Mirrors init.ts TOKEN_PLACEHOLDER; kept local so the doctor never needs to
|
|
26
38
|
// import the YouTrack/VCS stack just to label a credential state.
|
|
@@ -31,6 +43,7 @@ export type DoctorHost = "cli" | "opencode" | "cursor";
|
|
|
31
43
|
export type DoctorCheckId =
|
|
32
44
|
| "runtime"
|
|
33
45
|
| "versions"
|
|
46
|
+
| "codex_pin"
|
|
34
47
|
| "assets"
|
|
35
48
|
| "launcher"
|
|
36
49
|
| "utility"
|
|
@@ -41,7 +54,13 @@ export type DoctorCheckId =
|
|
|
41
54
|
| "malformed_config"
|
|
42
55
|
| "workspace_mismatch"
|
|
43
56
|
| "credential_metadata"
|
|
44
|
-
| "
|
|
57
|
+
| "github_identity"
|
|
58
|
+
| "log_writable"
|
|
59
|
+
| "legacy_component"
|
|
60
|
+
| "mixed_generation"
|
|
61
|
+
| "active_old_session"
|
|
62
|
+
| "managed_content_conflict"
|
|
63
|
+
| "missing_v1_component";
|
|
45
64
|
|
|
46
65
|
export type DoctorCheckStatus = "pass" | "warn" | "fail";
|
|
47
66
|
|
|
@@ -89,6 +108,8 @@ export type DoctorOptions = {
|
|
|
89
108
|
env?: NodeJS.ProcessEnv;
|
|
90
109
|
/** Installer run: only registration/config checks count toward exitCode. */
|
|
91
110
|
installer?: boolean;
|
|
111
|
+
/** Observed host sessions used by generation-aware cutover checks. */
|
|
112
|
+
sessions?: SessionObservation[];
|
|
92
113
|
};
|
|
93
114
|
|
|
94
115
|
type Resolved = {
|
|
@@ -104,6 +125,18 @@ type Resolved = {
|
|
|
104
125
|
cursorPluginDir: string;
|
|
105
126
|
env: NodeJS.ProcessEnv;
|
|
106
127
|
installer: boolean;
|
|
128
|
+
sessions: SessionObservation[];
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const parseSessionsFromEnv = (env: NodeJS.ProcessEnv): SessionObservation[] => {
|
|
132
|
+
const raw = env.WORKFLOW_TOOLKIT_SESSIONS;
|
|
133
|
+
if (!raw) return [];
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(raw);
|
|
136
|
+
return Array.isArray(parsed) ? (parsed as SessionObservation[]) : [];
|
|
137
|
+
} catch {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
107
140
|
};
|
|
108
141
|
|
|
109
142
|
const findDevFromCwd = (cwd: string): string | null => {
|
|
@@ -143,6 +176,7 @@ const resolve = (options: DoctorOptions): Resolved => {
|
|
|
143
176
|
options.cursorPluginDir ?? path.join(home, ".cursor", "plugins", "local", "workit"),
|
|
144
177
|
env,
|
|
145
178
|
installer: options.installer ?? false,
|
|
179
|
+
sessions: options.sessions ?? parseSessionsFromEnv(env),
|
|
146
180
|
};
|
|
147
181
|
};
|
|
148
182
|
|
|
@@ -175,7 +209,7 @@ const pluginEntries = (cfg: Record<string, any> | null): string[] => {
|
|
|
175
209
|
return list.map(String).filter(isWorkitPlugin);
|
|
176
210
|
};
|
|
177
211
|
|
|
178
|
-
const commandOnPath = (name: string, env: NodeJS.ProcessEnv): boolean => {
|
|
212
|
+
export const commandOnPath = (name: string, env: NodeJS.ProcessEnv): boolean => {
|
|
179
213
|
const dirs = (env.PATH ?? process.env.PATH ?? "").split(path.delimiter);
|
|
180
214
|
// win32 executables carry an .exe suffix (bun.exe, git.exe), so probe both
|
|
181
215
|
// names — statSync with the bare name would never find them.
|
|
@@ -302,7 +336,9 @@ const checkVersions = (res: Resolved): DoctorCheck => {
|
|
|
302
336
|
problems.push(`adapters pin different core versions: ${[...refs].join(", ")}`);
|
|
303
337
|
}
|
|
304
338
|
const opencodePkg = readJson(path.join(res.dev, "packages/workit-opencode/package.json"));
|
|
305
|
-
const sdk =
|
|
339
|
+
const sdk =
|
|
340
|
+
opencodePkg?.dependencies?.["@opencode-ai/plugin"] ??
|
|
341
|
+
opencodePkg?.devDependencies?.["@opencode-ai/plugin"];
|
|
306
342
|
const sdkVersion = typeof sdk === "string" ? (sdk.match(/^\d+(?:\.\d+){0,2}/) ?? [])[0] : null;
|
|
307
343
|
if (sdkVersion && !semverAtLeast(sdkVersion, SUPPORT_MATRIX.opencode.minimum)) {
|
|
308
344
|
problems.push(
|
|
@@ -324,19 +360,62 @@ const checkVersions = (res: Resolved): DoctorCheck => {
|
|
|
324
360
|
};
|
|
325
361
|
};
|
|
326
362
|
|
|
327
|
-
|
|
363
|
+
// The Codex CLI is a qualification host, not a runtime dependency: evidence
|
|
364
|
+
// covers exactly SUPPORT_MATRIX.codex.cli. A drifted install keeps working,
|
|
365
|
+
// but warns so a fresh install never silently outruns the qualification pin.
|
|
366
|
+
const checkCodexPin = (res: Resolved): DoctorCheck => {
|
|
367
|
+
const qualified = SUPPORT_MATRIX.codex.cli;
|
|
368
|
+
if (!commandOnPath("codex", res.env))
|
|
369
|
+
return {
|
|
370
|
+
id: "codex_pin",
|
|
371
|
+
status: "pass",
|
|
372
|
+
detail: "codex CLI not on PATH — skipping pin check",
|
|
373
|
+
};
|
|
374
|
+
const raw = versionOf("codex", res.env);
|
|
375
|
+
const installed = raw ? ((raw.match(/\d+\.\d+\.\d+/) ?? [])[0] ?? null) : null;
|
|
376
|
+
if (installed === null)
|
|
377
|
+
return {
|
|
378
|
+
id: "codex_pin",
|
|
379
|
+
status: "warn",
|
|
380
|
+
detail: `could not parse the installed codex version — cannot confirm the qualified ${qualified}`,
|
|
381
|
+
fix: `Reinstall the qualified Codex CLI ${qualified} or record fresh qualification evidence`,
|
|
382
|
+
};
|
|
383
|
+
if (installed === qualified)
|
|
384
|
+
return {
|
|
385
|
+
id: "codex_pin",
|
|
386
|
+
status: "pass",
|
|
387
|
+
detail: `codex CLI ${installed} matches the qualified pin`,
|
|
388
|
+
};
|
|
389
|
+
return {
|
|
390
|
+
id: "codex_pin",
|
|
391
|
+
status: "warn",
|
|
392
|
+
detail: `codex CLI ${installed} differs from the qualified ${qualified}`,
|
|
393
|
+
fix: `Reinstall the qualified Codex CLI ${qualified} or record fresh qualification evidence`,
|
|
394
|
+
};
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const detectDevGeneration = (dev: string): "legacy" | "v1" =>
|
|
398
|
+
existsSync(path.join(dev, "packages/workit-opencode/assets/skills/workit-plan/SKILL.md"))
|
|
399
|
+
? "v1"
|
|
400
|
+
: "legacy";
|
|
401
|
+
|
|
402
|
+
const effectiveGeneration = (res: Resolved): "legacy" | "v1" => {
|
|
403
|
+
const configured = readGenerationState(res.configDir).target;
|
|
404
|
+
if (configured === "v1") return "v1";
|
|
405
|
+
if (res.dev && detectDevGeneration(res.dev) === "v1") return "v1";
|
|
406
|
+
return "legacy";
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const assetPathsFor = (host: DoctorHost, dev: string, _generation: "legacy" | "v1"): string[] => {
|
|
328
410
|
const pkg = path.join(dev, "packages", `workit-${host}`);
|
|
329
411
|
switch (host) {
|
|
330
412
|
case "opencode":
|
|
331
|
-
return
|
|
332
|
-
path.join(pkg, "assets", "
|
|
333
|
-
|
|
334
|
-
path.join(pkg, "assets", "templates", "spec-template.md"),
|
|
335
|
-
path.join(pkg, "assets", "vendor", "superpowers", "skills", "brainstorming", "SKILL.md"),
|
|
336
|
-
];
|
|
413
|
+
return WORKIT_METHOD_SKILLS.map((skill) =>
|
|
414
|
+
path.join(pkg, "assets", "skills", skill, "SKILL.md"),
|
|
415
|
+
);
|
|
337
416
|
case "cursor":
|
|
338
417
|
return [
|
|
339
|
-
path.join(pkg, "assets", "templates", "
|
|
418
|
+
path.join(pkg, "assets", "templates", "workit-contract.md"),
|
|
340
419
|
path.join(pkg, "mcp.json"),
|
|
341
420
|
path.join(pkg, ".cursor-plugin"),
|
|
342
421
|
];
|
|
@@ -352,15 +431,16 @@ const hostsFor = (host: DoctorHost): DoctorHost[] =>
|
|
|
352
431
|
|
|
353
432
|
const checkAssets = (res: Resolved): DoctorCheck => {
|
|
354
433
|
const dev = res.dev;
|
|
434
|
+
const generation = effectiveGeneration(res);
|
|
355
435
|
const missing = dev
|
|
356
436
|
? hostsFor(res.host).flatMap((h) =>
|
|
357
|
-
assetPathsFor(h, dev)
|
|
437
|
+
assetPathsFor(h, dev, generation)
|
|
358
438
|
.filter((p) => !existsSync(p))
|
|
359
439
|
.map((p) => `${h}: ${p}`),
|
|
360
440
|
)
|
|
361
441
|
: [];
|
|
362
442
|
if (res.host === "cursor" || res.host === "cli") {
|
|
363
|
-
const cursorError = validateCursorSkills(res.cursorPluginDir);
|
|
443
|
+
const cursorError = validateCursorSkills(res.cursorPluginDir, WORKIT_METHOD_SKILLS);
|
|
364
444
|
if (cursorError) missing.push(`cursor: ${cursorError}`);
|
|
365
445
|
}
|
|
366
446
|
if (missing.length === 0) {
|
|
@@ -740,6 +820,18 @@ const checkStaleInstall = (res: Resolved): DoctorCheck & { registryProbed?: bool
|
|
|
740
820
|
fix: "Re-run install-cursor-plugin.sh — it rewrites the sessionStart hook to the canonical @latest selector",
|
|
741
821
|
};
|
|
742
822
|
}
|
|
823
|
+
// Enforcement-event drift: a present-but-divergent preToolUse matcher (or
|
|
824
|
+
// beforeShellExecution command) silently narrows what the hook intercepts.
|
|
825
|
+
// Absent events are filled by the installer merge, so only divergence fails.
|
|
826
|
+
const hookDrift = existsSync(hooksFile) ? cursorHookDrift(readJson(hooksFile)) : [];
|
|
827
|
+
if (hookDrift.length > 0) {
|
|
828
|
+
return {
|
|
829
|
+
id: "stale_install",
|
|
830
|
+
status: "fail",
|
|
831
|
+
detail: `stale_install: cursor hook drift on ${hookDrift.join(", ")} (differs from the canonical hook entries)`,
|
|
832
|
+
fix: "Re-run install-cursor-plugin.sh — it rewrites the workit hook entries to canonical",
|
|
833
|
+
};
|
|
834
|
+
}
|
|
743
835
|
const installed = installedPluginVersion(res);
|
|
744
836
|
const source = res.dev
|
|
745
837
|
? ((readJson(path.join(res.dev, "packages/workit-cursor/package.json"))?.version as
|
|
@@ -988,6 +1080,150 @@ const checkCredentialMetadata = (res: Resolved): DoctorCheck => {
|
|
|
988
1080
|
};
|
|
989
1081
|
};
|
|
990
1082
|
|
|
1083
|
+
// Identity surfaces for the github_identity check. Logins are public
|
|
1084
|
+
// usernames (safe to report); the token itself only ever travels as an
|
|
1085
|
+
// Authorization header value and never enters details, fixes, or logs.
|
|
1086
|
+
export type IdentitySurface = { surface: string; login: string | null };
|
|
1087
|
+
|
|
1088
|
+
export type IdentityProbes = {
|
|
1089
|
+
origin: (cwd: string) => string | null;
|
|
1090
|
+
ghLogin: (env: NodeJS.ProcessEnv) => string | null;
|
|
1091
|
+
tokenLogin: (tokenFile: string, host: string) => string | null;
|
|
1092
|
+
sshLogin: (host: string) => string | null;
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
export const githubHostFromRemote = (remote: string): string | null => {
|
|
1096
|
+
const rest = (remote || "").trim();
|
|
1097
|
+
if (!rest) return null;
|
|
1098
|
+
const scp = /^[^@/]+@([^:]+):/.exec(rest);
|
|
1099
|
+
if (scp) return scp[1].toLowerCase();
|
|
1100
|
+
try {
|
|
1101
|
+
const url = new URL(rest.includes("://") ? rest : `https://${rest}`);
|
|
1102
|
+
return url.hostname.toLowerCase() || null;
|
|
1103
|
+
} catch {
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
export const githubIdentityFinding = (surfaces: IdentitySurface[]): DoctorCheck => {
|
|
1109
|
+
const resolved = surfaces.filter(
|
|
1110
|
+
(entry): entry is { surface: string; login: string } => typeof entry.login === "string",
|
|
1111
|
+
);
|
|
1112
|
+
const distinct = [...new Set(resolved.map((entry) => entry.login.toLowerCase()))];
|
|
1113
|
+
if (distinct.length < 2) {
|
|
1114
|
+
return {
|
|
1115
|
+
id: "github_identity",
|
|
1116
|
+
status: "pass",
|
|
1117
|
+
detail:
|
|
1118
|
+
resolved.length === 0
|
|
1119
|
+
? "no GitHub identities configured"
|
|
1120
|
+
: "single GitHub identity across configured surfaces",
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
return {
|
|
1124
|
+
id: "github_identity",
|
|
1125
|
+
status: "warn",
|
|
1126
|
+
detail: `GitHub identities disagree across configured surfaces: ${resolved
|
|
1127
|
+
.map((entry) => `${entry.surface} reports "${entry.login}"`)
|
|
1128
|
+
.join("; ")}. Operations may land under the wrong account.`,
|
|
1129
|
+
fix: "Align every surface on the intended account via your own git/gh configuration (gh auth login/switch plus the vcs token file at mode 0600) — workit never hardcodes accounts",
|
|
1130
|
+
};
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
const defaultIdentityProbes = (env: NodeJS.ProcessEnv): IdentityProbes => ({
|
|
1134
|
+
origin: (cwd) => {
|
|
1135
|
+
try {
|
|
1136
|
+
const out = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
1137
|
+
cwd,
|
|
1138
|
+
encoding: "utf8",
|
|
1139
|
+
env,
|
|
1140
|
+
});
|
|
1141
|
+
return out.status === 0 ? (out.stdout ?? "").trim() || null : null;
|
|
1142
|
+
} catch {
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
},
|
|
1146
|
+
ghLogin: (e) => {
|
|
1147
|
+
try {
|
|
1148
|
+
const out = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
|
|
1149
|
+
const login = (out.stdout ?? "").trim();
|
|
1150
|
+
return out.status === 0 && login ? login : null;
|
|
1151
|
+
} catch {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
tokenLogin: (tokenFile, host) => {
|
|
1156
|
+
let token = "";
|
|
1157
|
+
try {
|
|
1158
|
+
token = readFileSync(tokenFile, "utf8").trim();
|
|
1159
|
+
} catch {
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
if (!token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) return null;
|
|
1163
|
+
try {
|
|
1164
|
+
const out = spawnSync("gh", ["api", "user", "--jq", ".login"], {
|
|
1165
|
+
encoding: "utf8",
|
|
1166
|
+
env: { ...env, GH_TOKEN: token, ...(host === "github.com" ? {} : { GH_HOST: host }) },
|
|
1167
|
+
});
|
|
1168
|
+
const login = (out.stdout ?? "").trim();
|
|
1169
|
+
return out.status === 0 && login ? login : null;
|
|
1170
|
+
} catch {
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
sshLogin: (host) => {
|
|
1175
|
+
try {
|
|
1176
|
+
const out = spawnSync(
|
|
1177
|
+
"ssh",
|
|
1178
|
+
["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`],
|
|
1179
|
+
{
|
|
1180
|
+
encoding: "utf8",
|
|
1181
|
+
env,
|
|
1182
|
+
},
|
|
1183
|
+
);
|
|
1184
|
+
const text = `${out.stdout ?? ""}${out.stderr ?? ""}`;
|
|
1185
|
+
const match = /^Hi ([^!]+)!/.exec(text);
|
|
1186
|
+
return match ? match[1].trim() || null : null;
|
|
1187
|
+
} catch {
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
},
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
const githubTokenFile = (res: Pick<Resolved, "configDir">): string | null => {
|
|
1194
|
+
const vcsJson = readJson(path.join(res.configDir, "vcs.json"));
|
|
1195
|
+
const provider = vcsJson?.github;
|
|
1196
|
+
if (provider && typeof provider === "object") {
|
|
1197
|
+
const tokenFile = (provider as Record<string, unknown>).tokenFile;
|
|
1198
|
+
const raw =
|
|
1199
|
+
typeof tokenFile === "string" ? tokenFile : path.join(res.configDir, "github.token");
|
|
1200
|
+
return path.isAbsolute(raw) ? raw : path.resolve(res.configDir, raw);
|
|
1201
|
+
}
|
|
1202
|
+
if (!vcsJson) {
|
|
1203
|
+
const fallback = path.join(res.configDir, "github.token");
|
|
1204
|
+
return existsSync(fallback) ? fallback : null;
|
|
1205
|
+
}
|
|
1206
|
+
return null;
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
export const checkGithubIdentity = (
|
|
1210
|
+
res: Pick<Resolved, "cwd" | "configDir" | "env">,
|
|
1211
|
+
probes: IdentityProbes = defaultIdentityProbes(res.env),
|
|
1212
|
+
): DoctorCheck => {
|
|
1213
|
+
const host = githubHostFromRemote(probes.origin(res.cwd) ?? "");
|
|
1214
|
+
if (!host)
|
|
1215
|
+
return { id: "github_identity", status: "pass", detail: "no GitHub remote configured" };
|
|
1216
|
+
const surfaces: IdentitySurface[] = [{ surface: "gh CLI", login: probes.ghLogin(res.env) }];
|
|
1217
|
+
const tokenFile = githubTokenFile(res);
|
|
1218
|
+
if (tokenFile)
|
|
1219
|
+
surfaces.push({
|
|
1220
|
+
surface: `token file ${tokenFile}`,
|
|
1221
|
+
login: probes.tokenLogin(tokenFile, host),
|
|
1222
|
+
});
|
|
1223
|
+
surfaces.push({ surface: "SSH", login: probes.sshLogin(host) });
|
|
1224
|
+
return githubIdentityFinding(surfaces);
|
|
1225
|
+
};
|
|
1226
|
+
|
|
991
1227
|
const checkLogWritable = (res: Resolved): DoctorCheck => {
|
|
992
1228
|
const logsDir = path.join(res.stateDir, "logs");
|
|
993
1229
|
// Fixed-name probe: even a killed process leaves at most one bounded file that
|
|
@@ -1017,9 +1253,177 @@ const checkLogWritable = (res: Resolved): DoctorCheck => {
|
|
|
1017
1253
|
}
|
|
1018
1254
|
};
|
|
1019
1255
|
|
|
1256
|
+
const GENERATION_HOSTS: CutoverHost[] = ["opencode", "cursor", "codex", "pi"];
|
|
1257
|
+
|
|
1258
|
+
const generationPaths = (res: Resolved) => ({
|
|
1259
|
+
home: res.home,
|
|
1260
|
+
configDir: res.configDir,
|
|
1261
|
+
stateDir: res.stateDir,
|
|
1262
|
+
dev: res.dev,
|
|
1263
|
+
workspace: res.cwd,
|
|
1264
|
+
opencodeConfig: res.opencodeConfig,
|
|
1265
|
+
cursorSettings: res.cursorSettings,
|
|
1266
|
+
cursorMcp: res.cursorMcp,
|
|
1267
|
+
cursorPluginDir: res.cursorPluginDir,
|
|
1268
|
+
sessions: res.sessions,
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
const checkMixedGeneration = (res: Resolved): DoctorCheck => {
|
|
1272
|
+
const paths = generationPaths(res);
|
|
1273
|
+
const mixed = GENERATION_HOSTS.filter((host) => classifyHostGeneration(host, paths) === "mixed");
|
|
1274
|
+
const target = readGenerationState(res.configDir).target;
|
|
1275
|
+
if (mixed.length === 0) {
|
|
1276
|
+
return {
|
|
1277
|
+
id: "mixed_generation",
|
|
1278
|
+
status: "pass",
|
|
1279
|
+
detail: "no mixed legacy/v1 components detected",
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
const detail = `mixed legacy and v1 components: ${mixed.join(", ")}`;
|
|
1283
|
+
if (target === "v1") {
|
|
1284
|
+
return {
|
|
1285
|
+
id: "mixed_generation",
|
|
1286
|
+
status: "fail",
|
|
1287
|
+
detail,
|
|
1288
|
+
fix: "Run the approved v1 cutover preview/apply or remove the conflicting generation",
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
return {
|
|
1292
|
+
id: "mixed_generation",
|
|
1293
|
+
status: "warn",
|
|
1294
|
+
detail: `${detail} (legacy target — cutover required before v1 activation)`,
|
|
1295
|
+
fix: "Preview cutover with `workit init` or the CLI cutover flow before activating v1",
|
|
1296
|
+
};
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1299
|
+
const checkLegacyComponent = (res: Resolved): DoctorCheck => {
|
|
1300
|
+
const target = readGenerationState(res.configDir).target;
|
|
1301
|
+
if (target !== "v1") {
|
|
1302
|
+
return {
|
|
1303
|
+
id: "legacy_component",
|
|
1304
|
+
status: "pass",
|
|
1305
|
+
detail: "legacy target — 0.x components expected",
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
const paths = generationPaths(res);
|
|
1309
|
+
const legacy = GENERATION_HOSTS.filter(
|
|
1310
|
+
(host) => classifyHostGeneration(host, paths) === "legacy",
|
|
1311
|
+
);
|
|
1312
|
+
if (legacy.length === 0) {
|
|
1313
|
+
return {
|
|
1314
|
+
id: "legacy_component",
|
|
1315
|
+
status: "pass",
|
|
1316
|
+
detail: "no legacy-only host components on v1 target",
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
return {
|
|
1320
|
+
id: "legacy_component",
|
|
1321
|
+
status: "fail",
|
|
1322
|
+
detail: `legacy components remain on: ${legacy.join(", ")}`,
|
|
1323
|
+
fix: "Complete the approved cutover or rollback to legacy before mixing generations",
|
|
1324
|
+
};
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
const checkMissingV1Component = (res: Resolved): DoctorCheck => {
|
|
1328
|
+
const target = readGenerationState(res.configDir).target;
|
|
1329
|
+
if (target !== "v1") {
|
|
1330
|
+
return { id: "missing_v1_component", status: "pass", detail: "legacy target" };
|
|
1331
|
+
}
|
|
1332
|
+
const paths = generationPaths(res);
|
|
1333
|
+
const missing = GENERATION_HOSTS.filter((host) => {
|
|
1334
|
+
const gen = classifyHostGeneration(host, paths);
|
|
1335
|
+
return gen === "none" || gen === "legacy";
|
|
1336
|
+
});
|
|
1337
|
+
if (missing.length === 0) {
|
|
1338
|
+
return {
|
|
1339
|
+
id: "missing_v1_component",
|
|
1340
|
+
status: "pass",
|
|
1341
|
+
detail: "v1 components present on selected hosts",
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
return {
|
|
1345
|
+
id: "missing_v1_component",
|
|
1346
|
+
status: "fail",
|
|
1347
|
+
detail: `missing v1 components on: ${missing.join(", ")}`,
|
|
1348
|
+
fix: "Re-run the cutover apply step or the host install script for the missing target",
|
|
1349
|
+
};
|
|
1350
|
+
};
|
|
1351
|
+
|
|
1352
|
+
const checkActiveOldSession = (res: Resolved): DoctorCheck => {
|
|
1353
|
+
if (res.sessions.length === 0) {
|
|
1354
|
+
return {
|
|
1355
|
+
id: "active_old_session",
|
|
1356
|
+
status: "pass",
|
|
1357
|
+
detail: "no session observations supplied to doctor",
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
const blocking = res.sessions.filter((s) => s.state === "active" || s.state === "unknown");
|
|
1361
|
+
if (blocking.length === 0) {
|
|
1362
|
+
return {
|
|
1363
|
+
id: "active_old_session",
|
|
1364
|
+
status: "pass",
|
|
1365
|
+
detail: "no active or unknown old sessions observed",
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
const detail = blocking.map((s) => `${s.host}:${s.handle} (${s.state})`).join(", ");
|
|
1369
|
+
const target = readGenerationState(res.configDir).target;
|
|
1370
|
+
if (target === "v1") {
|
|
1371
|
+
return {
|
|
1372
|
+
id: "active_old_session",
|
|
1373
|
+
status: "fail",
|
|
1374
|
+
detail: `active or unknown sessions block v1 activation: ${detail}`,
|
|
1375
|
+
fix: "Stop or account for old sessions before cutover apply",
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
return {
|
|
1379
|
+
id: "active_old_session",
|
|
1380
|
+
status: "warn",
|
|
1381
|
+
detail: `old sessions still observed: ${detail}`,
|
|
1382
|
+
fix: "Stop old sessions before previewing or applying v1 cutover",
|
|
1383
|
+
};
|
|
1384
|
+
};
|
|
1385
|
+
|
|
1386
|
+
const checkManagedContentConflict = (res: Resolved): DoctorCheck => {
|
|
1387
|
+
const gen = readGenerationState(res.configDir);
|
|
1388
|
+
if (!gen.cutover?.backupId) {
|
|
1389
|
+
return {
|
|
1390
|
+
id: "managed_content_conflict",
|
|
1391
|
+
status: "pass",
|
|
1392
|
+
detail: "no cutover receipt to compare",
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
const receipt = readCutoverReceipt(gen.cutover.backupId, res.stateDir);
|
|
1396
|
+
if (!receipt) {
|
|
1397
|
+
return {
|
|
1398
|
+
id: "managed_content_conflict",
|
|
1399
|
+
status: "fail",
|
|
1400
|
+
detail: `cutover receipt missing for backup ${gen.cutover.backupId}`,
|
|
1401
|
+
fix: "Re-run cutover apply or rollback to a known state",
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
const conflicts = receipt.managedFiles.filter((entry) => {
|
|
1405
|
+
const current = digestFile(entry.path);
|
|
1406
|
+
return current !== null && current !== entry.installedDigest;
|
|
1407
|
+
});
|
|
1408
|
+
if (conflicts.length === 0) {
|
|
1409
|
+
return {
|
|
1410
|
+
id: "managed_content_conflict",
|
|
1411
|
+
status: "pass",
|
|
1412
|
+
detail: "managed content matches cutover receipt",
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
return {
|
|
1416
|
+
id: "managed_content_conflict",
|
|
1417
|
+
status: "fail",
|
|
1418
|
+
detail: `managed content drifted since cutover on: ${conflicts.map((c) => c.path).join(", ")}`,
|
|
1419
|
+
fix: "Reconcile managed files with the cutover receipt or roll back before re-applying",
|
|
1420
|
+
};
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1020
1423
|
const RUN_CHECKS: Array<(res: Resolved) => DoctorCheck> = [
|
|
1021
1424
|
checkRuntime,
|
|
1022
1425
|
checkVersions,
|
|
1426
|
+
checkCodexPin,
|
|
1023
1427
|
checkAssets,
|
|
1024
1428
|
checkLauncher,
|
|
1025
1429
|
checkUtility,
|
|
@@ -1029,7 +1433,13 @@ const RUN_CHECKS: Array<(res: Resolved) => DoctorCheck> = [
|
|
|
1029
1433
|
checkMalformedConfig,
|
|
1030
1434
|
checkWorkspaceMismatch,
|
|
1031
1435
|
checkCredentialMetadata,
|
|
1436
|
+
checkGithubIdentity,
|
|
1032
1437
|
checkLogWritable,
|
|
1438
|
+
checkMixedGeneration,
|
|
1439
|
+
checkLegacyComponent,
|
|
1440
|
+
checkMissingV1Component,
|
|
1441
|
+
checkActiveOldSession,
|
|
1442
|
+
checkManagedContentConflict,
|
|
1033
1443
|
];
|
|
1034
1444
|
|
|
1035
1445
|
// AR-11/CA-40: the installer guarantees the selected host itself — runtime,
|