harnessed 3.8.0 → 3.9.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/dist/cli.mjs +253 -134
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -3,11 +3,12 @@ import { execSync, spawnSync, spawn } from 'child_process';
|
|
|
3
3
|
import { existsSync, mkdirSync, renameSync, writeFileSync, readFileSync, readdirSync } from 'fs';
|
|
4
4
|
import { join, dirname, resolve, relative } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
|
+
import { readFile, readdir, unlink, writeFile, stat, rm, cp, mkdir, access, rename } from 'fs/promises';
|
|
6
7
|
import { Type } from '@sinclair/typebox';
|
|
7
8
|
import { Value } from '@sinclair/typebox/value';
|
|
8
9
|
import { LineCounter, parseDocument, parse, isSeq, isScalar } from 'yaml';
|
|
9
|
-
import { readFile, readdir, unlink, writeFile, stat, rm, cp, mkdir, access, rename } from 'fs/promises';
|
|
10
10
|
import lockfile from 'proper-lockfile';
|
|
11
|
+
import * as p from '@clack/prompts';
|
|
11
12
|
import { Command } from 'commander';
|
|
12
13
|
import { Ajv } from 'ajv';
|
|
13
14
|
import * as ajvFormatsNs from 'ajv-formats';
|
|
@@ -15,7 +16,6 @@ import { fileURLToPath } from 'url';
|
|
|
15
16
|
import { createHash } from 'crypto';
|
|
16
17
|
import { Parser } from 'expr-eval';
|
|
17
18
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
18
|
-
import * as p from '@clack/prompts';
|
|
19
19
|
import { createPatch } from 'diff';
|
|
20
20
|
import pc from 'picocolors';
|
|
21
21
|
import { stdout, stdin } from 'process';
|
|
@@ -119,6 +119,89 @@ var init_harnessedRoot = __esm({
|
|
|
119
119
|
"src/installers/lib/harnessedRoot.ts"() {
|
|
120
120
|
}
|
|
121
121
|
});
|
|
122
|
+
function checkNodeVersion() {
|
|
123
|
+
const v = process.versions.node;
|
|
124
|
+
const major = Number.parseInt(v.split(".")[0] ?? "0", 10);
|
|
125
|
+
return major >= 22 ? { name: "node \u2265 22", status: "pass", message: `node ${v}` } : {
|
|
126
|
+
name: "node \u2265 22",
|
|
127
|
+
status: "fail",
|
|
128
|
+
message: `node ${v} (need \u2265 22)`,
|
|
129
|
+
fix: "nvm install 22 && nvm use 22"
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async function checkMcpScope() {
|
|
133
|
+
const projectMcp = join(process.cwd(), ".mcp.json");
|
|
134
|
+
const userClaude = join(homedir(), ".claude.json");
|
|
135
|
+
let projectExists = false;
|
|
136
|
+
try {
|
|
137
|
+
await readFile(projectMcp, "utf8");
|
|
138
|
+
projectExists = true;
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
let userHasMcp = false;
|
|
142
|
+
try {
|
|
143
|
+
const raw = await readFile(userClaude, "utf8");
|
|
144
|
+
const parsed = JSON.parse(raw);
|
|
145
|
+
userHasMcp = !!parsed.mcpServers && Object.keys(parsed.mcpServers).length > 0;
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
if (userHasMcp) {
|
|
149
|
+
return {
|
|
150
|
+
name: "mcp scope = project",
|
|
151
|
+
status: "fail",
|
|
152
|
+
message: `~/.claude.json has user-scope mcpServers (CC #54803 risk)`,
|
|
153
|
+
fix: "remove user-scope entries; re-add via `claude mcp add --scope project ...`"
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
name: "mcp scope = project",
|
|
158
|
+
status: "pass",
|
|
159
|
+
message: projectExists ? "project .mcp.json present" : "no MCP servers installed"
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function checkJq() {
|
|
163
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
164
|
+
const r = spawnSync(finder, ["jq"], { encoding: "utf8" });
|
|
165
|
+
if (r.status === 0 && r.stdout.trim().length > 0) {
|
|
166
|
+
return {
|
|
167
|
+
name: "jq present",
|
|
168
|
+
status: "pass",
|
|
169
|
+
message: r.stdout.split(/\r?\n/)[0]?.trim() ?? "jq found"
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const fix = process.platform === "win32" ? "winget install jqlang.jq (or: scoop install jq)" : process.platform === "darwin" ? "brew install jq" : "apt-get install jq (or: dnf install jq)";
|
|
173
|
+
return { name: "jq present", status: "fail", message: "jq not found in PATH", fix };
|
|
174
|
+
}
|
|
175
|
+
function checkWinBash() {
|
|
176
|
+
if (process.platform !== "win32") {
|
|
177
|
+
return { name: "bash flavor (win)", status: "pass", message: "skipped (non-Windows)" };
|
|
178
|
+
}
|
|
179
|
+
const where = spawnSync("where", ["bash"], { encoding: "utf8" });
|
|
180
|
+
const firstBash = (where.stdout ?? "").split(/\r?\n/)[0]?.trim() ?? "(not found)";
|
|
181
|
+
if (where.status !== 0 || !firstBash || firstBash === "(not found)") {
|
|
182
|
+
return {
|
|
183
|
+
name: "bash flavor (win)",
|
|
184
|
+
status: "fail",
|
|
185
|
+
message: "no bash on PATH",
|
|
186
|
+
fix: "install Git for Windows (Git Bash) and ensure it is on PATH"
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const probe = spawnSync("bash", ["-c", "echo $WSL_DISTRO_NAME"], { encoding: "utf8" });
|
|
190
|
+
const distro = (probe.stdout ?? "").trim();
|
|
191
|
+
if (distro.length > 0) {
|
|
192
|
+
return {
|
|
193
|
+
name: "bash flavor (win)",
|
|
194
|
+
status: "fail",
|
|
195
|
+
message: `WSL bash (${distro}) \u2014 ralph-loop subagent fork breaks under WSL`,
|
|
196
|
+
fix: "reorder PATH so Git Bash precedes WSL bash.exe (Settings \u2192 System \u2192 Environment Variables)"
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return { name: "bash flavor (win)", status: "pass", message: `${firstBash} (Git Bash / native)` };
|
|
200
|
+
}
|
|
201
|
+
var init_check_builtin = __esm({
|
|
202
|
+
"src/cli/lib/check-builtin.ts"() {
|
|
203
|
+
}
|
|
204
|
+
});
|
|
122
205
|
|
|
123
206
|
// src/cli/lib/probe-gstack.ts
|
|
124
207
|
var probe_gstack_exports = {};
|
|
@@ -604,21 +687,27 @@ async function checkPlanningWithFiles() {
|
|
|
604
687
|
name: "planning-with-files plugin",
|
|
605
688
|
status: "warn",
|
|
606
689
|
message: "plugin directory exists but no version subdir found",
|
|
607
|
-
fix: REMEDIATION
|
|
690
|
+
fix: REMEDIATION,
|
|
691
|
+
install_commands: INSTALL_COMMANDS
|
|
608
692
|
};
|
|
609
693
|
} catch {
|
|
610
694
|
return {
|
|
611
695
|
name: "planning-with-files plugin",
|
|
612
696
|
status: "warn",
|
|
613
697
|
message: "not installed (plugin cache path missing)",
|
|
614
|
-
fix: REMEDIATION
|
|
698
|
+
fix: REMEDIATION,
|
|
699
|
+
install_commands: INSTALL_COMMANDS
|
|
615
700
|
};
|
|
616
701
|
}
|
|
617
702
|
}
|
|
618
|
-
var REMEDIATION;
|
|
703
|
+
var REMEDIATION, INSTALL_COMMANDS;
|
|
619
704
|
var init_check_planning_with_files = __esm({
|
|
620
705
|
"src/cli/lib/check-planning-with-files.ts"() {
|
|
621
|
-
REMEDIATION = "install via
|
|
706
|
+
REMEDIATION = "install via `claude plugin marketplace add OthmanAdi/planning-with-files && claude plugin install planning-with-files` (requires >=2.2.0 per R20.15 + D-15)";
|
|
707
|
+
INSTALL_COMMANDS = [
|
|
708
|
+
"claude plugin marketplace add OthmanAdi/planning-with-files",
|
|
709
|
+
"claude plugin install planning-with-files"
|
|
710
|
+
];
|
|
622
711
|
}
|
|
623
712
|
});
|
|
624
713
|
|
|
@@ -662,13 +751,15 @@ async function checkMattpocockSkills() {
|
|
|
662
751
|
name: "mattpocock-skills",
|
|
663
752
|
status: "warn",
|
|
664
753
|
message: "not installed (plugin cache + user-skill paths both missing)",
|
|
665
|
-
fix: REMEDIATION2
|
|
754
|
+
fix: REMEDIATION2,
|
|
755
|
+
install_commands: INSTALL_COMMANDS2
|
|
666
756
|
};
|
|
667
757
|
}
|
|
668
|
-
var REMEDIATION2;
|
|
758
|
+
var REMEDIATION2, INSTALL_COMMANDS2;
|
|
669
759
|
var init_check_mattpocock_skills = __esm({
|
|
670
760
|
"src/cli/lib/check-mattpocock-skills.ts"() {
|
|
671
|
-
REMEDIATION2 = "install via
|
|
761
|
+
REMEDIATION2 = "install via `npx skills@latest add mattpocock/skills` (or git clone https://github.com/mattpocock/skills ~/.claude/skills/mattpocock-skills); methodology fallback already inline in role-prompts.yaml per v3.6.0 Phase 1 \u2014 install is optional but enables /grill-with-docs /zoom-out etc. SlashCommand acceleration";
|
|
762
|
+
INSTALL_COMMANDS2 = ["npx skills@latest add mattpocock/skills"];
|
|
672
763
|
}
|
|
673
764
|
});
|
|
674
765
|
|
|
@@ -699,25 +790,64 @@ async function checkMcpAvailability() {
|
|
|
699
790
|
message: `all 3 installed: ${installed.join(", ")}`
|
|
700
791
|
};
|
|
701
792
|
}
|
|
793
|
+
const installCommands = missing.map((s) => SERVER_INSTALL_COMMANDS[s]);
|
|
702
794
|
if (installed.length === 0) {
|
|
703
795
|
return {
|
|
704
796
|
name: "MCP servers (tavily/exa/chrome-devtools)",
|
|
705
797
|
status: "warn",
|
|
706
798
|
message: "none of 3 target MCP servers installed in ~/.claude/settings.json",
|
|
707
|
-
fix: "install via
|
|
799
|
+
fix: "install via per-server transport-specific command (see install_commands); harnessed routes web-search to tavily/exa per workflows/judgments/web-search-routing.yaml \u2014 without them, falls back to WebFetch/WebSearch built-in (degraded but functional)",
|
|
800
|
+
install_commands: installCommands
|
|
708
801
|
};
|
|
709
802
|
}
|
|
710
803
|
return {
|
|
711
804
|
name: "MCP servers (tavily/exa/chrome-devtools)",
|
|
712
805
|
status: "warn",
|
|
713
806
|
message: `${installed.length}/3 installed: ${installed.join(", ")}; missing: ${missing.join(", ")}`,
|
|
714
|
-
fix: `install missing via
|
|
807
|
+
fix: `install missing via per-server command (see install_commands): ${missing.join(", ")}`,
|
|
808
|
+
install_commands: installCommands
|
|
715
809
|
};
|
|
716
810
|
}
|
|
717
|
-
var TARGET_SERVERS;
|
|
811
|
+
var TARGET_SERVERS, SERVER_INSTALL_COMMANDS;
|
|
718
812
|
var init_check_mcp_availability = __esm({
|
|
719
813
|
"src/cli/lib/check-mcp-availability.ts"() {
|
|
720
814
|
TARGET_SERVERS = ["tavily-mcp", "exa-mcp", "chrome-devtools"];
|
|
815
|
+
SERVER_INSTALL_COMMANDS = {
|
|
816
|
+
"tavily-mcp": "claude mcp add tavily-remote-mcp --transport http https://mcp.tavily.com/mcp/",
|
|
817
|
+
"exa-mcp": "claude mcp add --transport http exa https://mcp.exa.ai/mcp",
|
|
818
|
+
// chrome-devtools: empirical-pending — assumed npx until dogfood confirmation.
|
|
819
|
+
"chrome-devtools": "npx chrome-devtools-mcp@latest"
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
// src/cli/lib/doctor-registry.ts
|
|
825
|
+
var CHECKS;
|
|
826
|
+
var init_doctor_registry = __esm({
|
|
827
|
+
"src/cli/lib/doctor-registry.ts"() {
|
|
828
|
+
init_check_builtin();
|
|
829
|
+
CHECKS = [
|
|
830
|
+
async () => checkNodeVersion(),
|
|
831
|
+
checkMcpScope,
|
|
832
|
+
async () => checkJq(),
|
|
833
|
+
async () => checkWinBash(),
|
|
834
|
+
async () => {
|
|
835
|
+
const { checkOrigin: checkOrigin2 } = await Promise.resolve().then(() => (init_origin_check(), origin_check_exports));
|
|
836
|
+
const r = checkOrigin2(process.cwd(), { allowFork: true });
|
|
837
|
+
return { name: "origin URL", status: r.status, message: r.detail, fix: r.fix };
|
|
838
|
+
},
|
|
839
|
+
async () => {
|
|
840
|
+
const { probeGstackPrefix: probeGstackPrefix2 } = await Promise.resolve().then(() => (init_probe_gstack(), probe_gstack_exports));
|
|
841
|
+
const r = probeGstackPrefix2();
|
|
842
|
+
return { name: "gstack prefix", status: r.status, message: r.detail, fix: r.fix };
|
|
843
|
+
},
|
|
844
|
+
async () => (await Promise.resolve().then(() => (init_check_deprecations(), check_deprecations_exports))).checkDeprecations(),
|
|
845
|
+
async () => (await Promise.resolve().then(() => (init_check_token_budget(), check_token_budget_exports))).checkTokenBudget(),
|
|
846
|
+
async () => (await Promise.resolve().then(() => (init_check_agent_teams_doctor(), check_agent_teams_doctor_exports))).checkAgentTeamsDoctor(),
|
|
847
|
+
async () => (await Promise.resolve().then(() => (init_check_planning_with_files(), check_planning_with_files_exports))).checkPlanningWithFiles(),
|
|
848
|
+
async () => (await Promise.resolve().then(() => (init_check_mattpocock_skills(), check_mattpocock_skills_exports))).checkMattpocockSkills(),
|
|
849
|
+
async () => (await Promise.resolve().then(() => (init_check_mcp_availability(), check_mcp_availability_exports))).checkMcpAvailability()
|
|
850
|
+
];
|
|
721
851
|
}
|
|
722
852
|
});
|
|
723
853
|
function statePath() {
|
|
@@ -950,9 +1080,91 @@ var init_resume = __esm({
|
|
|
950
1080
|
}
|
|
951
1081
|
});
|
|
952
1082
|
|
|
1083
|
+
// src/cli/lib/auto-install.ts
|
|
1084
|
+
var auto_install_exports = {};
|
|
1085
|
+
__export(auto_install_exports, {
|
|
1086
|
+
runAutoInstall: () => runAutoInstall
|
|
1087
|
+
});
|
|
1088
|
+
async function runAutoInstall(opts) {
|
|
1089
|
+
const out = { installed: [], skipped: [], failed: [] };
|
|
1090
|
+
if (!opts.autoInstall) {
|
|
1091
|
+
return out;
|
|
1092
|
+
}
|
|
1093
|
+
const results = await Promise.all(CHECKS.map((c) => c()));
|
|
1094
|
+
const installables = results.filter(
|
|
1095
|
+
(r) => r.status === "warn" && Array.isArray(r.install_commands) && r.install_commands.length > 0
|
|
1096
|
+
);
|
|
1097
|
+
if (installables.length === 0) {
|
|
1098
|
+
return out;
|
|
1099
|
+
}
|
|
1100
|
+
console.log(
|
|
1101
|
+
`
|
|
1102
|
+
\u{1F4A1} ${installables.length} optional check(s) installable \u2014 harnessed can run install commands now:`
|
|
1103
|
+
);
|
|
1104
|
+
for (const check of installables) {
|
|
1105
|
+
const commands = check.install_commands;
|
|
1106
|
+
if (opts.nonInteractive) {
|
|
1107
|
+
out.skipped.push(check.name);
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
const preview = commands.map((c) => ` $ ${c}`).join("\n");
|
|
1111
|
+
console.log(`
|
|
1112
|
+
${check.name}:`);
|
|
1113
|
+
console.log(preview);
|
|
1114
|
+
const ans = await p.confirm({
|
|
1115
|
+
message: `Run ${commands.length} install command(s) for "${check.name}"?`,
|
|
1116
|
+
initialValue: true
|
|
1117
|
+
});
|
|
1118
|
+
if (p.isCancel(ans) || ans !== true) {
|
|
1119
|
+
out.skipped.push(check.name);
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
let chainOk = true;
|
|
1123
|
+
for (const cmd of commands) {
|
|
1124
|
+
const tokens = cmd.split(/\s+/).filter((t2) => t2.length > 0);
|
|
1125
|
+
const exe = tokens[0];
|
|
1126
|
+
const args = tokens.slice(1);
|
|
1127
|
+
if (exe === void 0) {
|
|
1128
|
+
out.failed.push({ name: check.name, reason: `empty command in install_commands` });
|
|
1129
|
+
chainOk = false;
|
|
1130
|
+
break;
|
|
1131
|
+
}
|
|
1132
|
+
const r = spawnSync(exe, args, {
|
|
1133
|
+
encoding: "utf8",
|
|
1134
|
+
stdio: "inherit",
|
|
1135
|
+
// Windows needs shell for `.cmd` / `.bat` exes (npx.cmd / claude.cmd
|
|
1136
|
+
// shims); Unix is fine either way. Pass-through to OS shell handles
|
|
1137
|
+
// PATH resolution + extension lookup.
|
|
1138
|
+
shell: true
|
|
1139
|
+
});
|
|
1140
|
+
if (r.status !== 0) {
|
|
1141
|
+
const reason = r.error !== void 0 ? `spawn error: ${r.error.message}` : `exit code ${r.status ?? "<unknown>"} on \`${cmd}\``;
|
|
1142
|
+
out.failed.push({ name: check.name, reason });
|
|
1143
|
+
console.error(` \u2717 failed ${check.name} \u2014 ${reason}`);
|
|
1144
|
+
chainOk = false;
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
if (chainOk) {
|
|
1149
|
+
out.installed.push(check.name);
|
|
1150
|
+
console.log(` \u2713 installed ${check.name}`);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
console.log(
|
|
1154
|
+
`
|
|
1155
|
+
Auto-install summary: ${out.installed.length} installed / ${out.skipped.length} skipped / ${out.failed.length} failed`
|
|
1156
|
+
);
|
|
1157
|
+
return out;
|
|
1158
|
+
}
|
|
1159
|
+
var init_auto_install = __esm({
|
|
1160
|
+
"src/cli/lib/auto-install.ts"() {
|
|
1161
|
+
init_doctor_registry();
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
|
|
953
1165
|
// package.json
|
|
954
1166
|
var package_default = {
|
|
955
|
-
version: "3.
|
|
1167
|
+
version: "3.9.1"};
|
|
956
1168
|
|
|
957
1169
|
// src/manifest/errors.ts
|
|
958
1170
|
function instancePathToKeyPath(instancePath) {
|
|
@@ -1442,8 +1654,8 @@ function checkSecurityViolations(doc, filename, lineCounter) {
|
|
|
1442
1654
|
["spec", "verify", "cmd"],
|
|
1443
1655
|
["spec", "uninstall", "cmd"]
|
|
1444
1656
|
];
|
|
1445
|
-
for (const
|
|
1446
|
-
const err2 = checkScalarCmd(doc, lineCounter,
|
|
1657
|
+
for (const p5 of cmdPaths) {
|
|
1658
|
+
const err2 = checkScalarCmd(doc, lineCounter, p5, filename);
|
|
1447
1659
|
if (err2) errors.push(err2);
|
|
1448
1660
|
}
|
|
1449
1661
|
const cleanupNode = doc.getIn(["spec", "uninstall", "cleanup_paths"], true);
|
|
@@ -2024,111 +2236,9 @@ function registerBackupList(program2) {
|
|
|
2024
2236
|
console.log(t("backup.total_snapshots", { count: dirs.length }));
|
|
2025
2237
|
});
|
|
2026
2238
|
}
|
|
2027
|
-
function checkNodeVersion() {
|
|
2028
|
-
const v = process.versions.node;
|
|
2029
|
-
const major = Number.parseInt(v.split(".")[0] ?? "0", 10);
|
|
2030
|
-
return major >= 22 ? { name: "node \u2265 22", status: "pass", message: `node ${v}` } : {
|
|
2031
|
-
name: "node \u2265 22",
|
|
2032
|
-
status: "fail",
|
|
2033
|
-
message: `node ${v} (need \u2265 22)`,
|
|
2034
|
-
fix: "nvm install 22 && nvm use 22"
|
|
2035
|
-
};
|
|
2036
|
-
}
|
|
2037
|
-
async function checkMcpScope() {
|
|
2038
|
-
const projectMcp = join(process.cwd(), ".mcp.json");
|
|
2039
|
-
const userClaude = join(homedir(), ".claude.json");
|
|
2040
|
-
let projectExists = false;
|
|
2041
|
-
try {
|
|
2042
|
-
await readFile(projectMcp, "utf8");
|
|
2043
|
-
projectExists = true;
|
|
2044
|
-
} catch {
|
|
2045
|
-
}
|
|
2046
|
-
let userHasMcp = false;
|
|
2047
|
-
try {
|
|
2048
|
-
const raw = await readFile(userClaude, "utf8");
|
|
2049
|
-
const parsed = JSON.parse(raw);
|
|
2050
|
-
userHasMcp = !!parsed.mcpServers && Object.keys(parsed.mcpServers).length > 0;
|
|
2051
|
-
} catch {
|
|
2052
|
-
}
|
|
2053
|
-
if (userHasMcp) {
|
|
2054
|
-
return {
|
|
2055
|
-
name: "mcp scope = project",
|
|
2056
|
-
status: "fail",
|
|
2057
|
-
message: `~/.claude.json has user-scope mcpServers (CC #54803 risk)`,
|
|
2058
|
-
fix: "remove user-scope entries; re-add via `claude mcp add --scope project ...`"
|
|
2059
|
-
};
|
|
2060
|
-
}
|
|
2061
|
-
return {
|
|
2062
|
-
name: "mcp scope = project",
|
|
2063
|
-
status: "pass",
|
|
2064
|
-
message: projectExists ? "project .mcp.json present" : "no MCP servers installed"
|
|
2065
|
-
};
|
|
2066
|
-
}
|
|
2067
|
-
function checkJq() {
|
|
2068
|
-
const finder = process.platform === "win32" ? "where" : "which";
|
|
2069
|
-
const r = spawnSync(finder, ["jq"], { encoding: "utf8" });
|
|
2070
|
-
if (r.status === 0 && r.stdout.trim().length > 0) {
|
|
2071
|
-
return {
|
|
2072
|
-
name: "jq present",
|
|
2073
|
-
status: "pass",
|
|
2074
|
-
message: r.stdout.split(/\r?\n/)[0]?.trim() ?? "jq found"
|
|
2075
|
-
};
|
|
2076
|
-
}
|
|
2077
|
-
const fix = process.platform === "win32" ? "winget install jqlang.jq (or: scoop install jq)" : process.platform === "darwin" ? "brew install jq" : "apt-get install jq (or: dnf install jq)";
|
|
2078
|
-
return { name: "jq present", status: "fail", message: "jq not found in PATH", fix };
|
|
2079
|
-
}
|
|
2080
|
-
function checkWinBash() {
|
|
2081
|
-
if (process.platform !== "win32") {
|
|
2082
|
-
return { name: "bash flavor (win)", status: "pass", message: "skipped (non-Windows)" };
|
|
2083
|
-
}
|
|
2084
|
-
const where = spawnSync("where", ["bash"], { encoding: "utf8" });
|
|
2085
|
-
const firstBash = (where.stdout ?? "").split(/\r?\n/)[0]?.trim() ?? "(not found)";
|
|
2086
|
-
if (where.status !== 0 || !firstBash || firstBash === "(not found)") {
|
|
2087
|
-
return {
|
|
2088
|
-
name: "bash flavor (win)",
|
|
2089
|
-
status: "fail",
|
|
2090
|
-
message: "no bash on PATH",
|
|
2091
|
-
fix: "install Git for Windows (Git Bash) and ensure it is on PATH"
|
|
2092
|
-
};
|
|
2093
|
-
}
|
|
2094
|
-
const probe = spawnSync("bash", ["-c", "echo $WSL_DISTRO_NAME"], { encoding: "utf8" });
|
|
2095
|
-
const distro = (probe.stdout ?? "").trim();
|
|
2096
|
-
if (distro.length > 0) {
|
|
2097
|
-
return {
|
|
2098
|
-
name: "bash flavor (win)",
|
|
2099
|
-
status: "fail",
|
|
2100
|
-
message: `WSL bash (${distro}) \u2014 ralph-loop subagent fork breaks under WSL`,
|
|
2101
|
-
fix: "reorder PATH so Git Bash precedes WSL bash.exe (Settings \u2192 System \u2192 Environment Variables)"
|
|
2102
|
-
};
|
|
2103
|
-
}
|
|
2104
|
-
return { name: "bash flavor (win)", status: "pass", message: `${firstBash} (Git Bash / native)` };
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
// src/cli/lib/doctor-registry.ts
|
|
2108
|
-
var CHECKS = [
|
|
2109
|
-
async () => checkNodeVersion(),
|
|
2110
|
-
checkMcpScope,
|
|
2111
|
-
async () => checkJq(),
|
|
2112
|
-
async () => checkWinBash(),
|
|
2113
|
-
async () => {
|
|
2114
|
-
const { checkOrigin: checkOrigin2 } = await Promise.resolve().then(() => (init_origin_check(), origin_check_exports));
|
|
2115
|
-
const r = checkOrigin2(process.cwd(), { allowFork: true });
|
|
2116
|
-
return { name: "origin URL", status: r.status, message: r.detail, fix: r.fix };
|
|
2117
|
-
},
|
|
2118
|
-
async () => {
|
|
2119
|
-
const { probeGstackPrefix: probeGstackPrefix2 } = await Promise.resolve().then(() => (init_probe_gstack(), probe_gstack_exports));
|
|
2120
|
-
const r = probeGstackPrefix2();
|
|
2121
|
-
return { name: "gstack prefix", status: r.status, message: r.detail, fix: r.fix };
|
|
2122
|
-
},
|
|
2123
|
-
async () => (await Promise.resolve().then(() => (init_check_deprecations(), check_deprecations_exports))).checkDeprecations(),
|
|
2124
|
-
async () => (await Promise.resolve().then(() => (init_check_token_budget(), check_token_budget_exports))).checkTokenBudget(),
|
|
2125
|
-
async () => (await Promise.resolve().then(() => (init_check_agent_teams_doctor(), check_agent_teams_doctor_exports))).checkAgentTeamsDoctor(),
|
|
2126
|
-
async () => (await Promise.resolve().then(() => (init_check_planning_with_files(), check_planning_with_files_exports))).checkPlanningWithFiles(),
|
|
2127
|
-
async () => (await Promise.resolve().then(() => (init_check_mattpocock_skills(), check_mattpocock_skills_exports))).checkMattpocockSkills(),
|
|
2128
|
-
async () => (await Promise.resolve().then(() => (init_check_mcp_availability(), check_mcp_availability_exports))).checkMcpAvailability()
|
|
2129
|
-
];
|
|
2130
2239
|
|
|
2131
2240
|
// src/cli/doctor.ts
|
|
2241
|
+
init_doctor_registry();
|
|
2132
2242
|
function registerDoctor(program2) {
|
|
2133
2243
|
program2.command("doctor").description(
|
|
2134
2244
|
"Preflight checks (Node / MCP scope / jq / Win bash / origin URL / gstack prefix / deprecations / token budget / Agent Teams / planning-with-files / mattpocock-skills / MCP availability)"
|
|
@@ -2378,7 +2488,7 @@ var V3_4_3_SIGNATURE_MASTER_RX = /\*\*Preferred path\*\*[\s\S]*dispatch to the p
|
|
|
2378
2488
|
function shouldOverwriteFile(content) {
|
|
2379
2489
|
return HARNESSED_MARKER_RX.test(content) || V3_4_3_SIGNATURE_SUB_RX.test(content) || V3_4_3_SIGNATURE_MASTER_RX.test(content);
|
|
2380
2490
|
}
|
|
2381
|
-
async function writeAllCommands(slashNames, commandsDir, rolePrompts, capabilities, installedPlugins, installedUserSkills, writer, fileExists2 = existsSync, readFileSync9 = (
|
|
2491
|
+
async function writeAllCommands(slashNames, commandsDir, rolePrompts, capabilities, installedPlugins, installedUserSkills, writer, fileExists2 = existsSync, readFileSync9 = (p5) => readFileSync(p5, "utf8")) {
|
|
2382
2492
|
const results = [];
|
|
2383
2493
|
const aggregatedWarnings = /* @__PURE__ */ new Set();
|
|
2384
2494
|
for (const name of slashNames) {
|
|
@@ -3362,10 +3472,10 @@ ${rp.checklist.map((c, i) => ` ${i + 1}. ${c}`).join("\n")}` : "";
|
|
|
3362
3472
|
}
|
|
3363
3473
|
function isRalphLoopOptIn(phase) {
|
|
3364
3474
|
if (!phase || typeof phase !== "object") return false;
|
|
3365
|
-
const
|
|
3366
|
-
if (
|
|
3367
|
-
if (
|
|
3368
|
-
const fb =
|
|
3475
|
+
const p5 = phase;
|
|
3476
|
+
if (p5.max_iterations !== void 0 && p5.max_iterations !== null) return true;
|
|
3477
|
+
if (p5.upstream === "ralph-loop") return true;
|
|
3478
|
+
const fb = p5.fallback;
|
|
3369
3479
|
if (fb?.max_iterations_exceeded !== void 0) return true;
|
|
3370
3480
|
return false;
|
|
3371
3481
|
}
|
|
@@ -3713,19 +3823,19 @@ async function listWorkflowNames(workflowsDir) {
|
|
|
3713
3823
|
const names = [];
|
|
3714
3824
|
const entries = await readdir(workflowsDir);
|
|
3715
3825
|
for (const e of entries.sort()) {
|
|
3716
|
-
const
|
|
3717
|
-
const s = await stat(
|
|
3826
|
+
const p5 = join(workflowsDir, e);
|
|
3827
|
+
const s = await stat(p5).catch(() => null);
|
|
3718
3828
|
if (!s?.isDirectory()) continue;
|
|
3719
|
-
if (await fileExists(join(
|
|
3829
|
+
if (await fileExists(join(p5, "workflow.yaml"))) {
|
|
3720
3830
|
names.push(e);
|
|
3721
3831
|
continue;
|
|
3722
3832
|
}
|
|
3723
|
-
if (await fileExists(join(
|
|
3833
|
+
if (await fileExists(join(p5, "auto", "workflow.yaml"))) {
|
|
3724
3834
|
names.push(e);
|
|
3725
|
-
const subs = await readdir(
|
|
3835
|
+
const subs = await readdir(p5).catch(() => []);
|
|
3726
3836
|
for (const sub of subs.sort()) {
|
|
3727
3837
|
if (sub === "auto") continue;
|
|
3728
|
-
if (await fileExists(join(
|
|
3838
|
+
if (await fileExists(join(p5, sub, "workflow.yaml"))) {
|
|
3729
3839
|
names.push(`${e}-${sub}`);
|
|
3730
3840
|
}
|
|
3731
3841
|
}
|
|
@@ -3859,10 +3969,10 @@ async function dirSizeKb(dir) {
|
|
|
3859
3969
|
if (!cur) break;
|
|
3860
3970
|
const entries = await readdir(cur, { withFileTypes: true });
|
|
3861
3971
|
for (const e of entries) {
|
|
3862
|
-
const
|
|
3863
|
-
if (e.isDirectory()) stack.push(
|
|
3972
|
+
const p5 = join(cur, e.name);
|
|
3973
|
+
if (e.isDirectory()) stack.push(p5);
|
|
3864
3974
|
else if (e.isFile()) {
|
|
3865
|
-
const st = await stat(
|
|
3975
|
+
const st = await stat(p5);
|
|
3866
3976
|
total += st.size;
|
|
3867
3977
|
}
|
|
3868
3978
|
}
|
|
@@ -6029,7 +6139,7 @@ function registerSetup(program2) {
|
|
|
6029
6139
|
).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option(
|
|
6030
6140
|
"--user-lang <code>",
|
|
6031
6141
|
"override detected OS locale for env.HARNESSED_USER_LANG (en | zh-Hans / zh-CN / zh-TW)"
|
|
6032
|
-
).action(async (raw) => {
|
|
6142
|
+
).option("--non-interactive", "skip all confirm prompts (CI / scripted setup)").option("--no-auto-install", "do not prompt to auto-install missing plugins (advisory only)").action(async (raw) => {
|
|
6033
6143
|
const dryRun = raw.dryRun === true;
|
|
6034
6144
|
const pkgRoot = getPackageRoot();
|
|
6035
6145
|
const workflowsDir = resolve(pkgRoot, "workflows");
|
|
@@ -6112,7 +6222,7 @@ function registerSetup(program2) {
|
|
|
6112
6222
|
capabilitiesMap,
|
|
6113
6223
|
installedPlugins,
|
|
6114
6224
|
installedUserSkills,
|
|
6115
|
-
async (
|
|
6225
|
+
async (p5, c) => writeFile(p5, c, "utf8")
|
|
6116
6226
|
);
|
|
6117
6227
|
const writtenCount = cmdResult.results.filter((r) => r.written).length;
|
|
6118
6228
|
const skippedCount = cmdResult.results.filter((r) => !r.written && r.warning).length;
|
|
@@ -6188,6 +6298,15 @@ function registerSetup(program2) {
|
|
|
6188
6298
|
console.log(t("setup.bundled_summary"));
|
|
6189
6299
|
console.log(t("setup.bundled_location"));
|
|
6190
6300
|
console.log(t("setup.doctor_hint"));
|
|
6301
|
+
if (!dryRun) {
|
|
6302
|
+
const isTty = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
6303
|
+
const { runAutoInstall: runAutoInstall2 } = await Promise.resolve().then(() => (init_auto_install(), auto_install_exports));
|
|
6304
|
+
await runAutoInstall2({
|
|
6305
|
+
nonInteractive: raw.nonInteractive === true || !isTty,
|
|
6306
|
+
autoInstall: raw.autoInstall !== false
|
|
6307
|
+
// commander default = true; --no-auto-install flips
|
|
6308
|
+
});
|
|
6309
|
+
}
|
|
6191
6310
|
process.exit(0);
|
|
6192
6311
|
});
|
|
6193
6312
|
}
|