harnessed 1.0.4 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.mjs +532 -247
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/workflows/capabilities.yaml +413 -0
- package/workflows/defaults.yaml +36 -0
- package/workflows/execute-task/SKILL.md +22 -9
- package/workflows/execute-task/phases.yaml +60 -14
- package/workflows/judgments/fallback.yaml +35 -0
- package/workflows/judgments/parallelism-gate.yaml +47 -0
- package/workflows/judgments/phase-gate.yaml +17 -0
- package/workflows/judgments/strategic-gate.yaml +25 -0
- package/workflows/judgments/subtask-gate.yaml +17 -0
- package/workflows/judgments/tdd-gate.yaml +17 -0
- package/workflows/plan-feature/SKILL.md +56 -14
- package/workflows/plan-feature/workflow.yaml +66 -25
- package/workflows/research/SKILL.md +35 -0
- package/workflows/research/workflow.yaml +18 -0
- package/workflows/verify-work/SKILL.md +92 -0
- package/workflows/verify-work/workflow.yaml +143 -0
package/dist/cli.mjs
CHANGED
|
@@ -148,8 +148,8 @@ var init_path_guard = __esm({
|
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
150
|
function branchOnSchemaVersion(v, handlers) {
|
|
151
|
-
const
|
|
152
|
-
return
|
|
151
|
+
const isKnownVersion = Object.values(SCHEMA_VERSIONS).includes(v);
|
|
152
|
+
return isKnownVersion ? handlers.v1() : handlers.unknown();
|
|
153
153
|
}
|
|
154
154
|
var SCHEMA_VERSIONS;
|
|
155
155
|
var init_schemaVersion = __esm({
|
|
@@ -172,8 +172,14 @@ var init_schemaVersion = __esm({
|
|
|
172
172
|
// ← Phase 3.3 W0 T0.5 BACKFILL 11th surface (sister Phase 3.2 W2 T2.2 b875e21 commit msg claim "11th surface" was LATENT STALE — never registered; T0.5 surgical fix per sister Phase 3.2 W2 T2.6 latent W1 c37ee29 Rule 1 pattern)
|
|
173
173
|
aliases: "harnessed.aliases.v1",
|
|
174
174
|
// ← Phase 3.3 W1 T1.1 ADD 12th surface (D-01 RICH manifests/aliases.yaml upstream rename redirect + metadata)
|
|
175
|
-
knownGood: "harnessed.known-good.v1"
|
|
175
|
+
knownGood: "harnessed.known-good.v1",
|
|
176
176
|
// ← Phase 3.3 W1 T1.1 ADD 13th surface (D-03 YAML versions/<harnessed-ver>-known-good.yaml per-version lock)
|
|
177
|
+
capabilities: "harnessed.capabilities.v1",
|
|
178
|
+
// ← Phase v2.0-2.3 W0 T2.3.W0.6 ADD 14th surface (R20.2 flat yaml capabilities manifest validate)
|
|
179
|
+
judgment: "harnessed.judgment.v1",
|
|
180
|
+
// ← Phase v2.0-2.3 W0 T2.3.W0.6 ADD 15th surface (R20.4 multi-file judgments triggers/rules validate)
|
|
181
|
+
workflow: "harnessed.workflow.v2"
|
|
182
|
+
// ← Phase v2.0-2.4 W0 T2.4.W0.1 ADD 16th surface (R20.1 + R20.2 + R20.9 — workflow.yaml v2 schema: gate / on / capability / args / fallback / parallelism 字段, sister 4 workflow.yaml: plan-feature + execute-task + research + verify-work)
|
|
177
183
|
};
|
|
178
184
|
Type.Union([
|
|
179
185
|
Type.Literal(SCHEMA_VERSIONS.routingSnapshot),
|
|
@@ -193,8 +199,14 @@ var init_schemaVersion = __esm({
|
|
|
193
199
|
// ← Phase 3.3 W0 T0.5 BACKFILL 11th surface
|
|
194
200
|
Type.Literal(SCHEMA_VERSIONS.aliases),
|
|
195
201
|
// ← Phase 3.3 W1 T1.1 ADD 12th surface
|
|
196
|
-
Type.Literal(SCHEMA_VERSIONS.knownGood)
|
|
202
|
+
Type.Literal(SCHEMA_VERSIONS.knownGood),
|
|
197
203
|
// ← Phase 3.3 W1 T1.1 ADD 13th surface
|
|
204
|
+
Type.Literal(SCHEMA_VERSIONS.capabilities),
|
|
205
|
+
// ← Phase v2.0-2.3 W0 T2.3.W0.6 ADD 14th surface
|
|
206
|
+
Type.Literal(SCHEMA_VERSIONS.judgment),
|
|
207
|
+
// ← Phase v2.0-2.3 W0 T2.3.W0.6 ADD 15th surface
|
|
208
|
+
Type.Literal(SCHEMA_VERSIONS.workflow)
|
|
209
|
+
// ← Phase v2.0-2.4 W0 T2.4.W0.1 ADD 16th surface (workflow.yaml v2 schema, NOTE first .v2 surface in union)
|
|
198
210
|
]);
|
|
199
211
|
}
|
|
200
212
|
});
|
|
@@ -456,6 +468,109 @@ var init_check_token_budget = __esm({
|
|
|
456
468
|
PER_SKILL_THRESHOLD = 5e3;
|
|
457
469
|
}
|
|
458
470
|
});
|
|
471
|
+
async function checkAgentTeams() {
|
|
472
|
+
const envValue = process.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
|
|
473
|
+
const envOn = envValue === "1";
|
|
474
|
+
let settingsValue;
|
|
475
|
+
let settingsOn = false;
|
|
476
|
+
try {
|
|
477
|
+
const path = resolve(homedir(), ".claude", "settings.json");
|
|
478
|
+
const raw = await readFile(path, "utf8");
|
|
479
|
+
const data = JSON.parse(raw);
|
|
480
|
+
settingsValue = data.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
|
|
481
|
+
settingsOn = settingsValue === "1";
|
|
482
|
+
} catch {
|
|
483
|
+
}
|
|
484
|
+
const detected = { env: envOn, settingsJson: settingsOn };
|
|
485
|
+
if (envOn || settingsOn) {
|
|
486
|
+
return { status: "pass", detected, envValue, settingsValue };
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
status: "missing",
|
|
490
|
+
detected,
|
|
491
|
+
envValue,
|
|
492
|
+
settingsValue,
|
|
493
|
+
remediation: 'Agent Teams not enabled. Add to ~/.claude/settings.json:\n "env": { "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" }\nOR run: claude config set env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS 1\nOR export env var:\n export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1\nThen restart Claude Code (CC >= 2.1.133 required).'
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
var init_checkAgentTeams = __esm({
|
|
497
|
+
"src/cli/lib/checkAgentTeams.ts"() {
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// src/cli/lib/check-agent-teams-doctor.ts
|
|
502
|
+
var check_agent_teams_doctor_exports = {};
|
|
503
|
+
__export(check_agent_teams_doctor_exports, {
|
|
504
|
+
checkAgentTeamsDoctor: () => checkAgentTeamsDoctor
|
|
505
|
+
});
|
|
506
|
+
async function checkAgentTeamsDoctor() {
|
|
507
|
+
const r = await checkAgentTeams();
|
|
508
|
+
if (r.status === "pass") {
|
|
509
|
+
const source = r.detected.env ? "env var" : "settings.json";
|
|
510
|
+
return {
|
|
511
|
+
name: "Agent Teams env",
|
|
512
|
+
status: "pass",
|
|
513
|
+
message: `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 (${source})`
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
name: "Agent Teams env",
|
|
518
|
+
status: "warn",
|
|
519
|
+
message: "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS not set (Agent Teams disabled)",
|
|
520
|
+
fix: r.remediation
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
var init_check_agent_teams_doctor = __esm({
|
|
524
|
+
"src/cli/lib/check-agent-teams-doctor.ts"() {
|
|
525
|
+
init_checkAgentTeams();
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// src/cli/lib/check-planning-with-files.ts
|
|
530
|
+
var check_planning_with_files_exports = {};
|
|
531
|
+
__export(check_planning_with_files_exports, {
|
|
532
|
+
checkPlanningWithFiles: () => checkPlanningWithFiles
|
|
533
|
+
});
|
|
534
|
+
async function checkPlanningWithFiles() {
|
|
535
|
+
const root = join(
|
|
536
|
+
homedir(),
|
|
537
|
+
".claude",
|
|
538
|
+
"plugins",
|
|
539
|
+
"cache",
|
|
540
|
+
"planning-with-files",
|
|
541
|
+
"planning-with-files"
|
|
542
|
+
);
|
|
543
|
+
try {
|
|
544
|
+
const entries = await readdir(root);
|
|
545
|
+
const versions = entries.filter((e) => /^\d+\.\d+/.test(e));
|
|
546
|
+
if (versions.length > 0) {
|
|
547
|
+
return {
|
|
548
|
+
name: "planning-with-files plugin",
|
|
549
|
+
status: "pass",
|
|
550
|
+
message: `installed (version ${versions.join(", ")})`
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
name: "planning-with-files plugin",
|
|
555
|
+
status: "warn",
|
|
556
|
+
message: "plugin directory exists but no version subdir found",
|
|
557
|
+
fix: REMEDIATION
|
|
558
|
+
};
|
|
559
|
+
} catch {
|
|
560
|
+
return {
|
|
561
|
+
name: "planning-with-files plugin",
|
|
562
|
+
status: "warn",
|
|
563
|
+
message: "not installed (plugin cache path missing)",
|
|
564
|
+
fix: REMEDIATION
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
var REMEDIATION;
|
|
569
|
+
var init_check_planning_with_files = __esm({
|
|
570
|
+
"src/cli/lib/check-planning-with-files.ts"() {
|
|
571
|
+
REMEDIATION = "install via Claude Code plugin marketplace: `claude plugin install planning-with-files` (requires >=2.2.0 per R20.15 + D-15)";
|
|
572
|
+
}
|
|
573
|
+
});
|
|
459
574
|
async function withLock(fn) {
|
|
460
575
|
let release;
|
|
461
576
|
try {
|
|
@@ -670,7 +785,7 @@ var init_resume = __esm({
|
|
|
670
785
|
|
|
671
786
|
// package.json
|
|
672
787
|
var package_default = {
|
|
673
|
-
version: "
|
|
788
|
+
version: "2.0.1"};
|
|
674
789
|
|
|
675
790
|
// src/manifest/errors.ts
|
|
676
791
|
function instancePathToKeyPath(instancePath) {
|
|
@@ -1451,7 +1566,7 @@ function renderHumanTable(records) {
|
|
|
1451
1566
|
}
|
|
1452
1567
|
}
|
|
1453
1568
|
function pipeToJq(filterExpr, lines) {
|
|
1454
|
-
return new Promise((
|
|
1569
|
+
return new Promise((resolve8, reject) => {
|
|
1455
1570
|
const child = spawn("jq", [filterExpr], {
|
|
1456
1571
|
stdio: ["pipe", "inherit", "inherit"],
|
|
1457
1572
|
windowsHide: true
|
|
@@ -1460,12 +1575,12 @@ function pipeToJq(filterExpr, lines) {
|
|
|
1460
1575
|
const e = err2;
|
|
1461
1576
|
if (e.code === "ENOENT") {
|
|
1462
1577
|
console.error("\u2717 jq not found in PATH \u2014 run: harnessed doctor");
|
|
1463
|
-
|
|
1578
|
+
resolve8(1);
|
|
1464
1579
|
} else {
|
|
1465
1580
|
reject(err2);
|
|
1466
1581
|
}
|
|
1467
1582
|
});
|
|
1468
|
-
child.on("close", (code) =>
|
|
1583
|
+
child.on("close", (code) => resolve8(code ?? 0));
|
|
1469
1584
|
child.stdin.write(lines.join("\n"));
|
|
1470
1585
|
child.stdin.end();
|
|
1471
1586
|
});
|
|
@@ -1523,19 +1638,132 @@ function registerAuditLog(program2) {
|
|
|
1523
1638
|
}
|
|
1524
1639
|
);
|
|
1525
1640
|
}
|
|
1641
|
+
function getBackupRoot() {
|
|
1642
|
+
return join(homedir(), ".harnessed", "backups");
|
|
1643
|
+
}
|
|
1644
|
+
var HOME_DIR = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
1645
|
+
function mirrorPath(target, scope, backupDir) {
|
|
1646
|
+
const root = scope === "HOME" ? HOME_DIR : ".";
|
|
1647
|
+
const rel = root ? relative(root, target) : target;
|
|
1648
|
+
if (!rel || rel.startsWith("..")) {
|
|
1649
|
+
const flat = createHash("sha1").update(target).digest("hex").slice(0, 16);
|
|
1650
|
+
return join(backupDir, scope, flat);
|
|
1651
|
+
}
|
|
1652
|
+
return join(backupDir, scope, rel);
|
|
1653
|
+
}
|
|
1654
|
+
function detectEol(buf) {
|
|
1655
|
+
return buf.includes("\r\n") ? "crlf" : "lf";
|
|
1656
|
+
}
|
|
1657
|
+
async function backup(plan, ctx) {
|
|
1658
|
+
const filename = ctx.manifest.metadata.name;
|
|
1659
|
+
const backupId = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
1660
|
+
const backupDir = join(getBackupRoot(), backupId);
|
|
1661
|
+
try {
|
|
1662
|
+
await mkdir(backupDir, { recursive: true });
|
|
1663
|
+
} catch (err2) {
|
|
1664
|
+
return {
|
|
1665
|
+
ok: false,
|
|
1666
|
+
error: {
|
|
1667
|
+
file: filename,
|
|
1668
|
+
path: "/",
|
|
1669
|
+
message: `failed to create backup dir ${backupDir}: ${err2.message}`,
|
|
1670
|
+
line: null,
|
|
1671
|
+
column: null,
|
|
1672
|
+
keyword: "backup-mkdir-failed"
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
const entries = [];
|
|
1677
|
+
for (const file of plan.files) {
|
|
1678
|
+
let buf;
|
|
1679
|
+
try {
|
|
1680
|
+
buf = await readFile(file.target);
|
|
1681
|
+
} catch (err2) {
|
|
1682
|
+
const code = err2.code;
|
|
1683
|
+
if (code === "ENOENT" && file.oldText === "") {
|
|
1684
|
+
entries.push({
|
|
1685
|
+
target: file.target,
|
|
1686
|
+
backup: "",
|
|
1687
|
+
// sentinel: no backup written; rollback should unlink target
|
|
1688
|
+
sha1: "",
|
|
1689
|
+
eol: "lf"
|
|
1690
|
+
// moot for non-existent file; default to lf
|
|
1691
|
+
});
|
|
1692
|
+
continue;
|
|
1693
|
+
}
|
|
1694
|
+
return {
|
|
1695
|
+
ok: false,
|
|
1696
|
+
error: {
|
|
1697
|
+
file: filename,
|
|
1698
|
+
path: file.target,
|
|
1699
|
+
message: `failed to read original file for backup: ${err2.message}`,
|
|
1700
|
+
line: null,
|
|
1701
|
+
column: null,
|
|
1702
|
+
keyword: "backup-read-failed"
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
const sha1 = createHash("sha1").update(buf).digest("hex");
|
|
1707
|
+
const eol = detectEol(buf);
|
|
1708
|
+
const dest = mirrorPath(file.target, file.scope, backupDir);
|
|
1709
|
+
try {
|
|
1710
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
1711
|
+
await writeFile(dest, buf);
|
|
1712
|
+
} catch (err2) {
|
|
1713
|
+
return {
|
|
1714
|
+
ok: false,
|
|
1715
|
+
error: {
|
|
1716
|
+
file: filename,
|
|
1717
|
+
path: dest,
|
|
1718
|
+
message: `failed to write backup copy: ${err2.message}`,
|
|
1719
|
+
line: null,
|
|
1720
|
+
column: null,
|
|
1721
|
+
keyword: "backup-write-failed"
|
|
1722
|
+
}
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
entries.push({ target: file.target, backup: dest, sha1, eol });
|
|
1726
|
+
}
|
|
1727
|
+
const metadata = {
|
|
1728
|
+
installer: filename,
|
|
1729
|
+
manifest: filename,
|
|
1730
|
+
timestamp: backupId,
|
|
1731
|
+
files: entries
|
|
1732
|
+
};
|
|
1733
|
+
const metadataPath = join(backupDir, "metadata.json");
|
|
1734
|
+
try {
|
|
1735
|
+
await writeFile(metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
1736
|
+
`, "utf8");
|
|
1737
|
+
} catch (err2) {
|
|
1738
|
+
return {
|
|
1739
|
+
ok: false,
|
|
1740
|
+
error: {
|
|
1741
|
+
file: filename,
|
|
1742
|
+
path: metadataPath,
|
|
1743
|
+
message: `failed to write metadata.json: ${err2.message}`,
|
|
1744
|
+
line: null,
|
|
1745
|
+
column: null,
|
|
1746
|
+
keyword: "backup-metadata-failed"
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
return { ok: true, backupId, backupDir };
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// src/cli/backup-list.ts
|
|
1526
1754
|
function registerBackupList(program2) {
|
|
1527
1755
|
const backup2 = program2.command("backup").description("Backup snapshot operations");
|
|
1528
1756
|
backup2.command("list").description("List backup snapshots under .harnessed-backup/").action(async () => {
|
|
1529
|
-
const root =
|
|
1757
|
+
const root = getBackupRoot();
|
|
1530
1758
|
let dirs;
|
|
1531
1759
|
try {
|
|
1532
1760
|
dirs = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
1533
1761
|
} catch {
|
|
1534
|
-
console.log(
|
|
1762
|
+
console.log(`no backups found (${root} absent)`);
|
|
1535
1763
|
return;
|
|
1536
1764
|
}
|
|
1537
1765
|
if (dirs.length === 0) {
|
|
1538
|
-
console.log(
|
|
1766
|
+
console.log(`no backups found (${root} empty)`);
|
|
1539
1767
|
return;
|
|
1540
1768
|
}
|
|
1541
1769
|
for (const ts of dirs) {
|
|
@@ -1649,29 +1877,36 @@ async function checkDeprecations2() {
|
|
|
1649
1877
|
async function checkTokenBudget2() {
|
|
1650
1878
|
return (await Promise.resolve().then(() => (init_check_token_budget(), check_token_budget_exports))).checkTokenBudget();
|
|
1651
1879
|
}
|
|
1880
|
+
async function checkAgentTeamsEnv() {
|
|
1881
|
+
return (await Promise.resolve().then(() => (init_check_agent_teams_doctor(), check_agent_teams_doctor_exports))).checkAgentTeamsDoctor();
|
|
1882
|
+
}
|
|
1883
|
+
async function checkPlanningPlugin() {
|
|
1884
|
+
return (await Promise.resolve().then(() => (init_check_planning_with_files(), check_planning_with_files_exports))).checkPlanningWithFiles();
|
|
1885
|
+
}
|
|
1652
1886
|
function registerDoctor(program2) {
|
|
1653
1887
|
program2.command("doctor").description(
|
|
1654
|
-
"Preflight checks (Node / MCP scope / jq / Win bash / origin URL / gstack prefix / deprecations / token budget)"
|
|
1888
|
+
"Preflight checks (Node / MCP scope / jq / Win bash / origin URL / gstack prefix / deprecations / token budget / Agent Teams / planning-with-files)"
|
|
1655
1889
|
).option("--json", "output JSON instead of human-readable").action(async (opts) => {
|
|
1656
|
-
const [
|
|
1890
|
+
const [mcp, origin, gstack, dep, tok, at, ppwf] = await Promise.all([
|
|
1657
1891
|
checkMcpScope(),
|
|
1658
1892
|
checkOriginUrl(),
|
|
1659
1893
|
checkGstackPrefix(),
|
|
1660
|
-
// ← Phase 3.2 W1 T1.5 ADD 6th check (D-01 PROBE)
|
|
1661
1894
|
checkDeprecations2(),
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1895
|
+
checkTokenBudget2(),
|
|
1896
|
+
checkAgentTeamsEnv(),
|
|
1897
|
+
checkPlanningPlugin()
|
|
1665
1898
|
]);
|
|
1666
1899
|
const results = [
|
|
1667
1900
|
checkNodeVersion(),
|
|
1668
|
-
|
|
1901
|
+
mcp,
|
|
1669
1902
|
checkJq(),
|
|
1670
1903
|
checkWinBash(),
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1904
|
+
origin,
|
|
1905
|
+
gstack,
|
|
1906
|
+
dep,
|
|
1907
|
+
tok,
|
|
1908
|
+
at,
|
|
1909
|
+
ppwf
|
|
1675
1910
|
];
|
|
1676
1911
|
const hasFail = results.some((r) => r.status === "fail");
|
|
1677
1912
|
const hasWarn = results.some((r) => r.status === "warn");
|
|
@@ -2056,6 +2291,51 @@ async function completePhase(ctx) {
|
|
|
2056
2291
|
await complete();
|
|
2057
2292
|
}
|
|
2058
2293
|
|
|
2294
|
+
// src/routing/lib/fallbackHandlers.ts
|
|
2295
|
+
function handleMaxIterationsExceeded(err2, fallback, ctx) {
|
|
2296
|
+
const yamlShort = fallback.message.replace(
|
|
2297
|
+
/\{\{\s*args\.max_iterations\s*\}\}/g,
|
|
2298
|
+
String(ctx.maxIterations)
|
|
2299
|
+
);
|
|
2300
|
+
const truncated = (ctx.lastMessage ?? "<empty>").slice(0, 500);
|
|
2301
|
+
const uxText = `\u274C ralph-loop max-iterations exceeded (${err2.iterations}/${ctx.maxIterations}).
|
|
2302
|
+
Sub-task: ${ctx.subtaskSummary}
|
|
2303
|
+
Workflow: ${ctx.workflowName} / phase ${ctx.phaseId}
|
|
2304
|
+
Last subagent output (truncated): ${truncated}
|
|
2305
|
+
The subagent attempted ${err2.iterations} iterations without emitting verbatim "<promise>COMPLETE</promise>".
|
|
2306
|
+
This indicates one of:
|
|
2307
|
+
1. Sub-task is genuinely incomplete (escalate to user / re-scope)
|
|
2308
|
+
2. Subagent is stuck in a loop (review prompt / system instructions)
|
|
2309
|
+
3. max-iterations too low (override via --max-iterations <N>, hard upper limit 100)
|
|
2310
|
+
Manual options:
|
|
2311
|
+
A) Continue with current state: \`harnessed workflow resume --skip-completion-gate\`
|
|
2312
|
+
B) Re-run from last checkpoint: \`harnessed workflow resume --from-checkpoint\`
|
|
2313
|
+
C) Abort cleanly: exit 1
|
|
2314
|
+
Exit code: ${fallback.exit_code}
|
|
2315
|
+
${yamlShort}`;
|
|
2316
|
+
console.error(uxText);
|
|
2317
|
+
process.exit(fallback.exit_code);
|
|
2318
|
+
throw new Error("unreachable");
|
|
2319
|
+
}
|
|
2320
|
+
function handleVerbatimCompleteFail(err2, fallback, ctx) {
|
|
2321
|
+
const truncated = err2.lastMessage.slice(0, 500);
|
|
2322
|
+
const uxText = `\u274C ralph-loop verbatim COMPLETE signal missing (F33 P1).
|
|
2323
|
+
Sub-task: ${ctx.subtaskSummary}
|
|
2324
|
+
Workflow: ${ctx.workflowName} / phase ${ctx.phaseId}
|
|
2325
|
+
Last subagent output (truncated): ${truncated}
|
|
2326
|
+
The subagent's final message lacked verbatim "<promise>COMPLETE</promise>" tag.
|
|
2327
|
+
This indicates one of:
|
|
2328
|
+
1. Subagent skipped the completion-promise contract (review system prompt)
|
|
2329
|
+
2. Output format misconfigured (check outputFormat schema)
|
|
2330
|
+
Manual options:
|
|
2331
|
+
A) Re-run with explicit COMPLETE instruction in subagent prompt
|
|
2332
|
+
B) Abort cleanly: exit ${fallback.exit_code}
|
|
2333
|
+
Exit code: ${fallback.exit_code}`;
|
|
2334
|
+
console.error(uxText);
|
|
2335
|
+
process.exit(fallback.exit_code);
|
|
2336
|
+
throw new Error("unreachable");
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2059
2339
|
// src/routing/lib/promiseExtract.ts
|
|
2060
2340
|
var PROMISE_PATTERN = /<promise>([^<]+)<\/promise>/;
|
|
2061
2341
|
function extractPromise(text) {
|
|
@@ -2265,9 +2545,6 @@ async function runRouting(task, opts = {}) {
|
|
|
2265
2545
|
try {
|
|
2266
2546
|
await ensureSkillsInstalled(decision.required_skills ?? [], skillsRoot);
|
|
2267
2547
|
} catch (error) {
|
|
2268
|
-
if (error instanceof RestartRequiredError || error instanceof SkillNotInstalledError || error instanceof MissingSkillsError) {
|
|
2269
|
-
return { ok: false, phase: "install", error };
|
|
2270
|
-
}
|
|
2271
2548
|
return { ok: false, phase: "install", error };
|
|
2272
2549
|
}
|
|
2273
2550
|
let agentDef;
|
|
@@ -2294,6 +2571,11 @@ async function runRouting(task, opts = {}) {
|
|
|
2294
2571
|
onSessionIdInner?.(id);
|
|
2295
2572
|
}
|
|
2296
2573
|
});
|
|
2574
|
+
const fbCtx = {
|
|
2575
|
+
subtaskSummary: task.task,
|
|
2576
|
+
workflowName: task.task_type ?? "unknown",
|
|
2577
|
+
phaseId: opts.fallbackPhaseId ?? task.phaseId ?? "unknown"
|
|
2578
|
+
};
|
|
2297
2579
|
try {
|
|
2298
2580
|
const result = await ralphLoopWrap(wrappedSpawn, maxIter);
|
|
2299
2581
|
await completePhase({ phaseId, sessionId: capturedSessionId, status: "complete" });
|
|
@@ -2302,10 +2584,16 @@ async function runRouting(task, opts = {}) {
|
|
|
2302
2584
|
} catch (error) {
|
|
2303
2585
|
if (error instanceof MaxIterationsExceededError) {
|
|
2304
2586
|
emitAudit(task, decision, matched, "max-iter", capturedSessionId);
|
|
2587
|
+
if (opts.fallbackConfig)
|
|
2588
|
+
handleMaxIterationsExceeded(error, opts.fallbackConfig, {
|
|
2589
|
+
...fbCtx,
|
|
2590
|
+
maxIterations: maxIter
|
|
2591
|
+
});
|
|
2305
2592
|
return { aborted: true, reason: error.message };
|
|
2306
2593
|
}
|
|
2307
2594
|
if (error instanceof VerbatimCompleteFailError) {
|
|
2308
2595
|
emitAudit(task, decision, matched, "verbatim-fail", capturedSessionId);
|
|
2596
|
+
if (opts.fallbackConfig) handleVerbatimCompleteFail(error, opts.fallbackConfig, fbCtx);
|
|
2309
2597
|
return { ok: false, phase: "verbatim", error };
|
|
2310
2598
|
}
|
|
2311
2599
|
emitAudit(task, decision, matched, "spawn-err", capturedSessionId);
|
|
@@ -2346,6 +2634,70 @@ var PhasesSchema = Type.Object(
|
|
|
2346
2634
|
{ additionalProperties: false }
|
|
2347
2635
|
);
|
|
2348
2636
|
|
|
2637
|
+
// src/workflow/schema/workflow.ts
|
|
2638
|
+
init_schemaVersion();
|
|
2639
|
+
var ModelTier2 = Type.Union([Type.Literal("haiku"), Type.Literal("sonnet"), Type.Literal("opus")]);
|
|
2640
|
+
var OnAction = Type.Union([Type.Literal("skip"), Type.Literal("invoke")]);
|
|
2641
|
+
var OnClause = Type.Object(
|
|
2642
|
+
{
|
|
2643
|
+
if: Type.String(),
|
|
2644
|
+
// expr-eval expression OR judgments.<file>.<gate>.fires ref
|
|
2645
|
+
invoke: Type.Optional(Type.String()),
|
|
2646
|
+
// '{{ capabilities.<name>.cmd }}' OR literal
|
|
2647
|
+
action: Type.Optional(OnAction)
|
|
2648
|
+
},
|
|
2649
|
+
{ additionalProperties: false }
|
|
2650
|
+
);
|
|
2651
|
+
var FallbackMaxIterationsExceeded = Type.Object(
|
|
2652
|
+
{
|
|
2653
|
+
action: Type.Literal("emit_warning_and_halt"),
|
|
2654
|
+
// R20.10 acceptance c "explicit NOT silent"
|
|
2655
|
+
message: Type.String(),
|
|
2656
|
+
exit_code: Type.Number()
|
|
2657
|
+
},
|
|
2658
|
+
{ additionalProperties: false }
|
|
2659
|
+
);
|
|
2660
|
+
var PhaseFallback = Type.Object(
|
|
2661
|
+
{
|
|
2662
|
+
max_iterations_exceeded: Type.Optional(FallbackMaxIterationsExceeded)
|
|
2663
|
+
},
|
|
2664
|
+
{ additionalProperties: false }
|
|
2665
|
+
);
|
|
2666
|
+
var WorkflowPhaseV2 = Type.Object(
|
|
2667
|
+
{
|
|
2668
|
+
id: Type.String({ minLength: 1 }),
|
|
2669
|
+
name: Type.Optional(Type.String()),
|
|
2670
|
+
upstream: Type.Optional(Type.String()),
|
|
2671
|
+
capability: Type.Optional(Type.String()),
|
|
2672
|
+
// '{{ capabilities.ralph-loop.cmd }}'
|
|
2673
|
+
model: Type.Optional(ModelTier2),
|
|
2674
|
+
invokes: Type.Optional(Type.String()),
|
|
2675
|
+
// legacy slash-cmd OR JINJA template
|
|
2676
|
+
args: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
2677
|
+
gate: Type.Optional(Type.String()),
|
|
2678
|
+
// judgments.<file>.<gate>.fires 4-level ref
|
|
2679
|
+
on: Type.Optional(Type.Array(OnClause)),
|
|
2680
|
+
parallelism: Type.Optional(Type.String()),
|
|
2681
|
+
// judgments.parallelism-gate.<route>.fires
|
|
2682
|
+
fallback: Type.Optional(PhaseFallback),
|
|
2683
|
+
max_iterations: Type.Optional(
|
|
2684
|
+
Type.Union([Type.Number(), Type.String()])
|
|
2685
|
+
// numeric literal OR jinja '{{ defaults.x.y }}'
|
|
2686
|
+
),
|
|
2687
|
+
artifacts_expected: Type.Optional(Type.Array(Type.String()))
|
|
2688
|
+
},
|
|
2689
|
+
{ additionalProperties: false }
|
|
2690
|
+
);
|
|
2691
|
+
var WorkflowSchemaV2 = Type.Object(
|
|
2692
|
+
{
|
|
2693
|
+
schema_version: Type.Literal(SCHEMA_VERSIONS.workflow),
|
|
2694
|
+
workflow: Type.String({ minLength: 1 }),
|
|
2695
|
+
description: Type.Optional(Type.String()),
|
|
2696
|
+
phases: Type.Array(WorkflowPhaseV2, { minItems: 1 })
|
|
2697
|
+
},
|
|
2698
|
+
{ additionalProperties: false }
|
|
2699
|
+
);
|
|
2700
|
+
|
|
2349
2701
|
// src/workflow/loadPhases.ts
|
|
2350
2702
|
var PhasesValidationError = class extends Error {
|
|
2351
2703
|
constructor(errors) {
|
|
@@ -2358,10 +2710,18 @@ var PhasesValidationError = class extends Error {
|
|
|
2358
2710
|
function loadPhases(yamlPath, vars) {
|
|
2359
2711
|
const raw = readFileSync(yamlPath, "utf8");
|
|
2360
2712
|
const parsed = parse(raw);
|
|
2361
|
-
|
|
2362
|
-
|
|
2713
|
+
const isV2 = parsed?.schema_version === "harnessed.workflow.v2";
|
|
2714
|
+
if (isV2) {
|
|
2715
|
+
if (!Value.Check(WorkflowSchemaV2, parsed)) {
|
|
2716
|
+
throw new PhasesValidationError([...Value.Errors(WorkflowSchemaV2, parsed)]);
|
|
2717
|
+
}
|
|
2718
|
+
} else {
|
|
2719
|
+
if (!Value.Check(PhasesSchema, parsed)) {
|
|
2720
|
+
throw new PhasesValidationError([...Value.Errors(PhasesSchema, parsed)]);
|
|
2721
|
+
}
|
|
2363
2722
|
}
|
|
2364
|
-
|
|
2723
|
+
const validated = parsed;
|
|
2724
|
+
return validated;
|
|
2365
2725
|
}
|
|
2366
2726
|
|
|
2367
2727
|
// src/cli/lib/validateFlags.ts
|
|
@@ -2407,9 +2767,20 @@ function registerExecuteTask(program2) {
|
|
|
2407
2767
|
);
|
|
2408
2768
|
process.exit(0);
|
|
2409
2769
|
}
|
|
2770
|
+
let fallbackConfig;
|
|
2771
|
+
let fallbackPhaseId;
|
|
2772
|
+
for (const ph of phases.phases) {
|
|
2773
|
+
if ("fallback" in ph && ph.fallback?.max_iterations_exceeded) {
|
|
2774
|
+
fallbackConfig = ph.fallback.max_iterations_exceeded;
|
|
2775
|
+
fallbackPhaseId = ph.id;
|
|
2776
|
+
break;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2410
2779
|
const result = await runRouting(taskCtx, {
|
|
2411
2780
|
maxIterations: raw.maxIterations ?? 20,
|
|
2412
|
-
...raw.model ? { agentOpts: { modelOverride: raw.model } } : {}
|
|
2781
|
+
...raw.model ? { agentOpts: { modelOverride: raw.model } } : {},
|
|
2782
|
+
...fallbackConfig ? { fallbackConfig } : {},
|
|
2783
|
+
...fallbackPhaseId ? { fallbackPhaseId } : {}
|
|
2413
2784
|
});
|
|
2414
2785
|
if ("aborted" in result) {
|
|
2415
2786
|
console.error(`aborted: ${result.reason}`);
|
|
@@ -2463,12 +2834,12 @@ function registerGc(program2) {
|
|
|
2463
2834
|
return;
|
|
2464
2835
|
}
|
|
2465
2836
|
const keepLast = Number.parseInt(opts.keepLast ?? "0", 10);
|
|
2466
|
-
const root =
|
|
2837
|
+
const root = getBackupRoot();
|
|
2467
2838
|
let dirs;
|
|
2468
2839
|
try {
|
|
2469
2840
|
dirs = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
2470
2841
|
} catch {
|
|
2471
|
-
console.log(
|
|
2842
|
+
console.log(`no backups found (${root} absent) \u2014 nothing to gc`);
|
|
2472
2843
|
return;
|
|
2473
2844
|
}
|
|
2474
2845
|
const cutoff = Date.now() - olderMs;
|
|
@@ -2505,114 +2876,6 @@ function registerGc(program2) {
|
|
|
2505
2876
|
if (dryRun) console.log("\n(dry-run \u2014 re-run with --apply to actually delete)");
|
|
2506
2877
|
});
|
|
2507
2878
|
}
|
|
2508
|
-
var HOME_DIR = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
2509
|
-
function mirrorPath(target, scope, backupDir) {
|
|
2510
|
-
const root = scope === "HOME" ? HOME_DIR : ".";
|
|
2511
|
-
const rel = root ? relative(root, target) : target;
|
|
2512
|
-
if (!rel || rel.startsWith("..")) {
|
|
2513
|
-
const flat = createHash("sha1").update(target).digest("hex").slice(0, 16);
|
|
2514
|
-
return join(backupDir, scope, flat);
|
|
2515
|
-
}
|
|
2516
|
-
return join(backupDir, scope, rel);
|
|
2517
|
-
}
|
|
2518
|
-
function detectEol(buf) {
|
|
2519
|
-
return buf.includes("\r\n") ? "crlf" : "lf";
|
|
2520
|
-
}
|
|
2521
|
-
async function backup(plan, ctx) {
|
|
2522
|
-
const filename = ctx.manifest.metadata.name;
|
|
2523
|
-
const backupId = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
2524
|
-
const backupDir = join(ctx.cwd, ".harnessed-backup", backupId);
|
|
2525
|
-
try {
|
|
2526
|
-
await mkdir(backupDir, { recursive: true });
|
|
2527
|
-
} catch (err2) {
|
|
2528
|
-
return {
|
|
2529
|
-
ok: false,
|
|
2530
|
-
error: {
|
|
2531
|
-
file: filename,
|
|
2532
|
-
path: "/",
|
|
2533
|
-
message: `failed to create backup dir ${backupDir}: ${err2.message}`,
|
|
2534
|
-
line: null,
|
|
2535
|
-
column: null,
|
|
2536
|
-
keyword: "backup-mkdir-failed"
|
|
2537
|
-
}
|
|
2538
|
-
};
|
|
2539
|
-
}
|
|
2540
|
-
const entries = [];
|
|
2541
|
-
for (const file of plan.files) {
|
|
2542
|
-
let buf;
|
|
2543
|
-
try {
|
|
2544
|
-
buf = await readFile(file.target);
|
|
2545
|
-
} catch (err2) {
|
|
2546
|
-
const code = err2.code;
|
|
2547
|
-
if (code === "ENOENT" && file.oldText === "") {
|
|
2548
|
-
entries.push({
|
|
2549
|
-
target: file.target,
|
|
2550
|
-
backup: "",
|
|
2551
|
-
// sentinel: no backup written; rollback should unlink target
|
|
2552
|
-
sha1: "",
|
|
2553
|
-
eol: "lf"
|
|
2554
|
-
// moot for non-existent file; default to lf
|
|
2555
|
-
});
|
|
2556
|
-
continue;
|
|
2557
|
-
}
|
|
2558
|
-
return {
|
|
2559
|
-
ok: false,
|
|
2560
|
-
error: {
|
|
2561
|
-
file: filename,
|
|
2562
|
-
path: file.target,
|
|
2563
|
-
message: `failed to read original file for backup: ${err2.message}`,
|
|
2564
|
-
line: null,
|
|
2565
|
-
column: null,
|
|
2566
|
-
keyword: "backup-read-failed"
|
|
2567
|
-
}
|
|
2568
|
-
};
|
|
2569
|
-
}
|
|
2570
|
-
const sha1 = createHash("sha1").update(buf).digest("hex");
|
|
2571
|
-
const eol = detectEol(buf);
|
|
2572
|
-
const dest = mirrorPath(file.target, file.scope, backupDir);
|
|
2573
|
-
try {
|
|
2574
|
-
await mkdir(dirname(dest), { recursive: true });
|
|
2575
|
-
await writeFile(dest, buf);
|
|
2576
|
-
} catch (err2) {
|
|
2577
|
-
return {
|
|
2578
|
-
ok: false,
|
|
2579
|
-
error: {
|
|
2580
|
-
file: filename,
|
|
2581
|
-
path: dest,
|
|
2582
|
-
message: `failed to write backup copy: ${err2.message}`,
|
|
2583
|
-
line: null,
|
|
2584
|
-
column: null,
|
|
2585
|
-
keyword: "backup-write-failed"
|
|
2586
|
-
}
|
|
2587
|
-
};
|
|
2588
|
-
}
|
|
2589
|
-
entries.push({ target: file.target, backup: dest, sha1, eol });
|
|
2590
|
-
}
|
|
2591
|
-
const metadata = {
|
|
2592
|
-
installer: filename,
|
|
2593
|
-
manifest: filename,
|
|
2594
|
-
timestamp: backupId,
|
|
2595
|
-
files: entries
|
|
2596
|
-
};
|
|
2597
|
-
const metadataPath = join(backupDir, "metadata.json");
|
|
2598
|
-
try {
|
|
2599
|
-
await writeFile(metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
2600
|
-
`, "utf8");
|
|
2601
|
-
} catch (err2) {
|
|
2602
|
-
return {
|
|
2603
|
-
ok: false,
|
|
2604
|
-
error: {
|
|
2605
|
-
file: filename,
|
|
2606
|
-
path: metadataPath,
|
|
2607
|
-
message: `failed to write metadata.json: ${err2.message}`,
|
|
2608
|
-
line: null,
|
|
2609
|
-
column: null,
|
|
2610
|
-
keyword: "backup-metadata-failed"
|
|
2611
|
-
}
|
|
2612
|
-
};
|
|
2613
|
-
}
|
|
2614
|
-
return { ok: true, backupId, backupDir };
|
|
2615
|
-
}
|
|
2616
2879
|
async function confirmAt(level, ctx) {
|
|
2617
2880
|
if (level === "L4" && !ctx.opts.system) {
|
|
2618
2881
|
if (!ctx.opts.nonInteractive) {
|
|
@@ -2894,7 +3157,7 @@ var installCcHookAdd = async (ctx) => {
|
|
|
2894
3157
|
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath] };
|
|
2895
3158
|
};
|
|
2896
3159
|
function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
2897
|
-
return new Promise((
|
|
3160
|
+
return new Promise((resolve8) => {
|
|
2898
3161
|
const isWin = process.platform === "win32";
|
|
2899
3162
|
const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
|
|
2900
3163
|
let stderr = "";
|
|
@@ -2903,15 +3166,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
|
2903
3166
|
});
|
|
2904
3167
|
const timer = setTimeout(() => {
|
|
2905
3168
|
child.kill("SIGKILL");
|
|
2906
|
-
|
|
3169
|
+
resolve8({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
|
|
2907
3170
|
}, timeoutMs);
|
|
2908
3171
|
child.on("error", (e) => {
|
|
2909
3172
|
clearTimeout(timer);
|
|
2910
|
-
|
|
3173
|
+
resolve8({ exitCode: -1, stderr: `${stderr}${e.message}` });
|
|
2911
3174
|
});
|
|
2912
3175
|
child.on("close", (code) => {
|
|
2913
3176
|
clearTimeout(timer);
|
|
2914
|
-
|
|
3177
|
+
resolve8({ exitCode: code ?? -1, stderr });
|
|
2915
3178
|
});
|
|
2916
3179
|
});
|
|
2917
3180
|
}
|
|
@@ -3052,7 +3315,7 @@ ${newEntry}
|
|
|
3052
3315
|
)
|
|
3053
3316
|
};
|
|
3054
3317
|
}
|
|
3055
|
-
const vr = await new Promise((
|
|
3318
|
+
const vr = await new Promise((resolve8) => {
|
|
3056
3319
|
const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
|
|
3057
3320
|
let stderr = "";
|
|
3058
3321
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
@@ -3060,15 +3323,15 @@ ${newEntry}
|
|
|
3060
3323
|
});
|
|
3061
3324
|
const timer = setTimeout(() => {
|
|
3062
3325
|
child.kill("SIGKILL");
|
|
3063
|
-
|
|
3326
|
+
resolve8({ exitCode: -1, stderr: `${stderr}[timeout]` });
|
|
3064
3327
|
}, 15e3);
|
|
3065
3328
|
child.on("error", (e) => {
|
|
3066
3329
|
clearTimeout(timer);
|
|
3067
|
-
|
|
3330
|
+
resolve8({ exitCode: -1, stderr: e.message });
|
|
3068
3331
|
});
|
|
3069
3332
|
child.on("close", (code) => {
|
|
3070
3333
|
clearTimeout(timer);
|
|
3071
|
-
|
|
3334
|
+
resolve8({ exitCode: code ?? -1, stderr });
|
|
3072
3335
|
});
|
|
3073
3336
|
});
|
|
3074
3337
|
if (vr.exitCode !== 0) {
|
|
@@ -3124,10 +3387,10 @@ async function spawnCmd(ctx, cmd, args) {
|
|
|
3124
3387
|
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
3125
3388
|
stderr += chunk;
|
|
3126
3389
|
});
|
|
3127
|
-
return await new Promise((
|
|
3390
|
+
return await new Promise((resolve8) => {
|
|
3128
3391
|
const timer = setTimeout(() => {
|
|
3129
3392
|
child.kill("SIGKILL");
|
|
3130
|
-
|
|
3393
|
+
resolve8({
|
|
3131
3394
|
ok: false,
|
|
3132
3395
|
phase: "spawn",
|
|
3133
3396
|
error: {
|
|
@@ -3142,7 +3405,7 @@ async function spawnCmd(ctx, cmd, args) {
|
|
|
3142
3405
|
}, timeoutMs);
|
|
3143
3406
|
child.on("error", (err2) => {
|
|
3144
3407
|
clearTimeout(timer);
|
|
3145
|
-
|
|
3408
|
+
resolve8({
|
|
3146
3409
|
ok: false,
|
|
3147
3410
|
phase: "spawn",
|
|
3148
3411
|
error: {
|
|
@@ -3157,14 +3420,14 @@ async function spawnCmd(ctx, cmd, args) {
|
|
|
3157
3420
|
});
|
|
3158
3421
|
child.on("close", (code) => {
|
|
3159
3422
|
clearTimeout(timer);
|
|
3160
|
-
|
|
3423
|
+
resolve8({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
|
|
3161
3424
|
});
|
|
3162
3425
|
});
|
|
3163
3426
|
}
|
|
3164
3427
|
|
|
3165
3428
|
// src/installers/gitCloneWithSetup.ts
|
|
3166
3429
|
function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
3167
|
-
return new Promise((
|
|
3430
|
+
return new Promise((resolve8) => {
|
|
3168
3431
|
const isWin = process.platform === "win32";
|
|
3169
3432
|
const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
|
|
3170
3433
|
let stdout2 = "";
|
|
@@ -3173,15 +3436,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
|
3173
3436
|
});
|
|
3174
3437
|
const timer = setTimeout(() => {
|
|
3175
3438
|
child.kill("SIGKILL");
|
|
3176
|
-
|
|
3439
|
+
resolve8({ sha: "", exit: -1 });
|
|
3177
3440
|
}, timeoutMs);
|
|
3178
3441
|
child.on("error", () => {
|
|
3179
3442
|
clearTimeout(timer);
|
|
3180
|
-
|
|
3443
|
+
resolve8({ sha: "", exit: -1 });
|
|
3181
3444
|
});
|
|
3182
3445
|
child.on("close", (code) => {
|
|
3183
3446
|
clearTimeout(timer);
|
|
3184
|
-
|
|
3447
|
+
resolve8({ sha: stdout2.trim(), exit: code ?? -1 });
|
|
3185
3448
|
});
|
|
3186
3449
|
});
|
|
3187
3450
|
}
|
|
@@ -3521,7 +3784,7 @@ ${newEntry}
|
|
|
3521
3784
|
)
|
|
3522
3785
|
};
|
|
3523
3786
|
}
|
|
3524
|
-
const vr = await new Promise((
|
|
3787
|
+
const vr = await new Promise((resolve8) => {
|
|
3525
3788
|
const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
|
|
3526
3789
|
let stderr = "";
|
|
3527
3790
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
@@ -3529,15 +3792,15 @@ ${newEntry}
|
|
|
3529
3792
|
});
|
|
3530
3793
|
const timer = setTimeout(() => {
|
|
3531
3794
|
child.kill("SIGKILL");
|
|
3532
|
-
|
|
3795
|
+
resolve8({ exitCode: -1, stderr: `${stderr}[timeout]` });
|
|
3533
3796
|
}, 15e3);
|
|
3534
3797
|
child.on("error", (e) => {
|
|
3535
3798
|
clearTimeout(timer);
|
|
3536
|
-
|
|
3799
|
+
resolve8({ exitCode: -1, stderr: e.message });
|
|
3537
3800
|
});
|
|
3538
3801
|
child.on("close", (code) => {
|
|
3539
3802
|
clearTimeout(timer);
|
|
3540
|
-
|
|
3803
|
+
resolve8({ exitCode: code ?? -1, stderr });
|
|
3541
3804
|
});
|
|
3542
3805
|
});
|
|
3543
3806
|
if (vr.exitCode !== 0) {
|
|
@@ -3669,7 +3932,7 @@ ${newEntry}
|
|
|
3669
3932
|
)
|
|
3670
3933
|
};
|
|
3671
3934
|
}
|
|
3672
|
-
const vr = await new Promise((
|
|
3935
|
+
const vr = await new Promise((resolve8) => {
|
|
3673
3936
|
const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
|
|
3674
3937
|
let stderr = "";
|
|
3675
3938
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
@@ -3677,15 +3940,15 @@ ${newEntry}
|
|
|
3677
3940
|
});
|
|
3678
3941
|
const timer = setTimeout(() => {
|
|
3679
3942
|
child.kill("SIGKILL");
|
|
3680
|
-
|
|
3943
|
+
resolve8({ exitCode: -1, stderr: `${stderr}[timeout]` });
|
|
3681
3944
|
}, 15e3);
|
|
3682
3945
|
child.on("error", (e) => {
|
|
3683
3946
|
clearTimeout(timer);
|
|
3684
|
-
|
|
3947
|
+
resolve8({ exitCode: -1, stderr: e.message });
|
|
3685
3948
|
});
|
|
3686
3949
|
child.on("close", (code) => {
|
|
3687
3950
|
clearTimeout(timer);
|
|
3688
|
-
|
|
3951
|
+
resolve8({ exitCode: code ?? -1, stderr });
|
|
3689
3952
|
});
|
|
3690
3953
|
});
|
|
3691
3954
|
if (vr.exitCode !== 0) {
|
|
@@ -4257,7 +4520,7 @@ function normalizeEol(buf, eol) {
|
|
|
4257
4520
|
}
|
|
4258
4521
|
function registerRollback(program2) {
|
|
4259
4522
|
program2.command("rollback <timestamp>").description("Restore files from a backup snapshot (preserves original LF/CRLF)").action(async (timestamp) => {
|
|
4260
|
-
const dir =
|
|
4523
|
+
const dir = join(getBackupRoot(), timestamp);
|
|
4261
4524
|
const metaPath = join(dir, "metadata.json");
|
|
4262
4525
|
let meta;
|
|
4263
4526
|
try {
|
|
@@ -4298,12 +4561,96 @@ function registerRollback(program2) {
|
|
|
4298
4561
|
console.log(`restored ${meta.files.length} file(s) from ${timestamp}`);
|
|
4299
4562
|
});
|
|
4300
4563
|
}
|
|
4564
|
+
init_checkAgentTeams();
|
|
4301
4565
|
var PHASE_212 = /* @__PURE__ */ new Set([
|
|
4302
4566
|
"cc-plugin-marketplace",
|
|
4303
4567
|
"git-clone-with-setup",
|
|
4304
4568
|
"npx-skill-installer",
|
|
4305
4569
|
"mcp-http-add"
|
|
4306
4570
|
]);
|
|
4571
|
+
async function warnIfAgentTeamsMissing() {
|
|
4572
|
+
const r = await checkAgentTeams();
|
|
4573
|
+
if (r.status !== "missing") return;
|
|
4574
|
+
console.warn("\n\u26A0\uFE0F Agent Teams \u672A\u542F\u7528 \u2014 parallelism-gate \u5347\u7EA7\u8DEF\u5F84\u4E0D\u53EF\u7528");
|
|
4575
|
+
console.warn(" \u4FEE\u590D: claude config set env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS 1");
|
|
4576
|
+
console.warn(
|
|
4577
|
+
" \u8BF4\u660E: harnessed v2.0 \u4E09\u5C42\u6808\u65B9\u6CD5\u8BBA parallelism-gate \u5347\u7EA7\u8DEF\u5F84\u9700 CC 2.1.133+ Agent Teams enable (sister ~/.claude/rules/agent-teams.md)"
|
|
4578
|
+
);
|
|
4579
|
+
console.warn(
|
|
4580
|
+
" \u4E0D\u963B\u585E setup,\u540E\u7EED parallelism-gate workflow phase \u89E6\u53D1\u65F6\u81EA\u52A8\u964D\u7EA7 subagent fan-out\n"
|
|
4581
|
+
);
|
|
4582
|
+
}
|
|
4583
|
+
async function scanWorkflowsWithSkill(workflowsDir, entries) {
|
|
4584
|
+
const out = [];
|
|
4585
|
+
for (const entry of entries.sort()) {
|
|
4586
|
+
const src = join(workflowsDir, entry);
|
|
4587
|
+
try {
|
|
4588
|
+
const s = await stat(src);
|
|
4589
|
+
if (!s.isDirectory()) continue;
|
|
4590
|
+
await stat(join(src, "SKILL.md"));
|
|
4591
|
+
out.push(entry);
|
|
4592
|
+
} catch {
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
return out;
|
|
4596
|
+
}
|
|
4597
|
+
async function runStepBInstall(manifestPaths) {
|
|
4598
|
+
const opts = {
|
|
4599
|
+
apply: true,
|
|
4600
|
+
dryRun: false,
|
|
4601
|
+
system: false,
|
|
4602
|
+
nonInteractive: true,
|
|
4603
|
+
fullDiff: false,
|
|
4604
|
+
color: "auto"
|
|
4605
|
+
};
|
|
4606
|
+
const start = Date.now();
|
|
4607
|
+
const settled = await Promise.allSettled(
|
|
4608
|
+
manifestPaths.map(async (path) => {
|
|
4609
|
+
let yamlSrc;
|
|
4610
|
+
try {
|
|
4611
|
+
yamlSrc = await readFile(path, "utf8");
|
|
4612
|
+
} catch (e) {
|
|
4613
|
+
return { status: "failed", name: path, reason: `read: ${e.message}` };
|
|
4614
|
+
}
|
|
4615
|
+
const v = validateManifestFile(yamlSrc, path);
|
|
4616
|
+
if (!v.ok) {
|
|
4617
|
+
return {
|
|
4618
|
+
status: "failed",
|
|
4619
|
+
name: path,
|
|
4620
|
+
reason: `validate: ${v.errors[0]?.message ?? "unknown"}`
|
|
4621
|
+
};
|
|
4622
|
+
}
|
|
4623
|
+
const name = v.manifest.metadata.name;
|
|
4624
|
+
const method = v.manifest.spec.install.method;
|
|
4625
|
+
if (PHASE_212.has(method)) return { status: "skipped", name };
|
|
4626
|
+
const r = await runInstall(v.manifest, opts);
|
|
4627
|
+
if ("aborted" in r) return { status: "skipped", name };
|
|
4628
|
+
if (r.ok && "alreadyInstalled" in r && r.alreadyInstalled)
|
|
4629
|
+
return { status: "already-installed", name };
|
|
4630
|
+
if (r.ok) return { status: "installed", name };
|
|
4631
|
+
return { status: "failed", name, reason: r.error.message };
|
|
4632
|
+
})
|
|
4633
|
+
);
|
|
4634
|
+
const installed = [];
|
|
4635
|
+
const alreadyInstalled = [];
|
|
4636
|
+
const skipped = [];
|
|
4637
|
+
const failed = [];
|
|
4638
|
+
for (const s of settled) {
|
|
4639
|
+
const v = s.status === "fulfilled" ? s.value : {
|
|
4640
|
+
status: "failed",
|
|
4641
|
+
name: "?",
|
|
4642
|
+
reason: String(s.reason)
|
|
4643
|
+
};
|
|
4644
|
+
if (v.status === "installed") installed.push(v.name);
|
|
4645
|
+
else if (v.status === "already-installed") alreadyInstalled.push(v.name);
|
|
4646
|
+
else if (v.status === "skipped") skipped.push(v.name);
|
|
4647
|
+
else
|
|
4648
|
+
failed.push(`${v.name}: ${v.reason}`);
|
|
4649
|
+
}
|
|
4650
|
+
return { installed, alreadyInstalled, skipped, failed, elapsedMs: Date.now() - start };
|
|
4651
|
+
}
|
|
4652
|
+
|
|
4653
|
+
// src/cli/setup.ts
|
|
4307
4654
|
async function listBaseManifests2(pkgRoot) {
|
|
4308
4655
|
const out = [];
|
|
4309
4656
|
for (const d of ["manifests/tools", "manifests/skill-packs"]) {
|
|
@@ -4323,6 +4670,7 @@ function registerSetup(program2) {
|
|
|
4323
4670
|
const pkgRoot = getPackageRoot();
|
|
4324
4671
|
const workflowsDir = resolve(pkgRoot, "workflows");
|
|
4325
4672
|
const skillsBase = resolve(homedir(), ".claude", "skills");
|
|
4673
|
+
await warnIfAgentTeamsMissing();
|
|
4326
4674
|
let entries;
|
|
4327
4675
|
try {
|
|
4328
4676
|
entries = await readdir(workflowsDir);
|
|
@@ -4330,17 +4678,7 @@ function registerSetup(program2) {
|
|
|
4330
4678
|
console.error(`error: workflows directory not found at ${workflowsDir}`);
|
|
4331
4679
|
process.exit(1);
|
|
4332
4680
|
}
|
|
4333
|
-
const toInstall =
|
|
4334
|
-
for (const entry of entries.sort()) {
|
|
4335
|
-
const src = join(workflowsDir, entry);
|
|
4336
|
-
try {
|
|
4337
|
-
const s = await stat(src);
|
|
4338
|
-
if (!s.isDirectory()) continue;
|
|
4339
|
-
await stat(join(src, "SKILL.md"));
|
|
4340
|
-
toInstall.push(entry);
|
|
4341
|
-
} catch {
|
|
4342
|
-
}
|
|
4343
|
-
}
|
|
4681
|
+
const toInstall = await scanWorkflowsWithSkill(workflowsDir, entries);
|
|
4344
4682
|
if (toInstall.length === 0) {
|
|
4345
4683
|
console.log("setup: no workflow directories with SKILL.md found \u2014 nothing to install");
|
|
4346
4684
|
process.exit(2);
|
|
@@ -4372,88 +4710,35 @@ function registerSetup(program2) {
|
|
|
4372
4710
|
`
|
|
4373
4711
|
Step A complete: ${skillsInstalled} workflow skill(s) installed to ${skillsBase}`
|
|
4374
4712
|
);
|
|
4375
|
-
const opts = {
|
|
4376
|
-
apply: true,
|
|
4377
|
-
dryRun: false,
|
|
4378
|
-
system: false,
|
|
4379
|
-
nonInteractive: true,
|
|
4380
|
-
fullDiff: false,
|
|
4381
|
-
color: "auto"
|
|
4382
|
-
};
|
|
4383
4713
|
const manifestPaths = await listBaseManifests2(pkgRoot);
|
|
4384
|
-
const
|
|
4385
|
-
const
|
|
4386
|
-
manifestPaths.map(async (path) => {
|
|
4387
|
-
let yamlSrc;
|
|
4388
|
-
try {
|
|
4389
|
-
yamlSrc = await readFile(path, "utf8");
|
|
4390
|
-
} catch (e) {
|
|
4391
|
-
return {
|
|
4392
|
-
status: "failed",
|
|
4393
|
-
name: path,
|
|
4394
|
-
reason: `read: ${e.message}`
|
|
4395
|
-
};
|
|
4396
|
-
}
|
|
4397
|
-
const v = validateManifestFile(yamlSrc, path);
|
|
4398
|
-
if (!v.ok) {
|
|
4399
|
-
return {
|
|
4400
|
-
status: "failed",
|
|
4401
|
-
name: path,
|
|
4402
|
-
reason: `validate: ${v.errors[0]?.message ?? "unknown"}`
|
|
4403
|
-
};
|
|
4404
|
-
}
|
|
4405
|
-
const name = v.manifest.metadata.name;
|
|
4406
|
-
const method = v.manifest.spec.install.method;
|
|
4407
|
-
if (PHASE_212.has(method)) {
|
|
4408
|
-
return { status: "skipped", name };
|
|
4409
|
-
}
|
|
4410
|
-
const r = await runInstall(v.manifest, opts);
|
|
4411
|
-
if ("aborted" in r) return { status: "skipped", name };
|
|
4412
|
-
if (r.ok && "alreadyInstalled" in r && r.alreadyInstalled)
|
|
4413
|
-
return { status: "already-installed", name };
|
|
4414
|
-
if (r.ok) return { status: "installed", name };
|
|
4415
|
-
return { status: "failed", name, reason: r.error.message };
|
|
4416
|
-
})
|
|
4417
|
-
);
|
|
4418
|
-
const baseInstalled = [];
|
|
4419
|
-
const baseAlreadyInstalled = [];
|
|
4420
|
-
const baseSkipped = [];
|
|
4421
|
-
const baseFailed = [];
|
|
4422
|
-
for (const s of settled) {
|
|
4423
|
-
const v = s.status === "fulfilled" ? s.value : {
|
|
4424
|
-
status: "failed",
|
|
4425
|
-
name: "?",
|
|
4426
|
-
reason: String(s.reason)
|
|
4427
|
-
};
|
|
4428
|
-
if (v.status === "installed") baseInstalled.push(v.name);
|
|
4429
|
-
else if (v.status === "already-installed") baseAlreadyInstalled.push(v.name);
|
|
4430
|
-
else if (v.status === "skipped") baseSkipped.push(v.name);
|
|
4431
|
-
else
|
|
4432
|
-
baseFailed.push(
|
|
4433
|
-
`${v.name}: ${v.reason}`
|
|
4434
|
-
);
|
|
4435
|
-
}
|
|
4436
|
-
const stepBMs = ((Date.now() - stepBStart) / 1e3).toFixed(1);
|
|
4714
|
+
const b = await runStepBInstall(manifestPaths);
|
|
4715
|
+
const stepBMs = (b.elapsedMs / 1e3).toFixed(1);
|
|
4437
4716
|
console.log(
|
|
4438
|
-
`Step B complete: ${
|
|
4717
|
+
`Step B complete: ${b.installed.length} manifest(s) installed / ${b.alreadyInstalled.length} already-installed / ${b.skipped.length} skipped / ${b.failed.length} failed [parallel ${stepBMs}s]`
|
|
4439
4718
|
);
|
|
4440
|
-
for (const n of
|
|
4441
|
-
for (const n of
|
|
4719
|
+
for (const n of b.installed) console.log(` [B] installed ${n}`);
|
|
4720
|
+
for (const n of b.alreadyInstalled)
|
|
4442
4721
|
console.log(
|
|
4443
4722
|
` [B] already-installed ${n} \u2014 run \`/mcp\` in Claude Code to verify connection`
|
|
4444
4723
|
);
|
|
4445
|
-
for (const n of
|
|
4446
|
-
for (const n of
|
|
4724
|
+
for (const n of b.skipped) console.log(` [B] skipped ${n}`);
|
|
4725
|
+
for (const n of b.failed) console.error(` [B] failed ${n}`);
|
|
4447
4726
|
console.log(
|
|
4448
4727
|
`
|
|
4449
|
-
setup complete: ${skillsInstalled} workflow skill(s) + ${
|
|
4728
|
+
setup complete: ${skillsInstalled} workflow skill(s) + ${b.installed.length + b.alreadyInstalled.length} base manifest(s) configured`
|
|
4450
4729
|
);
|
|
4451
|
-
if (
|
|
4730
|
+
if (b.alreadyInstalled.length > 0 || b.installed.length > 0) {
|
|
4452
4731
|
console.log(
|
|
4453
4732
|
`
|
|
4454
4733
|
MCP servers configured. Run \`/mcp\` in Claude Code to verify each server's connection status. If a server shows disconnected, restart Claude Code or check the MCP command spec.`
|
|
4455
4734
|
);
|
|
4456
4735
|
}
|
|
4736
|
+
console.log(
|
|
4737
|
+
"\n\u2713 harnessed v2.0 \u4E09\u5C42\u6808\u65B9\u6CD5\u8BBA bundled \u2014 4 workflows + 6 judgments + 37 capabilities ready"
|
|
4738
|
+
);
|
|
4739
|
+
console.log(
|
|
4740
|
+
" workflows in <packageRoot>/workflows/ (Pure bundled, NOT user-dir override per D-01)"
|
|
4741
|
+
);
|
|
4457
4742
|
process.exit(0);
|
|
4458
4743
|
});
|
|
4459
4744
|
}
|
|
@@ -4678,7 +4963,7 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
4678
4963
|
const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
|
|
4679
4964
|
const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
|
|
4680
4965
|
const isWin = process.platform === "win32";
|
|
4681
|
-
const result = await new Promise((
|
|
4966
|
+
const result = await new Promise((resolve8) => {
|
|
4682
4967
|
const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
|
|
4683
4968
|
let stderr = "";
|
|
4684
4969
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
@@ -4686,15 +4971,15 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
4686
4971
|
});
|
|
4687
4972
|
const timer = setTimeout(() => {
|
|
4688
4973
|
child.kill("SIGKILL");
|
|
4689
|
-
|
|
4974
|
+
resolve8({ exitCode: -1, stderr: `${stderr}[timeout]` });
|
|
4690
4975
|
}, 3e4);
|
|
4691
4976
|
child.on("error", (e) => {
|
|
4692
4977
|
clearTimeout(timer);
|
|
4693
|
-
|
|
4978
|
+
resolve8({ exitCode: -1, stderr: e.message });
|
|
4694
4979
|
});
|
|
4695
4980
|
child.on("close", (code) => {
|
|
4696
4981
|
clearTimeout(timer);
|
|
4697
|
-
|
|
4982
|
+
resolve8({ exitCode: code ?? -1, stderr });
|
|
4698
4983
|
});
|
|
4699
4984
|
});
|
|
4700
4985
|
if (result.exitCode !== 0) {
|