ultimate-pi 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/harness-plan/SKILL.md +6 -6
- package/.pi/agents/harness/planner.md +9 -10
- package/.pi/extensions/budget-guard.ts +46 -17
- package/.pi/extensions/harness-run-context.ts +150 -28
- package/.pi/extensions/lib/harness-subagents/harness-subagent-policy.ts +1 -1
- package/.pi/extensions/lib/harness-subagents/parent-ask-user-bridge.ts +89 -0
- package/.pi/extensions/lib/harness-subagents/spawn-policy.ts +20 -2
- package/.pi/extensions/lib/harness-subagents/vendored/agent-manager.ts +1 -0
- package/.pi/extensions/lib/harness-subagents/vendored/agent-runner.ts +40 -24
- package/.pi/extensions/lib/harness-subagents/vendored/index.ts +21 -0
- package/.pi/extensions/policy-gate.ts +4 -4
- package/.pi/harness/agents.manifest.json +82 -82
- package/.pi/harness/docs/adrs/0031-harness-run-context.md +1 -1
- package/.pi/harness/docs/adrs/0032-harness-command-orchestration.md +6 -6
- package/.pi/harness/specs/budget-exhausted-event.schema.json +3 -1
- package/.pi/harness/specs/harness-turn.schema.json +18 -0
- package/.pi/lib/harness-run-context.ts +166 -32
- package/.pi/prompts/harness-plan.md +12 -14
- package/.pi/scripts/harness-verify.mjs +29 -1
- package/CHANGELOG.md +12 -0
- package/package.json +2 -2
|
@@ -129,6 +129,36 @@ export interface PlanUserApproval {
|
|
|
129
129
|
source: "ask_user" | "harness-plan-approval" | "noninteractive";
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/** Persisted on `input` when user invokes a raw `/harness-*` prompt template. */
|
|
133
|
+
export interface HarnessTurnEntry {
|
|
134
|
+
schema_version: "1.0.0";
|
|
135
|
+
command: string;
|
|
136
|
+
args: string;
|
|
137
|
+
source: "slash";
|
|
138
|
+
invoked_at: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const HARNESS_COMMAND_PHASE: Record<string, HarnessPhase> = {
|
|
142
|
+
"harness-plan": "plan",
|
|
143
|
+
"harness-auto": "plan",
|
|
144
|
+
"harness-run": "execute",
|
|
145
|
+
"harness-eval": "evaluate",
|
|
146
|
+
"harness-review": "evaluate",
|
|
147
|
+
"harness-critic": "adversary",
|
|
148
|
+
"harness-trace": "evaluate",
|
|
149
|
+
"harness-incident": "evaluate",
|
|
150
|
+
"harness-drift-replan": "plan",
|
|
151
|
+
"harness-drift-proceed": "execute",
|
|
152
|
+
"harness-abort": "plan",
|
|
153
|
+
"harness-new-run": "plan",
|
|
154
|
+
"harness-run-status": "plan",
|
|
155
|
+
"harness-use-run": "plan",
|
|
156
|
+
"harness-policy-status": "merge",
|
|
157
|
+
"harness-router-tune": "plan",
|
|
158
|
+
"harness-budget-status": "plan",
|
|
159
|
+
"harness-setup": "execute",
|
|
160
|
+
};
|
|
161
|
+
|
|
132
162
|
export interface PlanPhaseMutationDecision {
|
|
133
163
|
allowed: boolean;
|
|
134
164
|
reason?: string;
|
|
@@ -193,11 +223,40 @@ export async function isPlanPhaseScopedWrite(
|
|
|
193
223
|
return isCanonicalPlanPacketPath(resolved, projectRoot, runCtx.run_id);
|
|
194
224
|
}
|
|
195
225
|
|
|
226
|
+
export function getLatestHarnessTurn(
|
|
227
|
+
entries: unknown[],
|
|
228
|
+
): HarnessTurnEntry | null {
|
|
229
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
230
|
+
const entry = entries[i] as SessionEntryLike;
|
|
231
|
+
if (entry.type !== "custom" || entry.customType !== "harness-turn") {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const data = entry.data as Partial<HarnessTurnEntry> | undefined;
|
|
235
|
+
if (data?.command && typeof data.command === "string") {
|
|
236
|
+
return {
|
|
237
|
+
schema_version: "1.0.0",
|
|
238
|
+
command: data.command,
|
|
239
|
+
args: typeof data.args === "string" ? data.args : "",
|
|
240
|
+
source: "slash",
|
|
241
|
+
invoked_at:
|
|
242
|
+
typeof data.invoked_at === "string" ? data.invoked_at : nowIso(),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
196
249
|
export function indexOfLastPlanCommand(entries: unknown[]): number {
|
|
197
250
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
198
251
|
const entry = entries[i] as SessionEntryLike & {
|
|
199
252
|
message?: { role?: string; content?: string | unknown[] };
|
|
200
253
|
};
|
|
254
|
+
if (entry.type === "custom" && entry.customType === "harness-turn") {
|
|
255
|
+
const cmd = (entry.data as { command?: string })?.command;
|
|
256
|
+
if (cmd === "harness-plan" || cmd === "harness-auto") {
|
|
257
|
+
return i;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
201
260
|
if (
|
|
202
261
|
entry.type === "custom" &&
|
|
203
262
|
entry.customType === "harness-plan-attempt"
|
|
@@ -221,7 +280,7 @@ export function indexOfLastPlanCommand(entries: unknown[]): number {
|
|
|
221
280
|
.join("\n")
|
|
222
281
|
: "";
|
|
223
282
|
const visible = userVisiblePromptSlice(text);
|
|
224
|
-
const parsed =
|
|
283
|
+
const parsed = parseHarnessSlashInput(visible);
|
|
225
284
|
if (
|
|
226
285
|
parsed?.command === "harness-plan" ||
|
|
227
286
|
parsed?.command === "harness-auto"
|
|
@@ -342,7 +401,7 @@ export function isHarnessAutoSession(entries: unknown[]): boolean {
|
|
|
342
401
|
typeof entry.message.content === "string"
|
|
343
402
|
? userVisiblePromptSlice(entry.message.content)
|
|
344
403
|
: "";
|
|
345
|
-
const parsed =
|
|
404
|
+
const parsed = parseHarnessSlashInput(text);
|
|
346
405
|
if (parsed?.command === "harness-auto") return true;
|
|
347
406
|
}
|
|
348
407
|
return false;
|
|
@@ -469,18 +528,16 @@ export function nowIso(): string {
|
|
|
469
528
|
return new Date().toISOString();
|
|
470
529
|
}
|
|
471
530
|
|
|
531
|
+
/** @deprecated Use parseHarnessSlashInput on raw `input` event text only. */
|
|
472
532
|
export function isHarnessSlashCommand(prompt: string): boolean {
|
|
473
|
-
|
|
474
|
-
if (!trimmed.startsWith("/harness-")) return false;
|
|
475
|
-
const match = trimmed.match(/^\/(harness-[a-z0-9-]+)/);
|
|
476
|
-
if (!match) return false;
|
|
477
|
-
return HARNESS_COMMANDS.has(match[1]);
|
|
533
|
+
return parseHarnessSlashInput(prompt) !== null;
|
|
478
534
|
}
|
|
479
535
|
|
|
480
|
-
|
|
481
|
-
|
|
536
|
+
/** Parse raw user input before prompt-template expansion (`input` hook only). */
|
|
537
|
+
export function parseHarnessSlashInput(
|
|
538
|
+
text: string,
|
|
482
539
|
): { command: string; args: string } | null {
|
|
483
|
-
const trimmed =
|
|
540
|
+
const trimmed = text.trim();
|
|
484
541
|
const match = trimmed.match(/^\/(harness-[a-z0-9-]+)(?:\s+([\s\S]*))?$/);
|
|
485
542
|
if (!match) return null;
|
|
486
543
|
const command = match[1];
|
|
@@ -488,6 +545,13 @@ export function parseHarnessSlashCommand(
|
|
|
488
545
|
return { command, args: (match[2] ?? "").trim() };
|
|
489
546
|
}
|
|
490
547
|
|
|
548
|
+
/** @deprecated Prefer parseHarnessSlashInput on raw input; kept for expanded-prompt fallbacks. */
|
|
549
|
+
export function parseHarnessSlashCommand(
|
|
550
|
+
prompt: string,
|
|
551
|
+
): { command: string; args: string } | null {
|
|
552
|
+
return parseHarnessSlashInput(userVisiblePromptSlice(prompt));
|
|
553
|
+
}
|
|
554
|
+
|
|
491
555
|
/** User-visible prompt slice for policy signals (exclude injected blocks). */
|
|
492
556
|
export function userVisiblePromptSlice(prompt: string): string {
|
|
493
557
|
const markers = [
|
|
@@ -720,7 +784,32 @@ export function planPacketSummary(
|
|
|
720
784
|
};
|
|
721
785
|
}
|
|
722
786
|
|
|
723
|
-
export function
|
|
787
|
+
export function buildHarnessSpawnContextSnippet(
|
|
788
|
+
ctx: HarnessRunContext,
|
|
789
|
+
opts?: { mode?: "create" | "revise"; risk_level?: string; quick?: boolean },
|
|
790
|
+
): string {
|
|
791
|
+
const mode =
|
|
792
|
+
opts?.mode ??
|
|
793
|
+
(ctx.plan_ready || ctx.status === "aborted" ? "revise" : "create");
|
|
794
|
+
return JSON.stringify(
|
|
795
|
+
{
|
|
796
|
+
schema_version: "1.0.0",
|
|
797
|
+
run_id: ctx.run_id,
|
|
798
|
+
plan_packet_path: ctx.plan_packet_path,
|
|
799
|
+
task_summary: ctx.task_summary,
|
|
800
|
+
mode,
|
|
801
|
+
risk_level: opts?.risk_level ?? "med",
|
|
802
|
+
quick: opts?.quick ?? false,
|
|
803
|
+
},
|
|
804
|
+
null,
|
|
805
|
+
2,
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
export function formatPlanContextBlock(
|
|
810
|
+
ctx: HarnessRunContext,
|
|
811
|
+
opts?: { mode?: "create" | "revise"; risk_level?: string; quick?: boolean },
|
|
812
|
+
): string {
|
|
724
813
|
const lines = [
|
|
725
814
|
"[HarnessRunContext]",
|
|
726
815
|
`run_id=${ctx.run_id}`,
|
|
@@ -735,6 +824,12 @@ export function formatPlanContextBlock(ctx: HarnessRunContext): string {
|
|
|
735
824
|
if (ctx.plan_packet_path) {
|
|
736
825
|
lines.push(`plan_packet_path=${ctx.plan_packet_path}`);
|
|
737
826
|
}
|
|
827
|
+
if (ctx.task_summary) {
|
|
828
|
+
lines.push(`task_summary=${ctx.task_summary}`);
|
|
829
|
+
}
|
|
830
|
+
lines.push(
|
|
831
|
+
`HarnessSpawnContext=${buildHarnessSpawnContextSnippet(ctx, opts)}`,
|
|
832
|
+
);
|
|
738
833
|
return lines.join("\n");
|
|
739
834
|
}
|
|
740
835
|
|
|
@@ -850,7 +945,7 @@ export function shouldReuseHarnessRunId(
|
|
|
850
945
|
ctx: HarnessRunContext | null,
|
|
851
946
|
command: string | null,
|
|
852
947
|
): boolean {
|
|
853
|
-
if (!command
|
|
948
|
+
if (!command) return false;
|
|
854
949
|
if (command === "harness-new-run") return false;
|
|
855
950
|
if (!ctx) return false;
|
|
856
951
|
if (command === "harness-plan" || command === "harness-auto") {
|
|
@@ -875,27 +970,43 @@ export interface HarnessPolicyState {
|
|
|
875
970
|
aborted: boolean;
|
|
876
971
|
}
|
|
877
972
|
|
|
973
|
+
export function inferHarnessPhaseFromTurn(entries: unknown[]): HarnessPhase | null {
|
|
974
|
+
const turn = getLatestHarnessTurn(entries);
|
|
975
|
+
if (!turn) return null;
|
|
976
|
+
return HARNESS_COMMAND_PHASE[turn.command] ?? null;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/** Prefer session `harness-turn`; fall back to raw slash in visible prompt only. */
|
|
980
|
+
export function inferHarnessPhase(
|
|
981
|
+
entries: unknown[],
|
|
982
|
+
userPrompt?: string,
|
|
983
|
+
): HarnessPhase {
|
|
984
|
+
const fromTurn = inferHarnessPhaseFromTurn(entries);
|
|
985
|
+
if (fromTurn) return fromTurn;
|
|
986
|
+
if (userPrompt) {
|
|
987
|
+
const parsed = parseHarnessSlashInput(userVisiblePromptSlice(userPrompt));
|
|
988
|
+
if (parsed && HARNESS_COMMAND_PHASE[parsed.command]) {
|
|
989
|
+
return HARNESS_COMMAND_PHASE[parsed.command];
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return "execute";
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/** @deprecated Use inferHarnessPhase(entries, prompt) — substring matching causes false plan phase. */
|
|
878
996
|
export function inferHarnessPhaseFromPrompt(prompt: string): HarnessPhase {
|
|
879
|
-
const p = prompt.toLowerCase();
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
p.includes("/harness-auto") ||
|
|
884
|
-
p.includes("harness-auto")
|
|
885
|
-
) {
|
|
886
|
-
return "plan";
|
|
997
|
+
const p = userVisiblePromptSlice(prompt).toLowerCase();
|
|
998
|
+
const parsed = parseHarnessSlashInput(userVisiblePromptSlice(prompt));
|
|
999
|
+
if (parsed && HARNESS_COMMAND_PHASE[parsed.command]) {
|
|
1000
|
+
return HARNESS_COMMAND_PHASE[parsed.command];
|
|
887
1001
|
}
|
|
888
|
-
if (p.
|
|
889
|
-
|
|
890
|
-
return "evaluate";
|
|
1002
|
+
if (p.startsWith("/harness-plan") || p.startsWith("/harness-auto")) {
|
|
1003
|
+
return "plan";
|
|
891
1004
|
}
|
|
892
|
-
if (p.
|
|
1005
|
+
if (p.startsWith("/harness-run")) return "execute";
|
|
1006
|
+
if (p.startsWith("/harness-eval") || p.startsWith("/harness-review")) {
|
|
893
1007
|
return "evaluate";
|
|
894
1008
|
}
|
|
895
|
-
if (p.
|
|
896
|
-
return "adversary";
|
|
897
|
-
}
|
|
898
|
-
if (p.includes("adversary")) return "adversary";
|
|
1009
|
+
if (p.startsWith("/harness-critic")) return "adversary";
|
|
899
1010
|
if (p.includes("merge gate") || p.includes("policy decision")) return "merge";
|
|
900
1011
|
return "execute";
|
|
901
1012
|
}
|
|
@@ -914,8 +1025,8 @@ export function isValidHarnessPhaseTransition(
|
|
|
914
1025
|
|
|
915
1026
|
export function getLatestPolicyState(entries: unknown[]): HarnessPolicyState {
|
|
916
1027
|
const fallback: HarnessPolicyState = {
|
|
917
|
-
phase: "
|
|
918
|
-
approvedPlan:
|
|
1028
|
+
phase: "plan",
|
|
1029
|
+
approvedPlan: false,
|
|
919
1030
|
planId: null,
|
|
920
1031
|
aborted: false,
|
|
921
1032
|
};
|
|
@@ -970,7 +1081,7 @@ export function getPolicyTransitionBlock(
|
|
|
970
1081
|
return { blocked: false };
|
|
971
1082
|
}
|
|
972
1083
|
const state = getLatestPolicyState(entries);
|
|
973
|
-
const nextPhase =
|
|
1084
|
+
const nextPhase = inferHarnessPhase(entries, userPrompt);
|
|
974
1085
|
if (!isValidHarnessPhaseTransition(state.phase, nextPhase)) {
|
|
975
1086
|
return {
|
|
976
1087
|
blocked: true,
|
|
@@ -1014,7 +1125,7 @@ export function isNewTaskPlanBlocked(
|
|
|
1014
1125
|
): boolean {
|
|
1015
1126
|
if (ctx.status !== "active") return false;
|
|
1016
1127
|
if (isAmendPlanAllowed(ctx, prompt, false)) return false;
|
|
1017
|
-
const cmd =
|
|
1128
|
+
const cmd = parseHarnessSlashInput(userVisiblePromptSlice(prompt));
|
|
1018
1129
|
if (cmd?.command !== "harness-plan") return false;
|
|
1019
1130
|
const taskMatch = prompt.match(/"([^"]+)"/);
|
|
1020
1131
|
if (!taskMatch || !ctx.task_summary) return true;
|
|
@@ -1137,3 +1248,26 @@ export function driftGateActive(entries: unknown[]): boolean {
|
|
|
1137
1248
|
export function phaseTraceFileName(phase: HarnessPhase): string {
|
|
1138
1249
|
return `trace-${phase}.json`;
|
|
1139
1250
|
}
|
|
1251
|
+
|
|
1252
|
+
/** Collect plan approvals from a session entry list (e.g. subagent in-memory session). */
|
|
1253
|
+
export function extractPlanApprovalsFromEntries(
|
|
1254
|
+
entries: unknown[],
|
|
1255
|
+
): PlanUserApproval[] {
|
|
1256
|
+
const out: PlanUserApproval[] = [];
|
|
1257
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1258
|
+
const entry = entries[i] as SessionEntryLike & {
|
|
1259
|
+
message?: {
|
|
1260
|
+
role?: string;
|
|
1261
|
+
toolName?: string;
|
|
1262
|
+
details?: unknown;
|
|
1263
|
+
content?: { type?: string; text?: string }[];
|
|
1264
|
+
};
|
|
1265
|
+
};
|
|
1266
|
+
if (entry.type !== "message" || entry.message?.role !== "toolResult") {
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
const fromAsk = parseAskUserApprovalFromMessage(entry.message);
|
|
1270
|
+
if (fromAsk) out.push(fromAsk);
|
|
1271
|
+
}
|
|
1272
|
+
return out;
|
|
1273
|
+
}
|
|
@@ -5,7 +5,7 @@ argument-hint: "\"<task>\" [--risk low|med|high] [--budget <amount>] [--quick]"
|
|
|
5
5
|
|
|
6
6
|
# harness-plan
|
|
7
7
|
|
|
8
|
-
Orchestrator only — spawn `harness/planner
|
|
8
|
+
Orchestrator only — spawn `harness/planner` once; planner runs clarification and approval via `ask_user` (parent UI). Write `plan-packet.json` only after approval. Do **not** plan inline in this session.
|
|
9
9
|
|
|
10
10
|
## Step 0 — Parse arguments
|
|
11
11
|
|
|
@@ -22,39 +22,37 @@ If task is missing:
|
|
|
22
22
|
|
|
23
23
|
## Active plan context
|
|
24
24
|
|
|
25
|
+
Use injected context only — **do not** read `.pi/harness/specs/*.schema.json` or explore specs with bash.
|
|
26
|
+
|
|
25
27
|
If `[HarnessActivePlan]` is present:
|
|
26
28
|
|
|
27
|
-
- Read current packet from `plan_packet_path` first.
|
|
28
29
|
- Treat task as **revise/amend** unless `/harness-new-run` was used.
|
|
29
|
-
- Pass `mode: revise` in
|
|
30
|
+
- Pass `mode: revise` using the `HarnessSpawnContext` JSON in `[HarnessRunContext]`.
|
|
30
31
|
|
|
31
|
-
Otherwise use
|
|
32
|
+
Otherwise use `HarnessSpawnContext` from `[HarnessRunContext]` for greenfield `mode: create`.
|
|
32
33
|
|
|
33
34
|
## Orchestration (required)
|
|
34
35
|
|
|
35
|
-
1.
|
|
36
|
-
2. Spawn with **`inherit_context: false`**:
|
|
36
|
+
1. Copy the `HarnessSpawnContext=…` JSON from `[HarnessRunContext]` into the spawn prompt (adjust `risk_level`, `quick`, `mode` from `$ARGUMENTS` if needed).
|
|
37
|
+
2. Spawn **once** with **`inherit_context: false`**:
|
|
37
38
|
|
|
38
39
|
```
|
|
39
40
|
Agent({ subagent_type: "harness/planner", prompt: "<task + HarnessSpawnContext JSON + output schema>" })
|
|
40
41
|
```
|
|
41
42
|
|
|
42
43
|
3. `get_subagent_result` — parse final JSON (`status`, `plan_packet`, `human_summary`, `clarification`) via fenced `json` block.
|
|
43
|
-
4. If `
|
|
44
|
-
5.
|
|
45
|
-
6.
|
|
46
|
-
7. On **Request changes**, re-spawn planner with `mode: revise` and user feedback — do not write file.
|
|
47
|
-
8. **Only after Approve** — write `PlanPacket` JSON to canonical `plan_packet_path`.
|
|
44
|
+
4. If `status === "ready"` and user approved in the subagent (`ask_user` Approve), validate `plan_packet` fields, then **write** `PlanPacket` JSON to canonical `plan_packet_path` from `[HarnessRunContext]`.
|
|
45
|
+
5. If `needs_clarification`, tell the user the planner is waiting — do **not** re-spawn; user should answer in the subagent or re-run `/harness-plan`.
|
|
46
|
+
6. Do **not** call `ask_user` in this parent session for planner clarification or approval.
|
|
48
47
|
|
|
49
48
|
## Parent rules
|
|
50
49
|
|
|
51
|
-
- Do not mutate project source files — only `plan-packet.json` after approval.
|
|
52
|
-
- Validate draft against `.pi/harness/specs/plan-packet.schema.json` before `ask_user` Approve.
|
|
50
|
+
- Do not mutate project source files — only `plan-packet.json` after subagent approval is recorded.
|
|
53
51
|
- Do not embed `plan_id=` in prompts for policy sync.
|
|
52
|
+
- Optional: `/harness-plan-commit` if write was blocked but approval exists.
|
|
54
53
|
|
|
55
54
|
## Completion
|
|
56
55
|
|
|
57
56
|
- `plan_status`: `ready` or `needs_clarification`
|
|
58
57
|
- `risk_level` used
|
|
59
58
|
- `next_command`: `/harness-run` when `ready` (never `/harness-run --plan …`)
|
|
60
|
-
- If `needs_clarification`, user may reply in chat or re-run `/harness-plan`
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { readFile, access } from "node:fs/promises";
|
|
7
7
|
import { constants } from "node:fs";
|
|
8
|
-
import { join, dirname } from "node:path";
|
|
8
|
+
import { join, dirname, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
11
11
|
|
|
@@ -22,6 +22,7 @@ const REQUIRED_SCHEMAS = [
|
|
|
22
22
|
"run-trace.schema.json",
|
|
23
23
|
"eval-verdict.schema.json",
|
|
24
24
|
"harness-spawn-context.schema.json",
|
|
25
|
+
"harness-turn.schema.json",
|
|
25
26
|
];
|
|
26
27
|
|
|
27
28
|
const REQUIRED_ADRS = [
|
|
@@ -201,6 +202,33 @@ async function main() {
|
|
|
201
202
|
if (!(await fileExists(runCtxLib))) fail("missing lib/harness-run-context.ts");
|
|
202
203
|
ok("lib/harness-run-context.ts");
|
|
203
204
|
|
|
205
|
+
const vendoredIndex = join(
|
|
206
|
+
ROOT,
|
|
207
|
+
".pi",
|
|
208
|
+
"extensions",
|
|
209
|
+
"lib",
|
|
210
|
+
"harness-subagents",
|
|
211
|
+
"vendored",
|
|
212
|
+
"index.ts",
|
|
213
|
+
);
|
|
214
|
+
const vendoredSrc = await readFile(vendoredIndex, "utf-8");
|
|
215
|
+
const runCtxImport = vendoredSrc.match(
|
|
216
|
+
/from ["']([^"']*harness-run-context\.js)["']/,
|
|
217
|
+
);
|
|
218
|
+
if (!runCtxImport) {
|
|
219
|
+
fail("vendored/index.ts must import harness-run-context.js");
|
|
220
|
+
}
|
|
221
|
+
const runCtxImportPath = resolve(
|
|
222
|
+
dirname(vendoredIndex),
|
|
223
|
+
runCtxImport[1].replace(/\.js$/, ".ts"),
|
|
224
|
+
);
|
|
225
|
+
if (runCtxImportPath !== runCtxLib) {
|
|
226
|
+
fail(
|
|
227
|
+
`vendored/index.ts harness-run-context import resolves to ${runCtxImportPath}, expected ${runCtxLib}`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
ok("vendored/index.ts harness-run-context import path");
|
|
231
|
+
|
|
204
232
|
const policyGateSrc = await readFile(
|
|
205
233
|
join(ROOT, ".pi", "extensions", "policy-gate.ts"),
|
|
206
234
|
"utf-8",
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [v0.9.1] — 2026-05-17
|
|
8
|
+
|
|
9
|
+
### 🐛 Fixes
|
|
10
|
+
|
|
11
|
+
- **npm package:** fix `harness-subagents` vendored import of `harness-run-context` (`../../../../lib`); broken installs failed with `Cannot find module '../../../lib/harness-run-context.js'`.
|
|
12
|
+
|
|
13
|
+
## [v0.9.0] — 2026-05-17
|
|
14
|
+
|
|
15
|
+
### ✨ Features
|
|
16
|
+
|
|
17
|
+
- **Harness plan UX:** Pi-native `harness-turn` routing on `input` (no expanded-prompt matching); subagent `ask_user` bridged to parent UI; plan-phase budget cap 80k with debounced exhaustion events; thin `harness-plan` orchestrator with `harness-plan-commit`; `harness-turn.schema.json` and tests.
|
|
18
|
+
|
|
7
19
|
## [v0.8.0] — 2026-05-17
|
|
8
20
|
|
|
9
21
|
### ✨ Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultimate-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Ultimate AI coding harness for pi.dev — extensible skills, Obsidian wiki knowledge layer, compressed context, deterministic output",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"format": "biome format --write",
|
|
83
83
|
"format:check": "biome format",
|
|
84
84
|
"prepare": "lefthook install",
|
|
85
|
-
"test": "node --test test/harness-verify.test.mjs test/harness-ask-user.test.mjs test/harness-subagents-loader.test.mjs test/sentrux-rules-sync.test.mjs && npx -y tsx --test test/harness-vcc-settings.test.ts test/harness-plan-phase-policy.test.mjs test/harness-subagent-policy.test.mjs",
|
|
85
|
+
"test": "node --test test/harness-verify.test.mjs test/harness-ask-user.test.mjs test/harness-subagents-loader.test.mjs test/harness-subagents-import-path.test.mjs test/sentrux-rules-sync.test.mjs test/harness-budget-guard.test.mjs && npx -y tsx --test test/harness-vcc-settings.test.ts test/harness-plan-phase-policy.test.mjs test/harness-subagent-policy.test.mjs test/harness-turn-routing.test.mjs",
|
|
86
86
|
"test:vcc": "npx -y tsx --test vendor/pi-vcc/tests/*.test.ts",
|
|
87
87
|
"harness:sentrux-bootstrap": "node .pi/scripts/harness-sentrux-bootstrap.mjs",
|
|
88
88
|
"harness:sentrux-sync": "node .pi/scripts/sentrux-rules-sync.mjs --force",
|