pi-gauntlet 8.4.5 → 8.4.6
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/CHANGELOG.md +4 -0
- package/README.md +1 -1
- package/extensions/phase-tracker.test.ts +0 -235
- package/extensions/phase-tracker.ts +13 -25
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v8.4.6 - 2026-11-08
|
|
4
|
+
|
|
5
|
+
- Require pi-cohort >=7.7.0 for model-only `subagent` registrations; remove the now-unreachable scripted-dispatch guard and nested-result exclusion. The codemode guide documents direct-only dispatch under both `on` and `only` and clarifies that skill examples illustrate direct tool calls, not scripts to execute. (#135)
|
|
6
|
+
|
|
3
7
|
## v8.4.5 - 2026-11-08
|
|
4
8
|
|
|
5
9
|
- Guard, ship-verification, and post-ship spec warnings preserve tool `structuredContent`; nested-call warnings reach the parent codemode result once without changing the child's value or re-arming warn-once guards. (#133)
|
package/README.md
CHANGED
|
@@ -142,7 +142,7 @@ Run by a human against pi-cohort >= 7.1.0, before a pi-gauntlet release claims t
|
|
|
142
142
|
## Requirements
|
|
143
143
|
|
|
144
144
|
- [pi-coding-agent](https://github.com/earendil-works/pi) >= 1.5.0 - supports model-only tracker exposure and sequential tracker execution, so updates persist as transcript tool results rather than nested calls.
|
|
145
|
-
- [pi-cohort](https://github.com/jjuraszek/pi-cohort) >= 7.
|
|
145
|
+
- [pi-cohort](https://github.com/jjuraszek/pi-cohort) >= 7.7.0 - required peer package, including binary-safe worktree patches; it also lets a persona with `subagent` in `tools:` start with inherited extensions ([pi-cohort #26](https://github.com/jjuraszek/pi-cohort/issues/26)), which multi-PR `gatekeep-pr` needs. Skills that dispatch agents (`requesting-code-review`, `subagent-driven-development`, `dispatching-parallel-agents`, `writing-plans`, `forge-skill`, `shape-ticket`, `roasting-the-spec`) call `subagent({})`, which pi-cohort provides. pi-gauntlet does not vendor the dispatch tool; without pi-cohort those skills have nothing to call. Gauntlet execution dispatches explicitly use `async: false`; do not enable pi-cohort's incompatible `forceTopLevelAsync` setting. See pi-cohort's [dispatch configuration](https://github.com/jjuraszek/pi-cohort/blob/main/doc/configuration.md) for that setting's owner and semantics. A wave of N >= 1 tasks dispatches its implementers and first spec reviews as one chain call. `subagent-driven-development` stops before dispatch on unsupported installs; there is no older-runtime fallback.
|
|
146
146
|
|
|
147
147
|
Both packages must be listed in your `.pi/settings.json#packages` array (pi adds them automatically when you `pi install`). pi-gauntlet and pi-cohort are versioned independently but release together whenever dispatch semantics change - pin compatible versions of both. At each pi-gauntlet release, set the pi-cohort peer minimum and README floor together to the newest released pi-cohort version; `/skill:release` checks the registry before proceeding.
|
|
148
148
|
|
|
@@ -1638,153 +1638,6 @@ const implementerWave = (n = 1, agent = "implementer") => ({
|
|
|
1638
1638
|
const firstCallResult = async (h: ReturnType<typeof harness>, id: string, input: unknown) =>
|
|
1639
1639
|
(await h.emitEvent("tool_call", subagentCall(id, input)))[0] as { block?: boolean; reason?: string } | undefined;
|
|
1640
1640
|
|
|
1641
|
-
const nestedCall = (id: string, input: unknown) => ({ ...subagentCall(id, input), parentToolCallId: "script" });
|
|
1642
|
-
|
|
1643
|
-
for (const phase of PHASES) {
|
|
1644
|
-
test(`nested dispatch: blocked in ${phase}; reason names direct call, only-mode, and the switches`, async () => {
|
|
1645
|
-
const h = harness({ hasUI: true, branch: enteredBranch(phases({ [phase]: "in_progress" })) });
|
|
1646
|
-
await h.emit("session_start");
|
|
1647
|
-
const widget = h.widgets.at(-1);
|
|
1648
|
-
for (const [i, input] of [loneImplementer(), implementerWave(), { agent: "conformance-reviewer" }].entries()) {
|
|
1649
|
-
const [blocked] = await h.emitEvent("tool_call", nestedCall(`script/${i}`, input)) as { block: boolean; reason: string }[];
|
|
1650
|
-
assert.equal(blocked?.block, true);
|
|
1651
|
-
assert.match(blocked.reason, /call subagent as a direct tool call/i);
|
|
1652
|
-
assert.match(blocked.reason, /codemode only mode is unsupported while a gauntlet flow is entered/);
|
|
1653
|
-
assert.match(blocked.reason, /codemode\.mode: on/);
|
|
1654
|
-
assert.match(blocked.reason, /piGauntlet\.closureReview\.enforce: false/);
|
|
1655
|
-
assert.equal(h.widgets.at(-1), widget);
|
|
1656
|
-
}
|
|
1657
|
-
assert.equal(await firstCallResult(h, "direct", { agent: "conformance-reviewer" }), undefined);
|
|
1658
|
-
assert.ok(h.widgets.at(-1)!.includes(`${phase}(conformance)`));
|
|
1659
|
-
assert.doesNotMatch(h.widgets.at(-1)!, /#2/);
|
|
1660
|
-
});
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
const blockedNestedDispatchFixture = async (malformed = false) => {
|
|
1664
|
-
const cwd = tempCwd({ piGauntlet: { closureReview: { maxFixRounds: 5 } } });
|
|
1665
|
-
const h = harness({ hasUI: true, cwd, branch: verifyBranch([
|
|
1666
|
-
subagentResult(["conformance-reviewer"]),
|
|
1667
|
-
subagentResult(["wave-reviewer"]),
|
|
1668
|
-
...Array.from({ length: 5 }, () => subagentResult(["implementer"])),
|
|
1669
|
-
]) });
|
|
1670
|
-
await h.emit("session_start");
|
|
1671
|
-
const before = await firstCallResult(h, "cap-before", implementerWave());
|
|
1672
|
-
assert.equal(before?.block, true);
|
|
1673
|
-
assert.match(before?.reason ?? "", /5 fix round\(s\) used against a cap of 5/);
|
|
1674
|
-
if (malformed) writeFileSync(join(cwd, ".pi/settings.json"), "{");
|
|
1675
|
-
const widget = h.widgets.at(-1);
|
|
1676
|
-
const [blocked] = await h.emitEvent("tool_call", nestedCall("script/1", implementerWave())) as { block: boolean; reason: string }[];
|
|
1677
|
-
assert.equal(blocked?.block, true);
|
|
1678
|
-
assert.match(blocked.reason, /call subagent as a direct tool call/i);
|
|
1679
|
-
assert.equal(h.widgets.at(-1), widget);
|
|
1680
|
-
return { cwd, h, before, widget };
|
|
1681
|
-
};
|
|
1682
|
-
|
|
1683
|
-
const assertBlockedNestedStateUntouched = async ({ cwd, h, before, widget }: Awaited<ReturnType<typeof blockedNestedDispatchFixture>>) => {
|
|
1684
|
-
assert.equal(h.widgets.at(-1), widget);
|
|
1685
|
-
assert.deepEqual(await firstCallResult(h, "cap-after", implementerWave()), before);
|
|
1686
|
-
const tool = h.tools.find(t => t.name === "phase_tracker")!;
|
|
1687
|
-
const status = await tool.execute("status", { action: "status" }, undefined, undefined, h.ctx) as any;
|
|
1688
|
-
assert.equal(status.details.phases.verify.substep, undefined);
|
|
1689
|
-
assert.equal((await completePhase(h, "verify")).details.error, undefined);
|
|
1690
|
-
|
|
1691
|
-
// Re-enter implement without replay so the commit guard observes the live cadence.
|
|
1692
|
-
writeFileSync(join(cwd, ".pi/settings.json"), JSON.stringify({ piGauntlet: { flowGuards: { enforce: false } } }));
|
|
1693
|
-
const started = await tool.execute("restart", { action: "start", phase: "implement", force: true }, undefined, undefined, h.ctx) as any;
|
|
1694
|
-
assert.equal(started.details.error, undefined);
|
|
1695
|
-
writeFileSync(join(cwd, ".pi/settings.json"), "{}");
|
|
1696
|
-
await h.emitEvent("tool_call", commitCall("commit"));
|
|
1697
|
-
const [warning] = await h.emitEvent("tool_result", commitResult("commit")) as { content: { text: string }[] }[];
|
|
1698
|
-
assert.match(warning.content[0].text, /no wave-reviewer observed since the last implementer/);
|
|
1699
|
-
assert.equal(await firstCallResult(h, "direct", { agent: "wave-reviewer" }), undefined);
|
|
1700
|
-
assert.match(h.widgets.at(-1)!, /implement\(wave-review\)/);
|
|
1701
|
-
assert.deepEqual(await h.emitEvent("tool_result", waveResult("direct", [{ agent: "wave-reviewer", exitCode: 0 }])), [undefined]);
|
|
1702
|
-
assert.doesNotMatch(h.widgets.at(-1)!, /implement\(/);
|
|
1703
|
-
};
|
|
1704
|
-
|
|
1705
|
-
for (const malformed of [false, true]) {
|
|
1706
|
-
test(`nested dispatch: latch, counter, cadence, and liveDispatch unchanged after a block; malformed=${malformed}`, async () => {
|
|
1707
|
-
await assertBlockedNestedStateUntouched(await blockedNestedDispatchFixture(malformed));
|
|
1708
|
-
});
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
for (const isError of [true, false]) {
|
|
1712
|
-
test(`nested result: replay-mirrored state untouched; isError=${isError}`, async () => {
|
|
1713
|
-
const fixture = await blockedNestedDispatchFixture();
|
|
1714
|
-
await fixture.h.emitEvent("tool_result", {
|
|
1715
|
-
...waveResult("script/1", [
|
|
1716
|
-
{ agent: "conformance-reviewer", exitCode: 0 },
|
|
1717
|
-
{ agent: "implementer", exitCode: 0 },
|
|
1718
|
-
{ agent: "wave-reviewer", exitCode: 0 },
|
|
1719
|
-
], isError),
|
|
1720
|
-
parentToolCallId: "script",
|
|
1721
|
-
});
|
|
1722
|
-
await assertBlockedNestedStateUntouched(fixture);
|
|
1723
|
-
});
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
for (const entered of [false, true]) {
|
|
1727
|
-
test(`nested dispatch: child follows replayed flow entry=${entered}`, async () => {
|
|
1728
|
-
const priorDepth = process.env.PI_SUBAGENT_DEPTH;
|
|
1729
|
-
let h: ReturnType<typeof harness>;
|
|
1730
|
-
try {
|
|
1731
|
-
process.env.PI_SUBAGENT_DEPTH = "1";
|
|
1732
|
-
h = harness({ branch: entered ? verifyBranch() : [] });
|
|
1733
|
-
} finally {
|
|
1734
|
-
if (priorDepth === undefined) delete process.env.PI_SUBAGENT_DEPTH;
|
|
1735
|
-
else process.env.PI_SUBAGENT_DEPTH = priorDepth;
|
|
1736
|
-
}
|
|
1737
|
-
await h.emit("session_fork");
|
|
1738
|
-
const [result] = await h.emitEvent("tool_call", nestedCall("script/1", loneImplementer())) as ({ block?: boolean } | undefined)[];
|
|
1739
|
-
assert.equal(result?.block, entered ? true : undefined);
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
test("nested dispatch: allowed without flow entry", async () => {
|
|
1744
|
-
const h = harness({ cwd: tempCwd() });
|
|
1745
|
-
await h.emit("session_start");
|
|
1746
|
-
assert.deepEqual(await h.emitEvent("tool_call", nestedCall("script/1", implementerWave())), [undefined]);
|
|
1747
|
-
});
|
|
1748
|
-
|
|
1749
|
-
for (const malformed of [false, true]) {
|
|
1750
|
-
test(`nested dispatch: management passes and its parent consumes queued warnings; malformed=${malformed}`, async () => {
|
|
1751
|
-
const cwd = tempCwd({});
|
|
1752
|
-
if (malformed) writeFileSync(join(cwd, ".pi/settings.json"), "{");
|
|
1753
|
-
const h = harness({ cwd, branch: verifyBranch() });
|
|
1754
|
-
await h.emit("session_start");
|
|
1755
|
-
assert.deepEqual(await h.emitEvent("tool_call", nestedCall("script/status", { action: "status" })), [undefined]);
|
|
1756
|
-
const content = [{ type: "text", text: "status result" }];
|
|
1757
|
-
const result = { ...waveResult("script/status", []), parentToolCallId: "script", content };
|
|
1758
|
-
const original = structuredClone(result);
|
|
1759
|
-
const [ret] = await h.emitEvent("tool_result", result);
|
|
1760
|
-
assert.equal(ret, undefined);
|
|
1761
|
-
assert.deepEqual(result, original);
|
|
1762
|
-
assert.deepEqual(mergeToolResult(result, ret), original);
|
|
1763
|
-
assert.deepEqual(await h.emitEvent("tool_result", result), [undefined]);
|
|
1764
|
-
const parent = {
|
|
1765
|
-
...result, toolName: "codemode", toolCallId: "script", parentToolCallId: undefined,
|
|
1766
|
-
content: [{ type: "text", text: "script status result" }], structuredContent: { value: "status" },
|
|
1767
|
-
};
|
|
1768
|
-
const [warning] = await h.emitEvent("tool_result", parent);
|
|
1769
|
-
assert.equal(warning !== undefined, malformed);
|
|
1770
|
-
const merged = mergeToolResult(parent, warning);
|
|
1771
|
-
if (malformed) {
|
|
1772
|
-
assert.match(merged.content[0].text, /gauntlet settings load error \(using defaults\)/);
|
|
1773
|
-
assert.equal(merged.content[0].text.split("gauntlet settings load error").length - 1, 1);
|
|
1774
|
-
assert.deepEqual(merged, { ...parent, content: [merged.content[0], ...parent.content] });
|
|
1775
|
-
} else {
|
|
1776
|
-
assert.deepEqual(merged, parent);
|
|
1777
|
-
}
|
|
1778
|
-
assert.deepEqual(await h.emitEvent("tool_result", parent), [undefined]);
|
|
1779
|
-
});
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
test("nested dispatch: dormant when closureReview.enforce is false", async () => {
|
|
1783
|
-
const h = harness({ cwd: tempCwd({ piGauntlet: { closureReview: { enforce: false } } }), branch: verifyBranch() });
|
|
1784
|
-
await h.emit("session_start");
|
|
1785
|
-
assert.deepEqual(await h.emitEvent("tool_call", nestedCall("script/1", loneImplementer())), [undefined]);
|
|
1786
|
-
});
|
|
1787
|
-
|
|
1788
1641
|
for (const agent of ["implementer", "loop-implementer"]) {
|
|
1789
1642
|
test(`shape guard: lone ${agent} blocked in verify after the audit, with and without closureReview.model, async or not`, async () => {
|
|
1790
1643
|
for (const settings of [
|
|
@@ -1919,94 +1772,6 @@ for (const delivery of ["live", "session_start", "session_switch", "session_fork
|
|
|
1919
1772
|
});
|
|
1920
1773
|
}
|
|
1921
1774
|
|
|
1922
|
-
for (const isError of [false, true]) {
|
|
1923
|
-
for (const enforce of [true, false]) {
|
|
1924
|
-
test(`nested results: closure latch stays replay-equivalent with enforce=${enforce}, isError=${isError}`, async () => {
|
|
1925
|
-
const cwd = tempCwd({ piGauntlet: { closureReview: { enforce } } });
|
|
1926
|
-
const h = harness({ hasUI: true, cwd, branch: verifyBranch() });
|
|
1927
|
-
await h.emit("session_start");
|
|
1928
|
-
const widget = h.widgets.at(-1);
|
|
1929
|
-
await h.emitEvent("tool_result", {
|
|
1930
|
-
...waveResult("nested", [{ agent: "conformance-reviewer", exitCode: 0 }], isError),
|
|
1931
|
-
parentToolCallId: "script",
|
|
1932
|
-
});
|
|
1933
|
-
assert.equal(h.widgets.at(-1), widget);
|
|
1934
|
-
const status = await h.tools.find(t => t.name === "phase_tracker")!.execute("status", { action: "status" }, undefined, undefined, h.ctx) as any;
|
|
1935
|
-
assert.equal(status.details.phases.verify.substep, undefined);
|
|
1936
|
-
writeFileSync(join(cwd, ".pi/settings.json"), "{}");
|
|
1937
|
-
assert.equal((await completePhase(h, "verify")).details.error, "no conformance-reviewer dispatch observed");
|
|
1938
|
-
await h.emit("session_switch");
|
|
1939
|
-
assert.equal((await completePhase(h, "verify")).details.error, "no conformance-reviewer dispatch observed");
|
|
1940
|
-
await h.emitEvent("tool_result", waveResult("direct", [{ agent: "conformance-reviewer", exitCode: 0 }]));
|
|
1941
|
-
assert.equal((await completePhase(h, "verify")).details.error, undefined);
|
|
1942
|
-
});
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
|
-
for (const agent of ["implementer", "loop-implementer"]) {
|
|
1946
|
-
test(`nested results: ${agent} preserves rounds and granted credits; isError=${isError}`, async () => {
|
|
1947
|
-
const h = guardedHarness({ piGauntlet: { closureReview: { maxFixRounds: 1 } } });
|
|
1948
|
-
await h.emit("session_start");
|
|
1949
|
-
const result = waveResult("wave", [{ agent, exitCode: 0 }]);
|
|
1950
|
-
await h.emitEvent("tool_result", { ...result, isError, parentToolCallId: "script" });
|
|
1951
|
-
assert.equal(await firstCallResult(h, "first", implementerWave(1, agent)), undefined);
|
|
1952
|
-
await h.emitEvent("tool_result", result);
|
|
1953
|
-
assert.equal((await firstCallResult(h, "capped", implementerWave(1, agent)))?.block, true);
|
|
1954
|
-
assert.equal((await grant(h, "grant", { rounds: 1, reason: "one more repair" })).details.error, undefined);
|
|
1955
|
-
await h.emitEvent("tool_result", { ...result, isError, parentToolCallId: "script" });
|
|
1956
|
-
assert.equal(await firstCallResult(h, "funded", implementerWave(1, agent)), undefined);
|
|
1957
|
-
await h.emitEvent("tool_result", result);
|
|
1958
|
-
const spent = await firstCallResult(h, "spent", implementerWave(1, agent));
|
|
1959
|
-
assert.equal(spent?.block, true);
|
|
1960
|
-
assert.match(spent?.reason ?? "", /2 fix round\(s\) used against a cap of 1/);
|
|
1961
|
-
});
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
for (const row of [
|
|
1965
|
-
{ agent: "implementer", prior: [], warns: false },
|
|
1966
|
-
{ agent: "wave-reviewer", prior: [subagentResult(["implementer"])], warns: true },
|
|
1967
|
-
]) {
|
|
1968
|
-
test(`nested results: ${row.agent} leaves cadence unchanged but finishes the registered nested dispatch; isError=${isError}`, async () => {
|
|
1969
|
-
const h = harness({ hasUI: true, cwd: tempCwd({ piGauntlet: { closureReview: { enforce: false } } }), branch: implementBranch(row.prior) });
|
|
1970
|
-
await h.emit("session_start");
|
|
1971
|
-
assert.deepEqual(await h.emitEvent("tool_call", nestedCall("work", { agent: row.agent })), [undefined]);
|
|
1972
|
-
assert.match(h.widgets.at(-1)!, /implement\(/);
|
|
1973
|
-
const tool = h.tools.find(t => t.name === "phase_tracker")!;
|
|
1974
|
-
const status = () => tool.execute("status", { action: "status" }, undefined, undefined, h.ctx) as Promise<any>;
|
|
1975
|
-
assert.equal((await status()).details.phases.implement.substep, row.agent === "implementer" ? "implement" : "wave-review");
|
|
1976
|
-
await h.emitEvent("tool_result", {
|
|
1977
|
-
...waveResult("work", [{ agent: row.agent, exitCode: 0 }], isError),
|
|
1978
|
-
parentToolCallId: "script",
|
|
1979
|
-
});
|
|
1980
|
-
assert.doesNotMatch(h.widgets.at(-1)!, /implement\(/);
|
|
1981
|
-
assert.equal((await status()).details.phases.implement.substep, undefined);
|
|
1982
|
-
for (const delivery of ["live", "session_switch"]) {
|
|
1983
|
-
if (delivery !== "live") await h.emit(delivery);
|
|
1984
|
-
await h.emitEvent("tool_call", commitCall("commit"));
|
|
1985
|
-
const [warning] = await h.emitEvent("tool_result", commitResult("commit"));
|
|
1986
|
-
assert.equal(warning !== undefined, row.warns, delivery);
|
|
1987
|
-
}
|
|
1988
|
-
await h.emitEvent("tool_result", waveResult("direct", [{ agent: row.agent, exitCode: 0 }]));
|
|
1989
|
-
await h.emitEvent("tool_call", commitCall("direct-commit"));
|
|
1990
|
-
const [warning] = await h.emitEvent("tool_result", commitResult("direct-commit"));
|
|
1991
|
-
assert.equal(warning !== undefined, !row.warns);
|
|
1992
|
-
});
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
test("nested result: open-task nudge is skipped while the same direct result nudges", async () => {
|
|
1998
|
-
const h = harness({ branch: verifyBranch([
|
|
1999
|
-
subagentResult(["conformance-reviewer"]),
|
|
2000
|
-
taskSnapshot([{ name: "repair", status: "in_progress" }]),
|
|
2001
|
-
]) });
|
|
2002
|
-
await h.emit("session_start");
|
|
2003
|
-
const result = okWave("repair");
|
|
2004
|
-
const [nested] = await h.emitEvent("tool_result", { ...result, parentToolCallId: "script" });
|
|
2005
|
-
assert.equal(nested, undefined);
|
|
2006
|
-
const [direct] = await h.emitEvent("tool_result", result) as { content: { text: string }[] }[];
|
|
2007
|
-
assert.match(direct.content[0].text, /0: repair \(in_progress\)/);
|
|
2008
|
-
});
|
|
2009
|
-
|
|
2010
1775
|
test("interrupted implementer waves still spend rounds and granted credits and nudge open tasks live and on replay", async () => {
|
|
2011
1776
|
const cwd = tempCwd({ piGauntlet: { closureReview: { maxFixRounds: 1 } } });
|
|
2012
1777
|
const interrupted = waveResult("paused", [{ agent: "implementer", exitCode: 0, interrupted: true }]);
|
|
@@ -260,13 +260,6 @@ const fixRoundCapBlockReason = (used: number, cap: number, settingsPath: string)
|
|
|
260
260
|
"a repo closureReview block replaces the preset's whole block, so restate model and maxFixRounds alongside). " +
|
|
261
261
|
"Start the escalation message with the line: gauntlet: gate=verification-before-completion/step-6";
|
|
262
262
|
|
|
263
|
-
const nestedDispatchBlockReason = (): string =>
|
|
264
|
-
"Call subagent as a direct tool call, not from inside a codemode script; " +
|
|
265
|
-
"codemode only mode is unsupported while a gauntlet flow is entered, because a scripted dispatch's result " +
|
|
266
|
-
"is not replayed on reload or fork and the fix-round counter, closure-review latch, and cadence would reset. " +
|
|
267
|
-
"Switch codemode.mode: on and issue the call directly. " +
|
|
268
|
-
"Last resort: set piGauntlet.closureReview.enforce: false (disables all closure guards).";
|
|
269
|
-
|
|
270
263
|
const closureModelBlockReason = (model: string, missing: number, total: number): string =>
|
|
271
264
|
`Blocked: ${missing} of ${total} conformance-reviewer ${total === 1 ? "dispatch" : "entries"} ` +
|
|
272
265
|
`omitted a model while piGauntlet.closureReview.model is set to "${model}".\n` +
|
|
@@ -687,9 +680,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
687
680
|
// Only execution-mode dispatches carry a model or run agents; management/control
|
|
688
681
|
// modes (action: list/get/create/update/delete/status/...) execute nothing, so skip them.
|
|
689
682
|
const hasAction = !!(event.input as { action?: unknown })?.action;
|
|
690
|
-
if (event.parentToolCallId && !hasAction) {
|
|
691
|
-
return { block: true, reason: nestedDispatchBlockReason() };
|
|
692
|
-
}
|
|
693
683
|
// Fix-loop window: verify in progress and the R0 audit observed. Read directly,
|
|
694
684
|
// not via activeGuardPhase() - verify is not a GUARD_PHASES member.
|
|
695
685
|
const fixLoopWindow = !isSubagentChild && !hasAction && phases.verify.status === "in_progress" && conformanceDispatched;
|
|
@@ -852,21 +842,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
852
842
|
pi.on("tool_result", async (event, ctx) => {
|
|
853
843
|
if (finishDispatch(event.toolName, event.toolCallId, event.isError, event.details)) updateWidget(ctx);
|
|
854
844
|
if (event.toolName === "subagent") {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
)
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
if (open.length) pendingWarnings.add(event.toolCallId, openTaskNudge(open));
|
|
869
|
-
}
|
|
845
|
+
observeFixWave(event.details, event.isError, ctx);
|
|
846
|
+
if (qualifiesAsClosureDispatch(event.details)) conformanceDispatched = true;
|
|
847
|
+
observeCadence(event.details);
|
|
848
|
+
if (
|
|
849
|
+
!isSubagentChild &&
|
|
850
|
+
gauntletEntered &&
|
|
851
|
+
phases.verify.status === "in_progress" &&
|
|
852
|
+
isImplementerWave(event.details, event.isError)
|
|
853
|
+
) {
|
|
854
|
+
const loaded = loadGauntletSettings(ctx.cwd);
|
|
855
|
+
if (loaded.errors.length === 0 && resolveFlowGuards(loaded.gauntlet).enforce) {
|
|
856
|
+
const open = unfinishedTaskLines(latestPlanSnapshot(ctx));
|
|
857
|
+
if (open.length) pendingWarnings.add(event.toolCallId, openTaskNudge(open));
|
|
870
858
|
}
|
|
871
859
|
}
|
|
872
860
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-gauntlet",
|
|
3
|
-
"version": "8.4.
|
|
3
|
+
"version": "8.4.6",
|
|
4
4
|
"description": "Opinionated, gated workflow skills, subagent personas, and runtime extensions for the pi coding agent.",
|
|
5
5
|
"author": "Jacek Juraszek",
|
|
6
6
|
"type": "module",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"test": "node scripts/ci.mjs"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"pi-cohort": ">=7.
|
|
65
|
+
"pi-cohort": ">=7.7.0"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"yaml": "^2.9.0"
|