ultimate-pi 0.22.0 → 0.22.2
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-context/SKILL.md +3 -3
- package/.agents/skills/harness-debate-plan/SKILL.md +2 -2
- package/.agents/skills/harness-decisions/SKILL.md +2 -2
- package/.agents/skills/harness-eval/SKILL.md +1 -1
- package/.agents/skills/harness-git-commit/SKILL.md +1 -1
- package/.agents/skills/harness-governor/SKILL.md +5 -5
- package/.agents/skills/harness-ls-lint-setup/SKILL.md +2 -2
- package/.agents/skills/harness-orchestration/SKILL.md +4 -4
- package/.agents/skills/harness-plan/SKILL.md +2 -2
- package/.agents/skills/harness-review/SKILL.md +2 -2
- package/.agents/skills/harness-sentrux-repair/SKILL.md +1 -1
- package/.agents/skills/harness-sentrux-setup/SKILL.md +2 -2
- package/.agents/skills/harness-spec/SKILL.md +1 -1
- package/.agents/skills/harness-steer/SKILL.md +2 -2
- package/.agents/skills/posthog-analyst/SKILL.md +1 -1
- package/.agents/skills/sentrux/SKILL.md +4 -4
- package/.agents/skills/web-retrieval/SKILL.md +1 -1
- package/.pi/agents/harness/ls-lint-steward.md +3 -3
- package/.pi/agents/harness/planning/decompose.md +1 -1
- package/.pi/agents/harness/planning/execution-plan-author.md +1 -1
- package/.pi/agents/harness/planning/hypothesis-validator.md +1 -1
- package/.pi/agents/harness/planning/hypothesis.md +1 -1
- package/.pi/agents/harness/planning/plan-adversary.md +1 -1
- package/.pi/agents/harness/planning/plan-evaluator.md +2 -2
- package/.pi/agents/harness/planning/plan-synthesizer.md +2 -2
- package/.pi/agents/harness/planning/review-integrator.md +1 -1
- package/.pi/agents/harness/planning/sprint-contract-auditor.md +5 -5
- package/.pi/agents/harness/running/executor.md +1 -1
- package/.pi/agents/harness/sentrux-repair-advisor.md +1 -1
- package/.pi/agents/harness/sentrux-steward.md +2 -2
- package/.pi/extensions/agt-kill-switch.ts +7 -1
- package/.pi/extensions/harness-plan-approval.ts +9 -1
- package/.pi/extensions/harness-run-context.ts +529 -84
- package/.pi/extensions/policy-gate.ts +15 -2
- package/.pi/harness/agents.manifest.json +16 -16
- package/.pi/harness/agents.policy.yaml +82 -3
- package/.pi/harness/specs/plan-task-clarification.schema.json +10 -1
- package/.pi/lib/agents-policy.mjs +42 -1
- package/.pi/lib/agt/build-evaluation-context.ts +3 -1
- package/.pi/lib/agt/kill-switch-state.ts +14 -0
- package/.pi/lib/agt/legacy-evaluate.ts +3 -1
- package/.pi/lib/ask-user/index.ts +2 -0
- package/.pi/lib/ask-user/merge-task-clarification.ts +5 -0
- package/.pi/lib/ask-user/policy.ts +23 -0
- package/.pi/lib/ask-user/presenters/glimpse.ts +8 -1
- package/.pi/lib/ask-user/presenters/headless.ts +15 -0
- package/.pi/lib/ask-user/presenters/select.ts +11 -2
- package/.pi/lib/ask-user/validate-core.mjs +16 -0
- package/.pi/lib/harness-artifact-gate.ts +75 -5
- package/.pi/lib/harness-repair-brief.ts +30 -4
- package/.pi/lib/harness-run-context.ts +804 -17
- package/.pi/lib/harness-schema-validate.ts +147 -38
- package/.pi/lib/harness-spawn-policy.ts +9 -0
- package/.pi/lib/harness-spawn-topology.ts +109 -7
- package/.pi/lib/harness-subagent-precheck.ts +21 -0
- package/.pi/lib/harness-subagent-submit-pipeline.ts +95 -21
- package/.pi/lib/harness-subagent-submit-register.ts +6 -1
- package/.pi/lib/harness-subagents-bridge.ts +3 -0
- package/.pi/lib/harness-yaml.ts +11 -3
- package/.pi/lib/plan-approval/create-plan.ts +2 -6
- package/.pi/lib/plan-debate-gate.ts +87 -0
- package/.pi/lib/plan-debate-lane.ts +8 -2
- package/.pi/lib/plan-human-gates.ts +322 -0
- package/.pi/prompts/harness-clear.md +25 -0
- package/.pi/prompts/harness-plan.md +11 -7
- package/.pi/prompts/harness-review.md +5 -5
- package/.pi/prompts/harness-run.md +2 -2
- package/.pi/prompts/harness-sentrux-steward.md +2 -2
- package/.pi/prompts/harness-setup.md +3 -3
- package/.pi/prompts/harness-steer.md +5 -5
- package/.pi/scripts/generate-agents-policy-yaml.mjs +73 -7
- package/.pi/scripts/harness-reconcile-run-context.mjs +62 -0
- package/.pi/scripts/harness-schema-compile-verify.mjs +29 -0
- package/.pi/scripts/harness-verify.mjs +100 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +13 -0
- package/README.md +4 -0
- package/package.json +9 -6
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
laneArtifactPathsForParallelProbesRound,
|
|
20
20
|
laneArtifactPathsForRound,
|
|
21
21
|
} from "./plan-debate-lanes.js";
|
|
22
|
+
import { getPlanDebateRoundStatus } from "./plan-debate-round-status.js";
|
|
22
23
|
import {
|
|
23
24
|
getMessengerRoundState,
|
|
24
25
|
loadMessengerState,
|
|
@@ -331,3 +332,89 @@ export function isReviewRoundArtifactPath(relPath: string): boolean {
|
|
|
331
332
|
norm === CONSOLIDATED_REVIEW_ARTIFACT
|
|
332
333
|
);
|
|
333
334
|
}
|
|
335
|
+
|
|
336
|
+
function roundIndexForFocus(
|
|
337
|
+
focus: PlanDebateFocus,
|
|
338
|
+
required: readonly PlanDebateFocus[],
|
|
339
|
+
): number {
|
|
340
|
+
const idx = required.indexOf(focus);
|
|
341
|
+
return idx >= 0 ? idx + 1 : 1;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** Actionable recovery steps when approve_plan is blocked by the debate gate. */
|
|
345
|
+
export async function buildPlanDebateGateRecovery(
|
|
346
|
+
projectRoot: string,
|
|
347
|
+
runId: string,
|
|
348
|
+
gate: PlanDebateGateResult,
|
|
349
|
+
): Promise<string> {
|
|
350
|
+
const runDir = join(projectRoot, ".pi", "harness", "runs", runId);
|
|
351
|
+
const messenger = await loadMessengerState(runDir);
|
|
352
|
+
const required: readonly PlanDebateFocus[] =
|
|
353
|
+
messenger?.required_focuses && messenger.required_focuses.length > 0
|
|
354
|
+
? messenger.required_focuses
|
|
355
|
+
: (["spec", "wbs", "schedule", "quality"] as const);
|
|
356
|
+
const profile =
|
|
357
|
+
messenger?.debate_profile ?? gate.debate_profile ?? "standard";
|
|
358
|
+
const mode = messenger?.review_gate_mode ?? "threaded";
|
|
359
|
+
const coverage = gate.focus_coverage;
|
|
360
|
+
|
|
361
|
+
const lines: string[] = [
|
|
362
|
+
"Review Gate must finish before approve_plan.",
|
|
363
|
+
"",
|
|
364
|
+
"Blocking checks:",
|
|
365
|
+
...gate.errors.map((e) => `- ${e}`),
|
|
366
|
+
"",
|
|
367
|
+
`Debate profile: ${profile}, mode: ${mode}, required focuses: ${required.join(", ")}`,
|
|
368
|
+
"",
|
|
369
|
+
];
|
|
370
|
+
|
|
371
|
+
const needsConsensus = gate.errors.some(
|
|
372
|
+
(e) => e.includes("consensus") || e.includes(".consensus.json"),
|
|
373
|
+
);
|
|
374
|
+
const needsRounds = gate.errors.some(
|
|
375
|
+
(e) =>
|
|
376
|
+
e.includes("review_gate_ready") ||
|
|
377
|
+
e.includes("focus not covered") ||
|
|
378
|
+
e.includes("missing artifacts/") ||
|
|
379
|
+
e.includes("round events"),
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
if (needsRounds) {
|
|
383
|
+
const nextFocus: PlanDebateFocus =
|
|
384
|
+
(coverage?.missing[0] as PlanDebateFocus | undefined) ??
|
|
385
|
+
required[0] ??
|
|
386
|
+
"spec";
|
|
387
|
+
const roundIndex =
|
|
388
|
+
mode === "consolidated" ? 1 : roundIndexForFocus(nextFocus, required);
|
|
389
|
+
const status = await getPlanDebateRoundStatus(runDir, roundIndex, runId, {
|
|
390
|
+
debate_round_focus: mode === "consolidated" ? "all" : nextFocus,
|
|
391
|
+
});
|
|
392
|
+
lines.push(
|
|
393
|
+
`Next round: ${roundIndex} (focus: ${mode === "consolidated" ? "all" : nextFocus})`,
|
|
394
|
+
);
|
|
395
|
+
if (status.missing.length > 0) {
|
|
396
|
+
lines.push("Missing lane artifacts:");
|
|
397
|
+
for (const m of status.missing) {
|
|
398
|
+
lines.push(`- ${m}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (status.next_tool) {
|
|
402
|
+
lines.push(`Next tool: ${status.next_tool}`);
|
|
403
|
+
}
|
|
404
|
+
lines.push(
|
|
405
|
+
"Workflow: complete lane subagents (one per batch) → review-integrator → harness_debate_submit_round → harness_debate_focus_coverage.",
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (needsConsensus) {
|
|
410
|
+
lines.push(
|
|
411
|
+
"When all required focuses are covered and the last round has review_gate_ready: true, call harness_debate_consensus, then approve_plan again.",
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (gate.warnings.length > 0) {
|
|
416
|
+
lines.push("", "Warnings:", ...gate.warnings.map((w) => `- ${w}`));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return lines.join("\n");
|
|
420
|
+
}
|
|
@@ -48,12 +48,14 @@ export async function applyDebateLaneFromDoc(opts: {
|
|
|
48
48
|
lane: DebateLaneKind;
|
|
49
49
|
doc: Record<string, unknown>;
|
|
50
50
|
roundIndex?: number;
|
|
51
|
+
skipArtifactWrite?: boolean;
|
|
51
52
|
}): Promise<ApplyDebateLaneResult> {
|
|
52
53
|
return applyDebateLane({
|
|
53
54
|
runDir: opts.runDir,
|
|
54
55
|
lane: opts.lane,
|
|
55
56
|
content: JSON.stringify(opts.doc),
|
|
56
57
|
roundIndex: opts.roundIndex,
|
|
58
|
+
skipArtifactWrite: opts.skipArtifactWrite,
|
|
57
59
|
});
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -95,6 +97,8 @@ export async function applyDebateLane(opts: {
|
|
|
95
97
|
lane: DebateLaneKind;
|
|
96
98
|
content: string;
|
|
97
99
|
roundIndex?: number;
|
|
100
|
+
/** When true, artifact YAML was already written (e.g. submit pipeline); only messenger side effects run. */
|
|
101
|
+
skipArtifactWrite?: boolean;
|
|
98
102
|
}): Promise<ApplyDebateLaneResult> {
|
|
99
103
|
const errors: string[] = [];
|
|
100
104
|
let doc: Record<string, unknown>;
|
|
@@ -121,8 +125,10 @@ export async function applyDebateLane(opts: {
|
|
|
121
125
|
: (opts.roundIndex ?? 1);
|
|
122
126
|
const relPath = laneArtifactPath(opts.lane, roundIndex);
|
|
123
127
|
const absPath = join(opts.runDir, relPath);
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
if (!opts.skipArtifactWrite) {
|
|
129
|
+
await mkdir(dirname(absPath), { recursive: true });
|
|
130
|
+
await writeYamlFile(absPath, doc);
|
|
131
|
+
}
|
|
126
132
|
|
|
127
133
|
let messengerPosted = false;
|
|
128
134
|
let nextStep: string | undefined;
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-in-the-loop gates for /harness-plan — Phase 0 ask_user and Phase 6 approve_plan.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { constants } from "node:fs";
|
|
6
|
+
import { access } from "node:fs/promises";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import {
|
|
9
|
+
isHarnessNonInteractive,
|
|
10
|
+
isPlanApprovalAskUser,
|
|
11
|
+
} from "./ask-user/policy.js";
|
|
12
|
+
import {
|
|
13
|
+
hasPlanUserApproval,
|
|
14
|
+
indexOfLastPlanCommand,
|
|
15
|
+
} from "./harness-run-context.js";
|
|
16
|
+
import { validatePlanApprovalReadiness } from "./plan-approval-readiness.js";
|
|
17
|
+
import {
|
|
18
|
+
buildPlanDebateGateRecovery,
|
|
19
|
+
validatePlanDebateGate,
|
|
20
|
+
} from "./plan-debate-gate.js";
|
|
21
|
+
import {
|
|
22
|
+
isTaskClarificationReady,
|
|
23
|
+
readTaskClarificationDoc,
|
|
24
|
+
type TaskClarificationReadiness,
|
|
25
|
+
validateTaskClarificationDoc,
|
|
26
|
+
} from "./plan-task-clarification.js";
|
|
27
|
+
|
|
28
|
+
const EXPLICIT_ACCEPTANCE_RE =
|
|
29
|
+
/\b(acceptance|success criteria|definition of done|done when|must (pass|satisfy)|out of scope|in scope)\b/i;
|
|
30
|
+
|
|
31
|
+
type SessionEntryLike = {
|
|
32
|
+
type?: string;
|
|
33
|
+
customType?: string;
|
|
34
|
+
data?: unknown;
|
|
35
|
+
message?: {
|
|
36
|
+
role?: string;
|
|
37
|
+
toolName?: string;
|
|
38
|
+
details?: unknown;
|
|
39
|
+
content?: string | unknown[];
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function isNonInteractivePlan(): boolean {
|
|
44
|
+
return (
|
|
45
|
+
process.env.HARNESS_PLAN_NONINTERACTIVE === "1" || isHarnessNonInteractive()
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function askUserCallWasTaskClarification(details: unknown): boolean {
|
|
50
|
+
if (!details || typeof details !== "object") return false;
|
|
51
|
+
const d = details as { cancelled?: boolean; input?: unknown };
|
|
52
|
+
if (d.cancelled) return false;
|
|
53
|
+
const input = d.input as
|
|
54
|
+
| { question?: string; options?: unknown[]; questions?: unknown[] }
|
|
55
|
+
| undefined;
|
|
56
|
+
if (!input) return true;
|
|
57
|
+
return !isPlanApprovalAskUser(input);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function hasTaskClarificationAskUserSincePlanCommand(
|
|
61
|
+
entries: unknown[],
|
|
62
|
+
): boolean {
|
|
63
|
+
if (isNonInteractivePlan()) return true;
|
|
64
|
+
const since = Math.max(0, indexOfLastPlanCommand(entries));
|
|
65
|
+
for (let i = since; i < entries.length; i++) {
|
|
66
|
+
const entry = entries[i] as SessionEntryLike;
|
|
67
|
+
if (
|
|
68
|
+
entry.type === "custom" &&
|
|
69
|
+
entry.customType === "harness-task-clarification-engagement"
|
|
70
|
+
) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (entry.type !== "message" || entry.message?.role !== "toolResult") {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (entry.message.toolName !== "ask_user") continue;
|
|
77
|
+
if (askUserCallWasTaskClarification(entry.message.details)) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function hasClarificationFollowUpUserMessage(
|
|
85
|
+
entries: unknown[],
|
|
86
|
+
): boolean {
|
|
87
|
+
const since = Math.max(0, indexOfLastPlanCommand(entries));
|
|
88
|
+
for (let i = since; i < entries.length; i++) {
|
|
89
|
+
const entry = entries[i] as SessionEntryLike;
|
|
90
|
+
if (entry.type !== "message" || entry.message?.role !== "user") continue;
|
|
91
|
+
const content = entry.message.content;
|
|
92
|
+
const text =
|
|
93
|
+
typeof content === "string"
|
|
94
|
+
? content.trim()
|
|
95
|
+
: Array.isArray(content)
|
|
96
|
+
? content
|
|
97
|
+
.filter(
|
|
98
|
+
(c): c is { type: string; text?: string } =>
|
|
99
|
+
typeof c === "object" && c !== null && "type" in c,
|
|
100
|
+
)
|
|
101
|
+
.map((c) => c.text ?? "")
|
|
102
|
+
.join("")
|
|
103
|
+
.trim()
|
|
104
|
+
: "";
|
|
105
|
+
if (!text || text.startsWith("/")) continue;
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function isExplicitTaskAcceptance(taskSummary: string): boolean {
|
|
112
|
+
const t = taskSummary.trim();
|
|
113
|
+
if (t.length < 24) return false;
|
|
114
|
+
return EXPLICIT_ACCEPTANCE_RE.test(t);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface TaskClarificationHumanGateResult {
|
|
118
|
+
ok: boolean;
|
|
119
|
+
errors: string[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function validateTaskClarificationHumanGate(
|
|
123
|
+
entries: unknown[],
|
|
124
|
+
doc: Record<string, unknown> | null,
|
|
125
|
+
opts?: {
|
|
126
|
+
quick?: boolean;
|
|
127
|
+
taskSummary?: string;
|
|
128
|
+
allowFollowUpMessage?: boolean;
|
|
129
|
+
},
|
|
130
|
+
): TaskClarificationHumanGateResult {
|
|
131
|
+
const errors: string[] = [];
|
|
132
|
+
const status = String(doc?.status ?? "").toLowerCase();
|
|
133
|
+
if (status !== "ready") {
|
|
134
|
+
return { ok: true, errors };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const engagement = doc?.user_engagement as { source?: string } | undefined;
|
|
138
|
+
if (engagement?.source === "ask_user") {
|
|
139
|
+
return { ok: true, errors };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (hasTaskClarificationAskUserSincePlanCommand(entries)) {
|
|
143
|
+
return { ok: true, errors };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (
|
|
147
|
+
opts?.allowFollowUpMessage &&
|
|
148
|
+
hasClarificationFollowUpUserMessage(entries)
|
|
149
|
+
) {
|
|
150
|
+
return { ok: true, errors };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (opts?.quick && isExplicitTaskAcceptance(opts.taskSummary ?? "")) {
|
|
154
|
+
return { ok: true, errors };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
errors.push(
|
|
158
|
+
"Phase 0 requires ask_user before task-clarification status: ready. Call ask_user (harness-decisions skill), merge answers, then harness_artifact_ready.",
|
|
159
|
+
);
|
|
160
|
+
return { ok: false, errors };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function fileExists(path: string): Promise<boolean> {
|
|
164
|
+
try {
|
|
165
|
+
await access(path, constants.R_OK);
|
|
166
|
+
return true;
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface PlanHumanGateStatus {
|
|
173
|
+
phase0Ready: boolean;
|
|
174
|
+
phase0NeedsAskUser: boolean;
|
|
175
|
+
debateComplete: boolean;
|
|
176
|
+
debateRequired: boolean;
|
|
177
|
+
approvalRequired: boolean;
|
|
178
|
+
approvalRecorded: boolean;
|
|
179
|
+
nextRequiredAction: string | null;
|
|
180
|
+
/** Actionable Review Gate recovery when debateRequired. */
|
|
181
|
+
debateRecoveryHint: string | null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function resolvePlanHumanGateStatus(
|
|
185
|
+
projectRoot: string,
|
|
186
|
+
runId: string,
|
|
187
|
+
entries: unknown[],
|
|
188
|
+
opts?: { quick?: boolean; taskSummary?: string; lastOutcome?: string | null },
|
|
189
|
+
): Promise<PlanHumanGateStatus> {
|
|
190
|
+
const runDir = join(projectRoot, ".pi", "harness", "runs", runId);
|
|
191
|
+
const clar = await isTaskClarificationReady(runDir);
|
|
192
|
+
const clarDoc = clar.ok ? await readTaskClarificationDoc(runDir) : null;
|
|
193
|
+
const humanGate = validateTaskClarificationHumanGate(entries, clarDoc, {
|
|
194
|
+
quick: opts?.quick,
|
|
195
|
+
taskSummary: opts?.taskSummary,
|
|
196
|
+
allowFollowUpMessage: opts?.lastOutcome === "needs_clarification",
|
|
197
|
+
});
|
|
198
|
+
const phase0Ready = clar.ok && humanGate.ok;
|
|
199
|
+
const phase0NeedsAskUser = clar.ok && !humanGate.ok;
|
|
200
|
+
const approvalRecorded = hasPlanUserApproval(entries, {
|
|
201
|
+
sincePlanCommand: true,
|
|
202
|
+
});
|
|
203
|
+
const dagPath = join(runDir, "plan-packet.yaml");
|
|
204
|
+
const hasPacket = await fileExists(dagPath);
|
|
205
|
+
const messengerPath = join(runDir, "debate-messenger", "state.json");
|
|
206
|
+
const debateOpened = await fileExists(messengerPath);
|
|
207
|
+
|
|
208
|
+
let debateComplete = true;
|
|
209
|
+
let debateGate = null;
|
|
210
|
+
let readinessOk = false;
|
|
211
|
+
let approvalRequired = false;
|
|
212
|
+
|
|
213
|
+
if (phase0Ready && !approvalRecorded) {
|
|
214
|
+
const readiness = await validatePlanApprovalReadiness(projectRoot, runId, {
|
|
215
|
+
risk_level: String(clarDoc?.risk_level ?? "med"),
|
|
216
|
+
quick: opts?.quick,
|
|
217
|
+
});
|
|
218
|
+
readinessOk = readiness.ok;
|
|
219
|
+
debateGate = await validatePlanDebateGate(projectRoot, runId);
|
|
220
|
+
debateComplete = debateGate.ok;
|
|
221
|
+
approvalRequired = readiness.ok && debateComplete && hasPacket;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const debateRequired =
|
|
225
|
+
phase0Ready &&
|
|
226
|
+
!debateComplete &&
|
|
227
|
+
!approvalRecorded &&
|
|
228
|
+
(debateOpened || hasPacket);
|
|
229
|
+
|
|
230
|
+
let debateRecoveryHint: string | null = null;
|
|
231
|
+
let nextRequiredAction: string | null = null;
|
|
232
|
+
if (!phase0Ready) {
|
|
233
|
+
nextRequiredAction = phase0NeedsAskUser
|
|
234
|
+
? "ask_user (Phase 0 task contract)"
|
|
235
|
+
: "complete artifacts/task-clarification.yaml (Phase 0)";
|
|
236
|
+
} else if (debateRequired && debateGate) {
|
|
237
|
+
debateRecoveryHint = await buildPlanDebateGateRecovery(
|
|
238
|
+
projectRoot,
|
|
239
|
+
runId,
|
|
240
|
+
debateGate,
|
|
241
|
+
);
|
|
242
|
+
nextRequiredAction =
|
|
243
|
+
"Complete Review Gate (debate rounds + harness_debate_consensus) before approve_plan";
|
|
244
|
+
} else if (approvalRequired && !approvalRecorded) {
|
|
245
|
+
nextRequiredAction = "approve_plan then create_plan (Phase 6)";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
phase0Ready,
|
|
250
|
+
phase0NeedsAskUser,
|
|
251
|
+
debateComplete,
|
|
252
|
+
debateRequired,
|
|
253
|
+
approvalRequired,
|
|
254
|
+
approvalRecorded,
|
|
255
|
+
nextRequiredAction,
|
|
256
|
+
debateRecoveryHint,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function formatPlanHumanGateBlock(status: PlanHumanGateStatus): string {
|
|
261
|
+
if (!status.nextRequiredAction) return "";
|
|
262
|
+
const lines = [
|
|
263
|
+
"[HarnessPlanGate]",
|
|
264
|
+
`next_required_action=${status.nextRequiredAction}`,
|
|
265
|
+
`phase0_ready=${status.phase0Ready}`,
|
|
266
|
+
`review_gate_complete=${status.debateComplete}`,
|
|
267
|
+
`review_gate_required=${status.debateRequired}`,
|
|
268
|
+
`plan_approval_required=${status.approvalRequired}`,
|
|
269
|
+
`plan_approval_recorded=${status.approvalRecorded}`,
|
|
270
|
+
];
|
|
271
|
+
if (status.debateRequired) {
|
|
272
|
+
lines.push(
|
|
273
|
+
"Do not end this turn with prose only — call harness_debate_round_status / harness_debate_focus_coverage and spawn the next debate lane subagent (one per batch).",
|
|
274
|
+
);
|
|
275
|
+
} else {
|
|
276
|
+
lines.push(
|
|
277
|
+
"Do not spawn planning subagents or end this turn until the required human step completes.",
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (status.debateRecoveryHint) {
|
|
281
|
+
lines.push("", status.debateRecoveryHint);
|
|
282
|
+
}
|
|
283
|
+
return lines.join("\n");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function shouldBlockSubagentForMissingPlanApproval(
|
|
287
|
+
projectRoot: string,
|
|
288
|
+
runId: string,
|
|
289
|
+
entries: unknown[],
|
|
290
|
+
phase: string,
|
|
291
|
+
): Promise<{ block: boolean; reason?: string }> {
|
|
292
|
+
if (phase !== "plan" || isNonInteractivePlan()) return { block: false };
|
|
293
|
+
if (hasPlanUserApproval(entries, { sincePlanCommand: true })) {
|
|
294
|
+
return { block: false };
|
|
295
|
+
}
|
|
296
|
+
const status = await resolvePlanHumanGateStatus(projectRoot, runId, entries);
|
|
297
|
+
if (!status.approvalRequired) return { block: false };
|
|
298
|
+
return {
|
|
299
|
+
block: true,
|
|
300
|
+
reason:
|
|
301
|
+
"Plan Review Gate is complete but user approval is missing. Call approve_plan (then create_plan) before further subagent work.",
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export async function validateTaskClarificationReadyWithHumanGate(
|
|
306
|
+
runDir: string,
|
|
307
|
+
entries: unknown[],
|
|
308
|
+
opts?: { quick?: boolean; taskSummary?: string; lastOutcome?: string | null },
|
|
309
|
+
): Promise<TaskClarificationReadiness & { humanErrors: string[] }> {
|
|
310
|
+
const doc = await readTaskClarificationDoc(runDir);
|
|
311
|
+
const base = validateTaskClarificationDoc(doc, { requireReady: true });
|
|
312
|
+
const human = validateTaskClarificationHumanGate(entries, doc, {
|
|
313
|
+
quick: opts?.quick,
|
|
314
|
+
taskSummary: opts?.taskSummary,
|
|
315
|
+
allowFollowUpMessage: opts?.lastOutcome === "needs_clarification",
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
ok: base.ok && human.ok,
|
|
319
|
+
errors: [...base.errors, ...human.errors],
|
|
320
|
+
humanErrors: human.errors,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Safely delete historical harness run directories while preserving the active run.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# harness-clear
|
|
6
|
+
|
|
7
|
+
Delete only historical run directories under `.pi/harness/runs/`.
|
|
8
|
+
|
|
9
|
+
## What this does
|
|
10
|
+
|
|
11
|
+
- enumerates delete candidates strictly from `.pi/harness/runs/<run_id>/`
|
|
12
|
+
- always preserves active run ids discovered from session context and active-run pointer
|
|
13
|
+
- asks for one confirmation before any filesystem mutation
|
|
14
|
+
- fails closed: cancel/decline/timeout/error/unavailable confirmation paths delete nothing
|
|
15
|
+
- reports deleted vs protected/skipped counts
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
`/harness-clear`
|
|
20
|
+
|
|
21
|
+
## Safety boundaries
|
|
22
|
+
|
|
23
|
+
- in scope: historical run directories only
|
|
24
|
+
- out of scope: full `.pi/harness/` reset, non-run harness assets, active-run deletion overrides
|
|
25
|
+
- confirmation is mandatory; non-affirmative outcomes are no-op
|
|
@@ -5,13 +5,17 @@ argument-hint: "\"<task>\" [--risk low|med|high] [--quick]"
|
|
|
5
5
|
|
|
6
6
|
# harness-plan
|
|
7
7
|
|
|
8
|
-
You are the **planning orchestrator
|
|
8
|
+
You are the **planning orchestrator**. Produce an execution baseline (`plan-packet.yaml` + `plan-review.md`) with **lake-sized** outcomes and path-first tools. Parent owns gates: `ask_user`, `approve_plan({ human_summary? })`, `create_plan()`, plan-verify, and scoped writes under `.pi/harness/runs/<run_id>/`.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Use the phase order and spawn topology defined in this prompt directly.
|
|
11
11
|
|
|
12
12
|
Subagents persist artifacts via scoped **`submit_*`** tools (deterministic YAML under the run dir). Parent uses **`harness_artifact_ready`** to gate phases (no JSON parsing). Parent merges still use **`write_harness_yaml`** for `research-brief.yaml`, `plan-packet.yaml`, `planning-context.yaml`, and integrator patches.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
### Subagent submit → gate (required)
|
|
15
|
+
|
|
16
|
+
After a subprocess **`submit_*`** succeeds (or the artifact path is on disk and schema-valid), call **`harness_artifact_ready({ paths: ["<that-artifact>"] })` once** before the next phase or spawn. If spawn topology returns **Duplicate spawn blocked**, do **not** re-spawn that agent — call `harness_artifact_ready` on the existing artifact and advance. Never call the same `submit_*` twice with identical content (idempotent noop — end the subprocess turn instead).
|
|
17
|
+
|
|
18
|
+
**Phase 0 is mandatory** before reconnaissance or any planning subagent. `write_harness_yaml` and spawn topology enforce `artifacts/task-clarification.yaml` with `status: ready`.
|
|
15
19
|
|
|
16
20
|
## Allowed subagents
|
|
17
21
|
|
|
@@ -34,7 +38,7 @@ Read **harness-debate-plan** skill before Review Gate rounds.
|
|
|
34
38
|
|
|
35
39
|
1. Parallel `tasks` only for **independent** merges (implementation ∥ stack research; plan-evaluator ∥ plan-adversary for `parallel_probes`). **Never** parallelize decompose ∥ hypothesis.
|
|
36
40
|
2. Max **2** research lanes, **1** debate agent, **1** optional `planning-context` subagent per `subagent` call.
|
|
37
|
-
3. Downstream agents **read** upstream artifacts — do not re-derive
|
|
41
|
+
3. Downstream agents **read** upstream artifacts — do not re-derive upstream work.
|
|
38
42
|
|
|
39
43
|
## Performance rules
|
|
40
44
|
|
|
@@ -57,7 +61,7 @@ Use `[HarnessActivePlan]` / `[HarnessRunContext]` only. On revise: preserve `pla
|
|
|
57
61
|
|
|
58
62
|
## Phase 0 — Task clarification (mandatory; parent-led)
|
|
59
63
|
|
|
60
|
-
**Practice:** Collect requirements
|
|
64
|
+
**Practice:** Collect requirements and shared meaning before WBS (PMBOK; Crucial Conversations).
|
|
61
65
|
|
|
62
66
|
**Goal:** `artifacts/task-clarification.yaml` with `status: ready`, `unresolved_questions: []`, and a canonical `clarified_task`. No full planning until gated.
|
|
63
67
|
|
|
@@ -121,7 +125,7 @@ Decompose treats **`task-clarification.yaml` as authoritative** for scope; §1.1
|
|
|
121
125
|
|
|
122
126
|
## Phase 2b — Hypothesis-driven approach (sequential)
|
|
123
127
|
|
|
124
|
-
**Practice:** Lean exploration — falsifiable claim before plan detail
|
|
128
|
+
**Practice:** Lean exploration — require a falsifiable claim before plan detail.
|
|
125
129
|
|
|
126
130
|
**Requires** `artifacts/decomposition.yaml`. Do **not** spawn in parallel with decompose.
|
|
127
131
|
|
|
@@ -262,7 +266,7 @@ Med/low non-fork plans with clear stack and no implementation `open_questions` d
|
|
|
262
266
|
|
|
263
267
|
## Phase 5 — Structured inspection / Review Gate (Fagan-style)
|
|
264
268
|
|
|
265
|
-
**Practice:** Code Complete collaborative construction
|
|
269
|
+
**Practice:** Code Complete collaborative construction with Fagan-style inspection criteria. Parent is **chair**; one debate agent per `subagent` batch.
|
|
266
270
|
|
|
267
271
|
**Forbidden:** parallel `subagent` calls for any debate lane agent in one batch.
|
|
268
272
|
|
|
@@ -7,7 +7,7 @@ argument-hint: "[--run <run-id>] [--quick] [--readonly] [--trace <trace-ref>]"
|
|
|
7
7
|
|
|
8
8
|
You are the **post-run verification PM** (PMBOK Monitoring and Controlling). Run measure → judge → red team in one command. Parent owns `ask_user`, deterministic scripts, `harness_artifact_ready`, and run ownership (`--claim` on resume). Subagents persist via **`submit_*`** only (no parent `write` to verdict artifacts).
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Follow the review sequence in this prompt directly: deterministic checks → benchmark evaluator → verdict evaluator → adversary → optional tie-breaker.
|
|
11
11
|
|
|
12
12
|
Read **harness-orchestration** and **harness-review** skills before spawning.
|
|
13
13
|
|
|
@@ -91,7 +91,7 @@ notes: "…"
|
|
|
91
91
|
|
|
92
92
|
## Phase 1b — Sentrux repair advisor (subagent)
|
|
93
93
|
|
|
94
|
-
**Practice:** Close the loop from fitness-function observation
|
|
94
|
+
**Practice:** Close the loop from fitness-function observation to bounded repair directives. Skip when `artifacts/sentrux-repair-plan.yaml` already exists and `HARNESS_SENTRUX_RESCAN` is unset.
|
|
95
95
|
|
|
96
96
|
Spawn when **any**:
|
|
97
97
|
|
|
@@ -131,7 +131,7 @@ Gate:
|
|
|
131
131
|
harness_artifact_ready({ paths: ["artifacts/eval-verdict.yaml"] })
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
-
**Do not stop** after benchmark fail — continue to verdict (and adversary per tier) so `review-outcome.yaml` can route steer vs replan
|
|
134
|
+
**Do not stop** after benchmark fail — continue to verdict (and adversary per tier) so `review-outcome.yaml` can route steer vs replan.
|
|
135
135
|
|
|
136
136
|
## Phase 3 — Policy / quality audit (verdict evaluator)
|
|
137
137
|
|
|
@@ -153,7 +153,7 @@ Gate again with `harness_artifact_ready`.
|
|
|
153
153
|
|
|
154
154
|
## Phase 4 — Independent red team (adversary)
|
|
155
155
|
|
|
156
|
-
**Practice:** Generator–evaluator separation; adversary distinct from measurer
|
|
156
|
+
**Practice:** Generator–evaluator separation; adversary stays distinct from the measurer.
|
|
157
157
|
|
|
158
158
|
Skip when `--quick`. **Tiered steer:** full adversary on initial run + steer attempt 1; lite review (no adversary) on steer attempts 2+ unless prior `block_merge`.
|
|
159
159
|
|
|
@@ -185,7 +185,7 @@ subagent({ agentScope: "both", agent: "harness/reviewing/tie-breaker", task: "
|
|
|
185
185
|
|
|
186
186
|
- **Never** parse subprocess JSON to write `eval-verdict.yaml` or `adversary-report.yaml` — use `submit_*` + `harness_artifact_ready` only.
|
|
187
187
|
- Do not edit `plan-packet.yaml`.
|
|
188
|
-
- Do not run inline review checks in this session (
|
|
188
|
+
- Do not run inline review checks in this session (keep review work isolated to subagents).
|
|
189
189
|
- Same Pi session as `/harness-run` is preferred; `--claim` makes cross-session resume work.
|
|
190
190
|
|
|
191
191
|
## Phase 6 — Review outcome + repair brief (parent)
|
|
@@ -4,7 +4,7 @@ description: Execute only against an approved PlanPacket with strict phase gates
|
|
|
4
4
|
|
|
5
5
|
# harness-run
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Follow this prompt's execution flow directly: baseline gate → executor spawn → structural observation → review handoff.
|
|
8
8
|
|
|
9
9
|
You orchestrate the **Executing Process Group** — spawn `harness/running/executor` only. Do **not** implement inline.
|
|
10
10
|
|
|
@@ -106,7 +106,7 @@ phase: execute
|
|
|
106
106
|
|
|
107
107
|
## Parent rules
|
|
108
108
|
|
|
109
|
-
- On `scope_drift`, finish handoff and recommend **`/harness-review`** (review classifies
|
|
109
|
+
- On `scope_drift`, finish handoff and recommend **`/harness-review`** (review classifies whether the gap is planning or implementation).
|
|
110
110
|
- Do not call `ask_user` for plan-level ambiguity — return to plan command.
|
|
111
111
|
|
|
112
112
|
## Completion
|
|
@@ -39,14 +39,14 @@ Gate: `harness_artifact_ready({ paths: ["artifacts/sentrux-manifest-proposal.yam
|
|
|
39
39
|
Read `artifacts/sentrux-manifest-proposal.yaml`.
|
|
40
40
|
|
|
41
41
|
- `change_class: none` → report no manifest change; stop.
|
|
42
|
-
- Otherwise → `ask_user` with summary, evidence bullets, and
|
|
42
|
+
- Otherwise → `ask_user` with summary, evidence bullets, and any draft decision text when a formal decision record is required.
|
|
43
43
|
|
|
44
44
|
On approval:
|
|
45
45
|
|
|
46
46
|
1. Apply `manifest_patch` to `.pi/harness/sentrux/architecture.manifest.json` (parent `write` or manual edit).
|
|
47
47
|
2. `node "$UP_PKG/.pi/scripts/harness-sentrux-bootstrap.mjs" --force`
|
|
48
48
|
3. Append session custom entry `harness-architecture-changed` (triggers rules sync extension).
|
|
49
|
-
4. If
|
|
49
|
+
4. If a formal decision record is required, file it in the target project's standard decision-log location.
|
|
50
50
|
|
|
51
51
|
On reject: keep manifest unchanged; document decision in run notes.
|
|
52
52
|
|
|
@@ -289,7 +289,7 @@ Quick smoke test:
|
|
|
289
289
|
sg -p 'function $NAME($$$ARGS) { $$$BODY }' --json 2>/dev/null | head -5 && echo "✓ ast-grep pattern matching works" || echo "! ast-grep smoke test — may need language-specific config"
|
|
290
290
|
```
|
|
291
291
|
|
|
292
|
-
### 2.7 — gh CLI (GitHub Issues Spec Storage
|
|
292
|
+
### 2.7 — gh CLI (GitHub Issues Spec Storage)
|
|
293
293
|
|
|
294
294
|
```bash
|
|
295
295
|
if ! command -v gh &>/dev/null || [ "$FORCE" = "true" ]; then
|
|
@@ -335,7 +335,7 @@ Installed and smoke-tested by `harness-cli-verify.sh` (`npm install -g @ls-lint/
|
|
|
335
335
|
|
|
336
336
|
## Step 3 — Pi Extension Packages
|
|
337
337
|
|
|
338
|
-
Bundled extensions load from the installed `ultimate-pi` package. The harness lens wrapper at `.pi/extensions/harness-lens.ts` loads `.pi/extensions/lib/harness-lens/` for edit autopatch, secrets blocking, deferred format, and LSP tools. Structural search uses shell `sg` (installed globally by setup); architecture gates use Sentrux.
|
|
338
|
+
Bundled extensions load from the installed `ultimate-pi` package. The harness lens wrapper at `.pi/extensions/harness-lens.ts` loads `.pi/extensions/lib/harness-lens/` for edit autopatch, secrets blocking, deferred format, and LSP tools. Structural search uses shell `sg` (installed globally by setup); architecture gates use Sentrux.
|
|
339
339
|
|
|
340
340
|
Harness lens findings are **complementary** to Sentrux:
|
|
341
341
|
|
|
@@ -471,7 +471,7 @@ Harness `ask_user` supports terminal (TUI), headless (CI), and Glimpse WebView (
|
|
|
471
471
|
| **Desktop Linux / macOS / WSLg** | `auto` or `glimpse` for richer questionnaires |
|
|
472
472
|
| **CI / `--non-interactive`** | Prompts skipped; do not expect WebView |
|
|
473
473
|
|
|
474
|
-
Append `HARNESS_ASK_USER_UI=tui` to `.env` when WebView is unavailable. The first real `ask_user` reports `ui_backend` and `ui_degraded` in tool details.
|
|
474
|
+
Append `HARNESS_ASK_USER_UI=tui` to `.env` when WebView is unavailable. The first real `ask_user` reports `ui_backend` and `ui_degraded` in tool details.
|
|
475
475
|
|
|
476
476
|
Template keys (placeholders — user fills secrets): `HARNESS_TELEMETRY_ENABLED`, `HARNESS_WEB_*`, `HARNESS_VCC_COMPACTION`, `HARNESS_VCC_DEBUG`, plus commented optional PostHog / Graphify vars.
|
|
477
477
|
|
|
@@ -5,7 +5,7 @@ argument-hint: "[--attempt N]"
|
|
|
5
5
|
|
|
6
6
|
# harness-steer
|
|
7
7
|
|
|
8
|
-
Thin orchestrator for the **steer loop
|
|
8
|
+
Thin orchestrator for the **steer loop**. Run only after `/harness-review` produced `artifacts/review-outcome.yaml` and `artifacts/repair-brief.yaml` with `remediation_class: implementation_gap`.
|
|
9
9
|
|
|
10
10
|
## Preconditions
|
|
11
11
|
|
|
@@ -19,10 +19,10 @@ Thin orchestrator for the **steer loop** (ADR 0044). Run only after `/harness-re
|
|
|
19
19
|
2. Update `artifacts/steer-state.yaml` (`attempt`, `max_attempts`, `active: true`).
|
|
20
20
|
3. Set policy phase to **execute** before spawning executor (required for mutating tools).
|
|
21
21
|
4. One `ask_user` steer gate unless `run-context.steer_approved` is already true.
|
|
22
|
-
5. Spawn **`harness/running/executor`** with `HarnessSpawnContext.mode: repair` and `repair_brief_path: artifacts/repair-brief.yaml`. Repair uses the same hash-anchored `read`/`edit`, batching, and pre-handoff verification rules as `/harness-run
|
|
23
|
-
6. Optional: `node "$UP_PKG/.pi/scripts/harness-sentrux-cli.mjs" gate --save` after repair to refresh
|
|
24
|
-
7. Optional: `node "$UP_PKG/.pi/scripts/harness-ls-lint-cli.mjs"` after repair to confirm filename conventions
|
|
25
|
-
7. `next_command`: **`/harness-review`** (always re-verify; tiered adversary on attempts 2+
|
|
22
|
+
5. Spawn **`harness/running/executor`** with `HarnessSpawnContext.mode: repair` and `repair_brief_path: artifacts/repair-brief.yaml`. Repair uses the same hash-anchored `read`/`edit`, batching, and pre-handoff verification rules as `/harness-run`.
|
|
23
|
+
6. Optional: `node "$UP_PKG/.pi/scripts/harness-sentrux-cli.mjs" gate --save` after repair to refresh the structural baseline.
|
|
24
|
+
7. Optional: `node "$UP_PKG/.pi/scripts/harness-ls-lint-cli.mjs"` after repair to confirm filename conventions.
|
|
25
|
+
7. `next_command`: **`/harness-review`** (always re-verify; use tiered adversary on attempts 2+).
|
|
26
26
|
|
|
27
27
|
## Forbidden
|
|
28
28
|
|