harnessed 3.7.0 → 3.9.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/dist/cli.mjs +246 -141
- 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/workflows/task/deliver/workflow.yaml +2 -0
- package/workflows/task/test/workflow.yaml +3 -0
- package/workflows/verify/multispec/workflow.yaml +4 -0
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 = {};
|
|
@@ -720,6 +803,36 @@ var init_check_mcp_availability = __esm({
|
|
|
720
803
|
TARGET_SERVERS = ["tavily-mcp", "exa-mcp", "chrome-devtools"];
|
|
721
804
|
}
|
|
722
805
|
});
|
|
806
|
+
|
|
807
|
+
// src/cli/lib/doctor-registry.ts
|
|
808
|
+
var CHECKS;
|
|
809
|
+
var init_doctor_registry = __esm({
|
|
810
|
+
"src/cli/lib/doctor-registry.ts"() {
|
|
811
|
+
init_check_builtin();
|
|
812
|
+
CHECKS = [
|
|
813
|
+
async () => checkNodeVersion(),
|
|
814
|
+
checkMcpScope,
|
|
815
|
+
async () => checkJq(),
|
|
816
|
+
async () => checkWinBash(),
|
|
817
|
+
async () => {
|
|
818
|
+
const { checkOrigin: checkOrigin2 } = await Promise.resolve().then(() => (init_origin_check(), origin_check_exports));
|
|
819
|
+
const r = checkOrigin2(process.cwd(), { allowFork: true });
|
|
820
|
+
return { name: "origin URL", status: r.status, message: r.detail, fix: r.fix };
|
|
821
|
+
},
|
|
822
|
+
async () => {
|
|
823
|
+
const { probeGstackPrefix: probeGstackPrefix2 } = await Promise.resolve().then(() => (init_probe_gstack(), probe_gstack_exports));
|
|
824
|
+
const r = probeGstackPrefix2();
|
|
825
|
+
return { name: "gstack prefix", status: r.status, message: r.detail, fix: r.fix };
|
|
826
|
+
},
|
|
827
|
+
async () => (await Promise.resolve().then(() => (init_check_deprecations(), check_deprecations_exports))).checkDeprecations(),
|
|
828
|
+
async () => (await Promise.resolve().then(() => (init_check_token_budget(), check_token_budget_exports))).checkTokenBudget(),
|
|
829
|
+
async () => (await Promise.resolve().then(() => (init_check_agent_teams_doctor(), check_agent_teams_doctor_exports))).checkAgentTeamsDoctor(),
|
|
830
|
+
async () => (await Promise.resolve().then(() => (init_check_planning_with_files(), check_planning_with_files_exports))).checkPlanningWithFiles(),
|
|
831
|
+
async () => (await Promise.resolve().then(() => (init_check_mattpocock_skills(), check_mattpocock_skills_exports))).checkMattpocockSkills(),
|
|
832
|
+
async () => (await Promise.resolve().then(() => (init_check_mcp_availability(), check_mcp_availability_exports))).checkMcpAvailability()
|
|
833
|
+
];
|
|
834
|
+
}
|
|
835
|
+
});
|
|
723
836
|
function statePath() {
|
|
724
837
|
return harnessedFile("current-workflow.json");
|
|
725
838
|
}
|
|
@@ -950,9 +1063,71 @@ var init_resume = __esm({
|
|
|
950
1063
|
}
|
|
951
1064
|
});
|
|
952
1065
|
|
|
1066
|
+
// src/cli/lib/auto-install.ts
|
|
1067
|
+
var auto_install_exports = {};
|
|
1068
|
+
__export(auto_install_exports, {
|
|
1069
|
+
extractPluginName: () => extractPluginName,
|
|
1070
|
+
runAutoInstall: () => runAutoInstall
|
|
1071
|
+
});
|
|
1072
|
+
function extractPluginName(fix) {
|
|
1073
|
+
const m = fix.match(/claude\s+plugin\s+install\s+([\w@\-/.]+)/);
|
|
1074
|
+
return m?.[1] ?? null;
|
|
1075
|
+
}
|
|
1076
|
+
async function runAutoInstall(opts) {
|
|
1077
|
+
const out = { installed: [], skipped: [], failed: [] };
|
|
1078
|
+
if (!opts.autoInstall) {
|
|
1079
|
+
return out;
|
|
1080
|
+
}
|
|
1081
|
+
const results = await Promise.all(CHECKS.map((c) => c()));
|
|
1082
|
+
const installables = results.filter((r) => r.status === "warn" && typeof r.fix === "string").map((r) => ({ check: r, plugin: extractPluginName(r.fix ?? "") })).filter((entry) => entry.plugin !== null);
|
|
1083
|
+
if (installables.length === 0) {
|
|
1084
|
+
return out;
|
|
1085
|
+
}
|
|
1086
|
+
console.log(
|
|
1087
|
+
`
|
|
1088
|
+
\u{1F4A1} ${installables.length} optional plugin(s) missing \u2014 harnessed can install them now:`
|
|
1089
|
+
);
|
|
1090
|
+
for (const { check, plugin } of installables) {
|
|
1091
|
+
if (opts.nonInteractive) {
|
|
1092
|
+
out.skipped.push(plugin);
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
const ans = await p.confirm({
|
|
1096
|
+
message: `Install "${plugin}" via \`claude plugin install\`? (${check.name})`,
|
|
1097
|
+
initialValue: true
|
|
1098
|
+
});
|
|
1099
|
+
if (p.isCancel(ans) || ans !== true) {
|
|
1100
|
+
out.skipped.push(plugin);
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
const r = spawnSync("claude", ["plugin", "install", plugin], {
|
|
1104
|
+
encoding: "utf8",
|
|
1105
|
+
stdio: "inherit"
|
|
1106
|
+
});
|
|
1107
|
+
if (r.status === 0) {
|
|
1108
|
+
out.installed.push(plugin);
|
|
1109
|
+
console.log(` \u2713 installed ${plugin}`);
|
|
1110
|
+
} else {
|
|
1111
|
+
const reason = r.error !== void 0 ? `spawn error: ${r.error.message}` : `exit code ${r.status ?? "<unknown>"}`;
|
|
1112
|
+
out.failed.push({ name: plugin, reason });
|
|
1113
|
+
console.error(` \u2717 failed ${plugin} \u2014 ${reason}`);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
console.log(
|
|
1117
|
+
`
|
|
1118
|
+
Auto-install summary: ${out.installed.length} installed / ${out.skipped.length} skipped / ${out.failed.length} failed`
|
|
1119
|
+
);
|
|
1120
|
+
return out;
|
|
1121
|
+
}
|
|
1122
|
+
var init_auto_install = __esm({
|
|
1123
|
+
"src/cli/lib/auto-install.ts"() {
|
|
1124
|
+
init_doctor_registry();
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
|
|
953
1128
|
// package.json
|
|
954
1129
|
var package_default = {
|
|
955
|
-
version: "3.
|
|
1130
|
+
version: "3.9.0"};
|
|
956
1131
|
|
|
957
1132
|
// src/manifest/errors.ts
|
|
958
1133
|
function instancePathToKeyPath(instancePath) {
|
|
@@ -1442,8 +1617,8 @@ function checkSecurityViolations(doc, filename, lineCounter) {
|
|
|
1442
1617
|
["spec", "verify", "cmd"],
|
|
1443
1618
|
["spec", "uninstall", "cmd"]
|
|
1444
1619
|
];
|
|
1445
|
-
for (const
|
|
1446
|
-
const err2 = checkScalarCmd(doc, lineCounter,
|
|
1620
|
+
for (const p5 of cmdPaths) {
|
|
1621
|
+
const err2 = checkScalarCmd(doc, lineCounter, p5, filename);
|
|
1447
1622
|
if (err2) errors.push(err2);
|
|
1448
1623
|
}
|
|
1449
1624
|
const cleanupNode = doc.getIn(["spec", "uninstall", "cleanup_paths"], true);
|
|
@@ -2024,111 +2199,9 @@ function registerBackupList(program2) {
|
|
|
2024
2199
|
console.log(t("backup.total_snapshots", { count: dirs.length }));
|
|
2025
2200
|
});
|
|
2026
2201
|
}
|
|
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
2202
|
|
|
2131
2203
|
// src/cli/doctor.ts
|
|
2204
|
+
init_doctor_registry();
|
|
2132
2205
|
function registerDoctor(program2) {
|
|
2133
2206
|
program2.command("doctor").description(
|
|
2134
2207
|
"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 +2451,7 @@ var V3_4_3_SIGNATURE_MASTER_RX = /\*\*Preferred path\*\*[\s\S]*dispatch to the p
|
|
|
2378
2451
|
function shouldOverwriteFile(content) {
|
|
2379
2452
|
return HARNESSED_MARKER_RX.test(content) || V3_4_3_SIGNATURE_SUB_RX.test(content) || V3_4_3_SIGNATURE_MASTER_RX.test(content);
|
|
2380
2453
|
}
|
|
2381
|
-
async function writeAllCommands(slashNames, commandsDir, rolePrompts, capabilities, installedPlugins, installedUserSkills, writer, fileExists2 = existsSync, readFileSync9 = (
|
|
2454
|
+
async function writeAllCommands(slashNames, commandsDir, rolePrompts, capabilities, installedPlugins, installedUserSkills, writer, fileExists2 = existsSync, readFileSync9 = (p5) => readFileSync(p5, "utf8")) {
|
|
2382
2455
|
const results = [];
|
|
2383
2456
|
const aggregatedWarnings = /* @__PURE__ */ new Set();
|
|
2384
2457
|
for (const name of slashNames) {
|
|
@@ -3035,8 +3108,15 @@ var WorkflowPhaseV3 = Type.Object(
|
|
|
3035
3108
|
// numeric literal OR jinja '{{ defaults.x.y }}'
|
|
3036
3109
|
),
|
|
3037
3110
|
artifacts_expected: Type.Optional(Type.Array(Type.String())),
|
|
3038
|
-
invokes_tools: Type.Optional(Type.Array(InvokeToolClause))
|
|
3111
|
+
invokes_tools: Type.Optional(Type.Array(InvokeToolClause)),
|
|
3039
3112
|
// NEW v3 D-05 phase-level conditional fire
|
|
3113
|
+
// v3.8.0 P1 — Conditional RULES inject. Empty/absent → DEFAULT_INJECTS_RULES
|
|
3114
|
+
// (escalation + transparent-skip, ~470 tokens). Declare full list
|
|
3115
|
+
// ['escalation', 'transparent-skip', 'agent-teams-prevention'] on phases
|
|
3116
|
+
// that genuinely involve Agent Teams escalation (task-deliver / task-test /
|
|
3117
|
+
// verify-multispec). Unknown rule names silently filtered at runtime
|
|
3118
|
+
// (forward-compat for future RULES additions).
|
|
3119
|
+
injects_rules: Type.Optional(Type.Array(Type.String()))
|
|
3040
3120
|
},
|
|
3041
3121
|
{ additionalProperties: false }
|
|
3042
3122
|
);
|
|
@@ -3311,18 +3391,24 @@ var AGENT_TEAMS_PREVENTION_RULES = `If you signal needs_teams_escalation=true, A
|
|
|
3311
3391
|
3. **Token cost estimation**: Before creating a team, estimate \`team_cost \u2248 N_teammates \xD7 N_rounds \xD7 avg_tokens_per_round + N_teammates \xD7 initial_brief_tokens\`. Compare to subagent fan-out cost (\`\u2248 N_subagents \xD7 (initial_brief + summary_tokens)\`). Only open a team when \`team_cost < 2 \xD7 subagent_cost\` \u2014 otherwise prefer fan-out.
|
|
3312
3392
|
|
|
3313
3393
|
4. **Brief must be self-contained**: Each teammate launches WITHOUT main-session context. The Agent() prompt must include enough background, file paths, success criteria, and counter-positions so the teammate can work independently. Generic prompts produce shallow output.`;
|
|
3314
|
-
var
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3394
|
+
var RULES_MAP = {
|
|
3395
|
+
escalation: ESCALATION_RULES,
|
|
3396
|
+
"transparent-skip": TRANSPARENT_SKIP_RULES,
|
|
3397
|
+
"agent-teams-prevention": AGENT_TEAMS_PREVENTION_RULES
|
|
3398
|
+
};
|
|
3399
|
+
var DEFAULT_INJECTS_RULES = ["escalation", "transparent-skip"];
|
|
3400
|
+
function buildCriticalReminder(injectsRules) {
|
|
3401
|
+
const rules = injectsRules ?? DEFAULT_INJECTS_RULES;
|
|
3402
|
+
return rules.map((name) => RULES_MAP[name]).filter((rule) => rule !== void 0).join("\n\n");
|
|
3403
|
+
}
|
|
3404
|
+
function buildAgentDef(skillName, rolePrompts, workflowName, modelTierOverride, injectsRules) {
|
|
3320
3405
|
const rp = rolePrompts?.[skillName] ?? (workflowName ? rolePrompts?.[workflowName] : void 0);
|
|
3406
|
+
const criticalReminder = buildCriticalReminder(injectsRules);
|
|
3321
3407
|
if (!rp) {
|
|
3322
3408
|
return {
|
|
3323
3409
|
description: `harnessed workflow phase: ${skillName}`,
|
|
3324
3410
|
prompt: `You are executing the '${skillName}' workflow phase. Follow the phase intent and emit a structured COMPLETE signal when done.`,
|
|
3325
|
-
criticalSystemReminder_EXPERIMENTAL:
|
|
3411
|
+
criticalSystemReminder_EXPERIMENTAL: criticalReminder,
|
|
3326
3412
|
...modelTierOverride ? { model: modelTierOverride } : {}
|
|
3327
3413
|
};
|
|
3328
3414
|
}
|
|
@@ -3343,16 +3429,16 @@ ${rp.checklist.map((c, i) => ` ${i + 1}. ${c}`).join("\n")}` : "";
|
|
|
3343
3429
|
return {
|
|
3344
3430
|
description: rp.description,
|
|
3345
3431
|
prompt,
|
|
3346
|
-
criticalSystemReminder_EXPERIMENTAL:
|
|
3432
|
+
criticalSystemReminder_EXPERIMENTAL: criticalReminder,
|
|
3347
3433
|
...modelTierOverride ? { model: modelTierOverride } : {}
|
|
3348
3434
|
};
|
|
3349
3435
|
}
|
|
3350
3436
|
function isRalphLoopOptIn(phase) {
|
|
3351
3437
|
if (!phase || typeof phase !== "object") return false;
|
|
3352
|
-
const
|
|
3353
|
-
if (
|
|
3354
|
-
if (
|
|
3355
|
-
const fb =
|
|
3438
|
+
const p5 = phase;
|
|
3439
|
+
if (p5.max_iterations !== void 0 && p5.max_iterations !== null) return true;
|
|
3440
|
+
if (p5.upstream === "ralph-loop") return true;
|
|
3441
|
+
const fb = p5.fallback;
|
|
3356
3442
|
if (fb?.max_iterations_exceeded !== void 0) return true;
|
|
3357
3443
|
return false;
|
|
3358
3444
|
}
|
|
@@ -3373,14 +3459,23 @@ function resolveMaxIterations(phase, gateContext) {
|
|
|
3373
3459
|
var _dispatchSkillStub = {
|
|
3374
3460
|
fn: async (skillName, phase, opts) => {
|
|
3375
3461
|
const optIn = isRalphLoopOptIn(phase);
|
|
3376
|
-
const spawnOnce = async (resumeSessionId, onSessionId) =>
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3462
|
+
const spawnOnce = async (resumeSessionId, onSessionId) => {
|
|
3463
|
+
const injectsRules = phase && typeof phase === "object" && "injects_rules" in phase && Array.isArray(phase.injects_rules) ? phase.injects_rules : void 0;
|
|
3464
|
+
return sdkSpawn(
|
|
3465
|
+
buildAgentDef(
|
|
3466
|
+
skillName,
|
|
3467
|
+
opts?.rolePrompts,
|
|
3468
|
+
opts?.workflowName,
|
|
3469
|
+
opts?.modelTierOverride,
|
|
3470
|
+
injectsRules
|
|
3471
|
+
),
|
|
3472
|
+
{
|
|
3473
|
+
expertName: skillName,
|
|
3474
|
+
...resumeSessionId ? { resumeSessionId } : {},
|
|
3475
|
+
...onSessionId ? { onSessionId } : {}
|
|
3476
|
+
}
|
|
3477
|
+
);
|
|
3478
|
+
};
|
|
3384
3479
|
let envelopeJson;
|
|
3385
3480
|
try {
|
|
3386
3481
|
if (optIn) {
|
|
@@ -3691,19 +3786,19 @@ async function listWorkflowNames(workflowsDir) {
|
|
|
3691
3786
|
const names = [];
|
|
3692
3787
|
const entries = await readdir(workflowsDir);
|
|
3693
3788
|
for (const e of entries.sort()) {
|
|
3694
|
-
const
|
|
3695
|
-
const s = await stat(
|
|
3789
|
+
const p5 = join(workflowsDir, e);
|
|
3790
|
+
const s = await stat(p5).catch(() => null);
|
|
3696
3791
|
if (!s?.isDirectory()) continue;
|
|
3697
|
-
if (await fileExists(join(
|
|
3792
|
+
if (await fileExists(join(p5, "workflow.yaml"))) {
|
|
3698
3793
|
names.push(e);
|
|
3699
3794
|
continue;
|
|
3700
3795
|
}
|
|
3701
|
-
if (await fileExists(join(
|
|
3796
|
+
if (await fileExists(join(p5, "auto", "workflow.yaml"))) {
|
|
3702
3797
|
names.push(e);
|
|
3703
|
-
const subs = await readdir(
|
|
3798
|
+
const subs = await readdir(p5).catch(() => []);
|
|
3704
3799
|
for (const sub of subs.sort()) {
|
|
3705
3800
|
if (sub === "auto") continue;
|
|
3706
|
-
if (await fileExists(join(
|
|
3801
|
+
if (await fileExists(join(p5, sub, "workflow.yaml"))) {
|
|
3707
3802
|
names.push(`${e}-${sub}`);
|
|
3708
3803
|
}
|
|
3709
3804
|
}
|
|
@@ -3837,10 +3932,10 @@ async function dirSizeKb(dir) {
|
|
|
3837
3932
|
if (!cur) break;
|
|
3838
3933
|
const entries = await readdir(cur, { withFileTypes: true });
|
|
3839
3934
|
for (const e of entries) {
|
|
3840
|
-
const
|
|
3841
|
-
if (e.isDirectory()) stack.push(
|
|
3935
|
+
const p5 = join(cur, e.name);
|
|
3936
|
+
if (e.isDirectory()) stack.push(p5);
|
|
3842
3937
|
else if (e.isFile()) {
|
|
3843
|
-
const st = await stat(
|
|
3938
|
+
const st = await stat(p5);
|
|
3844
3939
|
total += st.size;
|
|
3845
3940
|
}
|
|
3846
3941
|
}
|
|
@@ -6007,7 +6102,7 @@ function registerSetup(program2) {
|
|
|
6007
6102
|
).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option(
|
|
6008
6103
|
"--user-lang <code>",
|
|
6009
6104
|
"override detected OS locale for env.HARNESSED_USER_LANG (en | zh-Hans / zh-CN / zh-TW)"
|
|
6010
|
-
).action(async (raw) => {
|
|
6105
|
+
).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) => {
|
|
6011
6106
|
const dryRun = raw.dryRun === true;
|
|
6012
6107
|
const pkgRoot = getPackageRoot();
|
|
6013
6108
|
const workflowsDir = resolve(pkgRoot, "workflows");
|
|
@@ -6090,7 +6185,7 @@ function registerSetup(program2) {
|
|
|
6090
6185
|
capabilitiesMap,
|
|
6091
6186
|
installedPlugins,
|
|
6092
6187
|
installedUserSkills,
|
|
6093
|
-
async (
|
|
6188
|
+
async (p5, c) => writeFile(p5, c, "utf8")
|
|
6094
6189
|
);
|
|
6095
6190
|
const writtenCount = cmdResult.results.filter((r) => r.written).length;
|
|
6096
6191
|
const skippedCount = cmdResult.results.filter((r) => !r.written && r.warning).length;
|
|
@@ -6165,6 +6260,16 @@ function registerSetup(program2) {
|
|
|
6165
6260
|
}
|
|
6166
6261
|
console.log(t("setup.bundled_summary"));
|
|
6167
6262
|
console.log(t("setup.bundled_location"));
|
|
6263
|
+
console.log(t("setup.doctor_hint"));
|
|
6264
|
+
if (!dryRun) {
|
|
6265
|
+
const isTty = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
6266
|
+
const { runAutoInstall: runAutoInstall2 } = await Promise.resolve().then(() => (init_auto_install(), auto_install_exports));
|
|
6267
|
+
await runAutoInstall2({
|
|
6268
|
+
nonInteractive: raw.nonInteractive === true || !isTty,
|
|
6269
|
+
autoInstall: raw.autoInstall !== false
|
|
6270
|
+
// commander default = true; --no-auto-install flips
|
|
6271
|
+
});
|
|
6272
|
+
}
|
|
6168
6273
|
process.exit(0);
|
|
6169
6274
|
});
|
|
6170
6275
|
}
|