@ilya-lesikov/pi-pi 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/3p/pi-subagents/package.json +0 -1
  2. package/3p/pi-subagents/src/agent-manager.ts +1 -0
  3. package/3p/pi-subagents/src/index.ts +1 -0
  4. package/3p/pi-subagents/src/types.ts +8 -0
  5. package/extensions/orchestrator/agents/advisor.ts +2 -1
  6. package/extensions/orchestrator/agents/code-reviewer.ts +1 -0
  7. package/extensions/orchestrator/agents/constraints.test.ts +43 -10
  8. package/extensions/orchestrator/agents/constraints.ts +26 -5
  9. package/extensions/orchestrator/agents/deep-debugger.ts +7 -6
  10. package/extensions/orchestrator/agents/explore.ts +1 -1
  11. package/extensions/orchestrator/agents/librarian.ts +1 -1
  12. package/extensions/orchestrator/agents/planner.ts +1 -1
  13. package/extensions/orchestrator/agents/prompts.test.ts +135 -1
  14. package/extensions/orchestrator/agents/reviewer.ts +2 -2
  15. package/extensions/orchestrator/agents/task.ts +6 -2
  16. package/extensions/orchestrator/agents/tool-routing.ts +22 -6
  17. package/extensions/orchestrator/assumptions.test.ts +77 -0
  18. package/extensions/orchestrator/assumptions.ts +85 -0
  19. package/extensions/orchestrator/cbm.ts +4 -1
  20. package/extensions/orchestrator/cbm.which.test.ts +92 -0
  21. package/extensions/orchestrator/command-handlers.ts +9 -1
  22. package/extensions/orchestrator/config.test.ts +1 -3
  23. package/extensions/orchestrator/config.ts +3 -4
  24. package/extensions/orchestrator/context.test.ts +2 -7
  25. package/extensions/orchestrator/context.ts +3 -3
  26. package/extensions/orchestrator/doctor.test.ts +35 -0
  27. package/extensions/orchestrator/doctor.ts +4 -2
  28. package/extensions/orchestrator/event-handlers.more.test.ts +185 -0
  29. package/extensions/orchestrator/event-handlers.test.ts +22 -2
  30. package/extensions/orchestrator/event-handlers.ts +156 -18
  31. package/extensions/orchestrator/flant-infra.more.test.ts +55 -0
  32. package/extensions/orchestrator/flant-infra.test.ts +0 -3
  33. package/extensions/orchestrator/flant-infra.ts +17 -7
  34. package/extensions/orchestrator/integration.test.ts +355 -173
  35. package/extensions/orchestrator/orchestrator.ts +3 -9
  36. package/extensions/orchestrator/phases/brainstorm.test.ts +25 -7
  37. package/extensions/orchestrator/phases/brainstorm.ts +35 -71
  38. package/extensions/orchestrator/phases/implementation.test.ts +22 -0
  39. package/extensions/orchestrator/phases/implementation.ts +6 -0
  40. package/extensions/orchestrator/phases/machine.test.ts +0 -28
  41. package/extensions/orchestrator/phases/machine.ts +0 -38
  42. package/extensions/orchestrator/phases/planning.test.ts +27 -1
  43. package/extensions/orchestrator/phases/planning.ts +1 -1
  44. package/extensions/orchestrator/phases/review-task.test.ts +7 -0
  45. package/extensions/orchestrator/phases/review-task.ts +1 -1
  46. package/extensions/orchestrator/phases/review.test.ts +47 -10
  47. package/extensions/orchestrator/phases/review.ts +36 -25
  48. package/extensions/orchestrator/pp-menu.from.test.ts +65 -0
  49. package/extensions/orchestrator/pp-menu.leaves.test.ts +17 -0
  50. package/extensions/orchestrator/pp-menu.test.ts +106 -49
  51. package/extensions/orchestrator/pp-menu.ts +280 -134
  52. package/extensions/orchestrator/pp-state-tools.ts +1 -1
  53. package/extensions/orchestrator/rate-limit-fallback-flow.test.ts +2 -2
  54. package/extensions/orchestrator/rate-limit-fallback.ts +1 -1
  55. package/extensions/orchestrator/state.test.ts +17 -17
  56. package/extensions/orchestrator/state.ts +35 -12
  57. package/package.json +6 -3
  58. package/scripts/lib/smoke-resolve.mjs +99 -0
  59. package/scripts/postinstall.mjs +93 -0
  60. package/scripts/test-package.sh +62 -0
  61. package/scripts/postinstall.sh +0 -18
@@ -441,8 +441,6 @@ export class Orchestrator {
441
441
  switch (this.active.state.phase) {
442
442
  case "brainstorm":
443
443
  return brainstormSystemPrompt(this.active.type, this.active.description, this.active.dir, this.cwd);
444
- case "debug":
445
- return brainstormSystemPrompt(this.active.type, this.active.description, this.active.dir, this.cwd);
446
444
  case "plan":
447
445
  return planningSystemPrompt(this.active.dir, mode);
448
446
  case "implement":
@@ -592,8 +590,7 @@ export class Orchestrator {
592
590
  log.info({ s: "task", dir, taskId: this.active.taskId, phase: state.phase, step: state.step }, "task activated");
593
591
 
594
592
  const modelConfig = this.config.agents.orchestrators[
595
- type === "debug" ? "debug"
596
- : type === "brainstorm" ? "brainstorm"
593
+ type === "brainstorm" ? "brainstorm"
597
594
  : type === "review" ? "review"
598
595
  : type === "quick" ? "quick"
599
596
  : "implement"
@@ -611,7 +608,7 @@ export class Orchestrator {
611
608
  this.injectContextAndArtifacts(this.active.dir, this.active.state.phase);
612
609
 
613
610
  this.phaseStartTime = Date.now();
614
- const isGenericDescription = ["implement", "debug", "brainstorm", "review"].includes(this.active.description);
611
+ const isGenericDescription = ["implement", "brainstorm", "review"].includes(this.active.description);
615
612
  const isGenericQuickDescription = this.active.description === "quick";
616
613
  const hasInheritedTaskContext = Boolean(fromTaskDir && type === "implement");
617
614
  const isWaitingForPlanners = this.active.state.phase === "plan" && this.active.state.step === "await_planners";
@@ -829,7 +826,6 @@ export class Orchestrator {
829
826
  // block. Mirrors the phase→model selection in injectContextAndArtifacts.
830
827
  mainAgentConfigForPhase(phase: Phase | undefined): { model: string; thinking: string } {
831
828
  const o = this.config.agents.orchestrators;
832
- if (phase === "debug" && this.active?.type === "debug") return o.debug;
833
829
  if (phase === "brainstorm" && this.active?.type === "brainstorm") return o.brainstorm;
834
830
  if (phase === "review" && this.active?.type === "review") return o.review;
835
831
  if (phase === "plan") return o.plan;
@@ -840,9 +836,7 @@ export class Orchestrator {
840
836
  const log = getLogger();
841
837
  log.debug({ s: "context", taskDir, phase }, "injecting context and artifacts");
842
838
  const modelSpec =
843
- phase === "debug" && this.active?.type === "debug"
844
- ? this.config.agents.orchestrators.debug.model
845
- : phase === "brainstorm" && this.active?.type === "brainstorm"
839
+ phase === "brainstorm" && this.active?.type === "brainstorm"
846
840
  ? this.config.agents.orchestrators.brainstorm.model
847
841
  : phase === "review" && this.active?.type === "review"
848
842
  ? this.config.agents.orchestrators.review.model
@@ -6,19 +6,37 @@ import { brainstormSystemPrompt, spawnBrainstormReviewers } from "./brainstorm.j
6
6
  import { getDefaultConfig } from "../config.js";
7
7
 
8
8
  describe("brainstormSystemPrompt", () => {
9
- it("debug prompt body is pure procedure (no completion/menu restatements)", () => {
10
- const prompt = brainstormSystemPrompt("debug", "fix a bug", "/tmp/task", "/tmp");
11
- expect(prompt).toContain("DEBUG PHASE");
12
- expect(prompt).not.toContain("pp_phase_complete");
13
- expect(prompt).not.toContain("/pp");
14
- });
15
-
16
9
  it("brainstorm prompt body is pure procedure (no completion/menu restatements)", () => {
17
10
  const prompt = brainstormSystemPrompt("brainstorm", "explore ideas", "/tmp/task", "/tmp");
18
11
  expect(prompt).toContain("conversation");
19
12
  expect(prompt).not.toContain("pp_phase_complete");
20
13
  expect(prompt).not.toContain("/pp");
21
14
  });
15
+
16
+ it("conversation branch uses the Socratic one-at-a-time-with-second-push clarify policy", () => {
17
+ const prompt = brainstormSystemPrompt("brainstorm", "explore ideas", "/tmp/task", "/tmp");
18
+ expect(prompt).toContain("CLARIFY ONE AT A TIME");
19
+ expect(prompt).toContain("push once more");
20
+ expect(prompt).not.toContain("batch them into one focused round");
21
+ });
22
+
23
+ it("task branch also carries the Socratic clarify policy and degrades to recorded assumptions when autonomous", () => {
24
+ const prompt = brainstormSystemPrompt("task", "do the thing", "/tmp/task", "/tmp");
25
+ expect(prompt).toContain("CLARIFY ONE AT A TIME");
26
+ expect(prompt).toContain("artifacts/ASSUMPTIONS.md");
27
+ });
28
+
29
+ it("both branches state anti-sycophancy as a behavior, with the quoted phrase adjacent to an example marker", () => {
30
+ for (const t of ["brainstorm", "task"] as const) {
31
+ const prompt = brainstormSystemPrompt(t, "x", "/tmp/task", "/tmp");
32
+ expect(prompt).toContain("take a position");
33
+ expect(prompt).toMatch(/what evidence would change|what would change it/i);
34
+ // The quoted anti-pattern phrase must sit right after an explicit example
35
+ // marker on the same line — a stray "e.g." elsewhere in the prompt must not
36
+ // satisfy this.
37
+ expect(prompt).toMatch(/(illustrative anti-patterns|Examples of the anti-pattern)[^\n]{0,60}'that could work'/);
38
+ }
39
+ });
22
40
  });
23
41
 
24
42
  describe("spawnBrainstormReviewers missing prerequisites", () => {
@@ -19,75 +19,44 @@ function isEnabled(value: { enabled?: boolean } | undefined): boolean {
19
19
  // advisor to consult by default (Claude-driven → a GPT-family advisor; GPT-driven
20
20
  // → a Claude-family advisor). Advisors are model-named pool subagents; pick one
21
21
  // from the roster in the delegation guidance.
22
- export function interactiveFlowBlock(driverFamily: string, defaultAdvisor: string): string {
22
+ //
23
+ // The clarify step is caller-selected. "socratic" (brainstorm) elicits design intent
24
+ // one question at a time with a second push, because the first answer to a design
25
+ // question is usually the polished/surface one. "scope" (review-task intake) is
26
+ // factual up-front scoping (which repo/branch/PR/range), not design elicitation, so
27
+ // it deliberately keeps the batch-it-up-front behavior. `conversational` selects the
28
+ // wording for the free-form brainstorm conversation (which is driven by the user turn
29
+ // by turn) over the one-shot phase task; it never emits `/pp` or `pp_phase_complete`.
30
+ export function interactiveFlowBlock(
31
+ driverFamily: string,
32
+ defaultAdvisor: string,
33
+ opts: { clarify?: "socratic" | "scope"; conversational?: boolean } = {},
34
+ ): string {
35
+ const { clarify = "socratic", conversational = false } = opts;
36
+ const clarifyStep =
37
+ clarify === "scope"
38
+ ? "1. CLARIFY SCOPE UP-FRONT: if what to review is ambiguous (which repo/branch/PR/commit-range, what to focus on), ask up front before diving in. This is factual scoping, not design elicitation — ask them up front in quick succession (still one focused question per ask_user call, never bundled into one prompt) rather than dripping them out across the work. If the scope is clear, skip straight to step 2."
39
+ : "1. CLARIFY ONE AT A TIME: if the request is ambiguous, ask ONE focused question, then WAIT for the answer before asking the next — do NOT batch a list. The first answer to a design question is usually the polished/surface one; push once more on it (\"what would that actually look like / what breaks if…\") to reach the real answer before moving on. Skip any question the request already answers. If running autonomously with no user to ask, do NOT block: make the strongest-supported assumption and record it via the assumptions rule in your constraints (artifacts/ASSUMPTIONS.md) instead of asking.";
40
+ const workStep = conversational
41
+ ? "2. WORK AUTONOMOUSLY IN THE MIDDLE: once the topic is clear, research/explore/design without stopping to ask. Delegate to subagents (parallel explores for broad searches) and use tools directly for quick lookups. Do NOT interrupt with piecemeal mid-flow questions — collect uncertainties and raise them together at a natural checkpoint. The one-at-a-time push above is for the clarify stage, not license to interrupt mid-work."
42
+ : "2. WORK AUTONOMOUSLY: research, explore, and design without stopping to ask. Delegate to subagents (parallel explores for broad searches). Do NOT interrupt mid-flow with questions — collect uncertainties for step 4 instead. Only a genuine blocker (you cannot proceed at all) justifies an ask here. The one-at-a-time push above is for the clarify stage, not license to interrupt mid-work.";
43
+ const presentStep = conversational
44
+ ? "6. PRESENT RESULTS: give conclusions as a clear, structured summary (findings, decisions, recommended direction) — don't just dump raw results."
45
+ : "6. PRESENT RESULTS: end with a structured summary (what you found, the decisions, the recommended direction) and hand back with the standard closing block.";
23
46
  return [
24
- "# Flow (minimize interruptions):",
25
- "1. CLARIFY UP-FRONT: if the request is ambiguous, ask your clarifying question(s) now, at the very start — batch them. If it's clear, skip straight to step 2.",
26
- "2. WORK AUTONOMOUSLY: research, explore, and design without stopping to ask. Delegate to subagents (parallel explores for broad searches). Do NOT interrupt mid-flow with questions — collect uncertainties for step 4 instead. Only a genuine blocker (you cannot proceed at all) justifies an ask here.",
47
+ conversational ? "# Flow (minimize interruptions — the streamlined flow, adapted to conversation):" : "# Flow (minimize interruptions):",
48
+ clarifyStep,
49
+ workStep,
27
50
  `3. CONSULT AN ADVISOR: before presenting, get an independent second opinion from an advisor whose model family differs from yours. You run on ${driverFamily}, so default to ${defaultAdvisor}. Escalate to more advisors for hard or high-stakes calls.`,
28
- "4. CLARIFY AT THE END: surface any remaining decisions as focused asks — one at a time.",
51
+ "4. CLARIFY AT THE END: surface any remaining decisions as focused asks — one at a time. Don't bury unresolved questions inside an approval prompt or the summary.",
29
52
  "5. APPROVE COMMITTED SPECIFICS: before finalizing, when your output commits to concrete, costly-to-reverse or opinion-heavy choices — exact wording, structure, naming, default values, or interface signatures — show the ACTUAL proposed text/values inline in your message, then ask for explicit approval. Don't silently invent and bury them.",
30
- "6. PRESENT RESULTS: end with a structured summary (what you found, the decisions, the recommended direction) and hand back with the standard closing block.",
53
+ presentStep,
54
+ ...(conversational ? ["This is still a conversation: the user drives it and may steer at any time. The flow shapes HOW you work between their messages — it does not forbid responding to what they say."] : []),
31
55
  ].join("\n");
32
56
  }
33
57
 
34
58
  export function brainstormSystemPrompt(taskType: TaskType, taskDescription: string, taskDir: string, cwd: string): string {
35
59
  const registerReposInstruction = `First, register all git repositories you'll work in using pp_register_repo (including the root: ${cwd}). For each, determine the base branch by examining the current branch and remote tracking.`;
36
- if (taskType === "debug") {
37
- return [
38
- "[PI-PI — DEBUG PHASE]",
39
- `Problem: ${taskDescription}`,
40
- "",
41
- registerReposInstruction,
42
- "",
43
- "Read-only diagnosis mode. You MAY use write/edit for diagnosis only (repro/test/analysis files) — never to implement the actual fix or feature.",
44
- "",
45
- interactiveFlowBlock("GPT", "a Claude-family advisor"),
46
- "",
47
- "# Your job:",
48
- "1. Clarify the problem with the user if needed",
49
- "2. Delegate research to subagents where useful (see the delegation guidance in your system prompt), and use bash to run commands, check logs, reproduce issues",
50
- "3. Use tools to trace the bug:",
51
- " - cbm_search/cbm_search_code: find relevant code by concept or text",
52
- " - lsp goToDefinition, findReferences, hover: precise navigation and type info",
53
- " - lsp goToImplementation: check interface implementors",
54
- " - lsp diagnostics: find type errors",
55
- " - cbm_trace: trace call chains to/from suspect functions",
56
- " - ast_search: find structural patterns (e.g. error handling, goroutines)",
57
- "",
58
- "Produce two files:",
59
- `- ${taskDir}/USER_REQUEST.md — the fix request. MUST follow this exact structure:`,
60
- " # User Request",
61
- " <1-3 sentence distillation of the fix needed>",
62
- " ## Problem",
63
- " <What's broken, from the user's perspective / your diagnosis>",
64
- " ## Constraints",
65
- " <User-stated boundaries or critical constraints discovered during diagnosis>",
66
- `- ${taskDir}/RESEARCH.md — MUST follow this exact structure:`,
67
- " ## Affected Code",
68
- " <file:symbol — one-line role, per line>",
69
- " ## Architecture Context",
70
- " <Dense bullets. How affected pieces connect. Sub-group by subsystem for complex tasks.>",
71
- " ## Constraints & Edge Cases",
72
- " - MUST: <hard requirements discovered from code>",
73
- " - RISK: <things that could break>",
74
- " ## Open Questions",
75
- " <Unresolved items needing user input. Omit section if none.>",
76
- "",
77
- "This is the LAST interactive phase. If the task continues in autonomous mode, the downstream plan/implement phases cannot ask the user anything — so resolve every Open Question now (answer it, or mark it DECIDED:/ASSUMED: with rationale). Do NOT defer questions to the plan phase.",
78
- "These files are validated programmatically. Missing sections or unexpected sections will be rejected.",
79
- "Use pp_write_state_file / pp_edit_state_file (NOT the generic write/edit) for .pp state files — they keep the output compact and validate structure.",
80
- "",
81
- "# Optional: focused analysis artifacts",
82
- `You may also write additional analysis files to ${taskDir}/artifacts/<name>.md`,
83
- "for deep dives on specific topics (e.g. architecture analysis, API comparison, risk assessment).",
84
- "Each artifact must start with # <Title>. Content is freeform. These are reviewed alongside USER_REQUEST.md and RESEARCH.md.",
85
- "Do NOT duplicate content already in RESEARCH.md — artifacts are for supplementary deep dives.",
86
- "",
87
- "Keep USER_REQUEST.md current: update it whenever the user's request changes or clarifies, so it never goes stale.",
88
- ].join("\n");
89
- }
90
-
91
60
  if (taskType === "brainstorm") {
92
61
  return [
93
62
  "[PI-PI — BRAINSTORM]",
@@ -99,17 +68,12 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
99
68
  "Your primary job is to TALK WITH THE USER. Explore ideas, analyze tradeoffs, answer questions, discuss approaches.",
100
69
  "Do NOT rush to produce artifacts or finish. Stay in the conversation until the user is satisfied.",
101
70
  "",
102
- "# Flow (minimize interruptions the streamlined flow, adapted to conversation):",
103
- "1. CLARIFY UP-FRONT: if the topic is ambiguous, ask your clarifying question(s) at the very start — batch them into one focused round rather than dripping them out.",
104
- "2. WORK AUTONOMOUSLY IN THE MIDDLE: once the topic is clear, research/explore/design without stopping to ask. Delegate to subagents (parallel explores for broad searches) and use tools directly for quick lookups. Do NOT interrupt with piecemeal mid-flow questions — collect uncertainties and raise them together at a natural checkpoint.",
105
- "3. CONSULT AN ADVISOR: before landing on a recommendation, get an independent second opinion from an advisor whose model family differs from yours. You run on Claude, so default to a GPT-family advisor. Escalate to more advisors for hard or high-stakes calls.",
106
- "4. CLARIFY AT THE END: before finalizing, surface any remaining open decisions as focused asks — one at a time. Don't bury unresolved questions inside an approval prompt or the summary.",
107
- "5. APPROVE COMMITTED SPECIFICS: when you commit to concrete, costly-to-reverse or opinion-heavy choices — exact wording, structure, naming, default values, or interface signatures — show the ACTUAL proposed text/values inline, then get explicit approval. Don't silently invent and bury them.",
108
- "6. PRESENT RESULTS: give conclusions as a clear, structured summary (findings, decisions, recommended direction) — don't just dump raw results.",
109
- "This is still a conversation: the user drives it and may steer at any time. The flow shapes HOW you work between their messages — it does not forbid responding to what they say.",
71
+ interactiveFlowBlock("Claude", "a GPT-family advisor", { clarify: "socratic", conversational: true }),
110
72
  "",
111
73
  "# How to work:",
112
- "- Discuss the topic with the user. Propose approaches. Analyze tradeoffs.",
74
+ "- Discuss the topic with the user. Propose approaches. Analyze tradeoffs. When you give a judgment, take a position and say what evidence would change it — do NOT hedge without landing anywhere or offer empty validation (e.g. agreeing just to be agreeable). Examples of the anti-pattern to avoid: 'that could work', 'it depends' with no position taken.",
75
+ "- Scope triage up front: if the topic spans multiple independent subsystems, flag that immediately and help decompose it into sub-problems rather than treating it as one blob.",
76
+ "- Explore 2-3 viable approaches with tradeoffs; lead with your recommended one and say why. Do NOT dismiss a topic as 'too simple to need design' — that is where unexamined assumptions cause the most wasted work.",
113
77
  "- Delegate research to subagents where useful (see the delegation guidance in your system prompt).",
114
78
  "- Use tools directly for quick lookups (cbm_search, lsp, ast_search, grep, etc.)",
115
79
  "",
@@ -152,7 +116,7 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
152
116
  " - lsp documentSymbol, goToDefinition, findReferences, goToImplementation, hover",
153
117
  " - cbm_trace: trace call chains for dependency understanding",
154
118
  " - ast_search: find structural patterns across the codebase",
155
- "4. Explore design options: identify the viable approaches, weigh their tradeoffs, and land on a recommended direction (capture the reasoning in RESEARCH.md / an artifact)",
119
+ "4. Explore design options: identify 2-3 viable approaches, weigh their tradeoffs, and lead with a recommended direction + why (capture the reasoning in RESEARCH.md / an artifact). If the task spans multiple independent subsystems, triage that up front and decompose it rather than treating it as one blob. Do NOT dismiss anything as 'too simple to need design' — unexamined assumptions in 'simple' work cause the most wasted effort. When you give a judgment, take a position and state what evidence would change it; do NOT hedge without landing anywhere or offer empty validation (illustrative anti-patterns, any language: 'that could work', 'it depends' with no position).",
156
120
  "5. Ask the user follow-up questions as needed",
157
121
  "6. Actively drive every Open Question to resolution — chase down answers via research or by asking the user; the Open Questions section should be empty (or every entry marked DECIDED:/ASSUMED: with rationale) before you hand off, not a passive backlog. This is the LAST interactive phase: if the task continues in autonomous mode, the downstream plan/implement phases cannot ask the user anything, so a deferred question becomes an unanswered one. Resolve or explicitly ASSUME each now — do NOT defer to the plan phase.",
158
122
  "7. Write findings into RESEARCH.md as results come back — don't wait for all subagents",
@@ -9,3 +9,25 @@ describe("implementationSystemPrompt self-complete directive", () => {
9
9
  expect(prompt).not.toContain("do NOT wait for the user");
10
10
  });
11
11
  });
12
+
13
+ describe("implement-phase verification gate + conditional test-first", () => {
14
+ const prompt = implementationSystemPrompt("/tmp/task", "/tmp");
15
+
16
+ it("gates completion claims on fresh evidence with an explicit not-applicable path", () => {
17
+ expect(prompt).toContain("Verification gate");
18
+ expect(prompt).toMatch(/fresh tool output|proving evidence/i);
19
+ expect(prompt).toContain("not applicable");
20
+ });
21
+
22
+ it("states the gate as behavior, not a language-specific phrase list", () => {
23
+ expect(prompt).toMatch(/in any language|holds in any language/i);
24
+ });
25
+
26
+ it("adopts conditional test-first, not universal TDD or delete-untested-code", () => {
27
+ expect(prompt).toContain("Test-first policy");
28
+ expect(prompt).toMatch(/FAILING test first/);
29
+ expect(prompt).toContain("There is no rule to delete untested code");
30
+ expect(prompt).not.toMatch(/NO PRODUCTION CODE/i);
31
+ expect(prompt).toMatch(/not feasible|infeasible/);
32
+ });
33
+ });
@@ -25,6 +25,12 @@ export function implementationSystemPrompt(taskDir: string, cwd: string): string
25
25
  "6. After completing changes, run cbm_changes to verify blast radius",
26
26
  "7. Delegate to subagents where useful (see the delegation guidance in your system prompt) — spawn multiple explores in parallel for broad searches, and use task for parallelizable self-contained implementation subtasks",
27
27
  "",
28
+ "# Verification gate (before claiming any item done):",
29
+ "A completion claim is only valid when fresh tool output proves it. For each item, before you check it off: (1) identify what tool output would prove the claim (lsp diagnostics clean, a test run passing, cbm_changes showing the expected blast radius, a command exit code); (2) actually produce that output this turn; (3) cite it. This gates on whether the proving evidence EXISTS — not on how you phrase the claim — so it holds in any language. If a claim genuinely cannot be proven with your granted tools, say so explicitly and state why (\"not applicable — <reason>\", e.g. no runnable harness in this repo) rather than implying it was verified.",
30
+ "",
31
+ "# Test-first policy (conditional, not universal TDD):",
32
+ "For a behavior change or a bug fix where an automated test is feasible, write or reproduce the FAILING test first, then make it pass. For work where an automated test is not feasible (config, migrations, docs, pure refactor, no usable harness), state the verification method BEFORE editing instead. There is no rule to delete untested code and no requirement to test-first when a test is infeasible — pick the path that actually produces evidence.",
33
+ "",
28
34
  "# Commits:",
29
35
  "After completing a logical unit of work (a plan item, a bug fix, a test), call pp_commit",
30
36
  "with a descriptive message (what changed and why). Prefix it with a conventional-commit",
@@ -63,12 +63,6 @@ describe("canTransition", () => {
63
63
  expect(canTransition("implement", "done", "implement")).toBe(false);
64
64
  });
65
65
 
66
- it("handles debug transitions", () => {
67
- expect(canTransition("debug", "debug", "plan")).toBe(true);
68
- expect(canTransition("debug", "debug", "done")).toBe(false);
69
- expect(canTransition("debug", "done", "debug")).toBe(false);
70
- });
71
-
72
66
  it("handles brainstorm transitions", () => {
73
67
  expect(canTransition("brainstorm", "brainstorm", "plan")).toBe(true);
74
68
  expect(canTransition("brainstorm", "brainstorm", "done")).toBe(false);
@@ -89,11 +83,6 @@ describe("nextPhase", () => {
89
83
  expect(nextPhase("implement", "done")).toBeNull();
90
84
  });
91
85
 
92
- it("returns debug next phase and terminal null", () => {
93
- expect(nextPhase("debug", "debug")).toBe("plan");
94
- expect(nextPhase("debug", "done")).toBeNull();
95
- });
96
-
97
86
  it("returns brainstorm next phase and terminal null", () => {
98
87
  expect(nextPhase("brainstorm", "brainstorm")).toBe("plan");
99
88
  expect(nextPhase("brainstorm", "done")).toBeNull();
@@ -110,10 +99,6 @@ describe("phasePipeline", () => {
110
99
  expect(phasePipeline("implement")).toEqual(["brainstorm", "plan", "implement", "done"]);
111
100
  });
112
101
 
113
- it("returns debug pipeline", () => {
114
- expect(phasePipeline("debug")).toEqual(["debug", "plan", "implement", "done"]);
115
- });
116
-
117
102
  it("returns brainstorm pipeline", () => {
118
103
  expect(phasePipeline("brainstorm")).toEqual(["brainstorm", "plan", "implement", "done"]);
119
104
  });
@@ -285,19 +270,6 @@ Fix bug.
285
270
  expect(validateExitCriteria(pass, "implement", "brainstorm")).toEqual({ ok: true });
286
271
  });
287
272
 
288
- it("validates debug artifacts", () => {
289
- const missing = makeTempDir();
290
- expect(validateExitCriteria(missing, "debug", "debug")).toEqual({
291
- ok: false,
292
- reason: "USER_REQUEST.md does not exist or is empty",
293
- });
294
-
295
- const pass = makeTempDir();
296
- writeFileSync(join(pass, "USER_REQUEST.md"), VALID_USER_REQUEST, "utf-8");
297
- writeFileSync(join(pass, "RESEARCH.md"), VALID_RESEARCH, "utf-8");
298
- expect(validateExitCriteria(pass, "debug", "debug")).toEqual({ ok: true });
299
- });
300
-
301
273
  it("validates review phase — requires artifacts and an ANCHORS-bearing final_pass file", () => {
302
274
  const missing = makeTempDir();
303
275
  expect(validateExitCriteria(missing, "review", "review")).toEqual({
@@ -60,11 +60,6 @@ const TRANSITIONS: Record<TaskType, Record<string, string[]>> = {
60
60
  plan: ["implement"],
61
61
  implement: ["done"],
62
62
  },
63
- debug: {
64
- debug: ["plan"],
65
- plan: ["implement"],
66
- implement: ["done"],
67
- },
68
63
  brainstorm: {
69
64
  brainstorm: ["plan"],
70
65
  plan: ["implement"],
@@ -217,37 +212,6 @@ export function validateExitCriteria(
217
212
  return { ok: true };
218
213
  }
219
214
 
220
- case "debug": {
221
- const ur = join(taskDir, "USER_REQUEST.md");
222
- const res = join(taskDir, "RESEARCH.md");
223
- if (isMissingOrEmpty(ur)) {
224
- return { ok: false, reason: "USER_REQUEST.md does not exist or is empty" };
225
- }
226
- if (isMissingOrEmpty(res)) {
227
- return { ok: false, reason: "RESEARCH.md does not exist or is empty" };
228
- }
229
- const urContent = readFileSync(ur, "utf-8");
230
- const resContent = readFileSync(res, "utf-8");
231
-
232
- const userRequestValidation = validateUserRequest(urContent);
233
- if (!userRequestValidation.ok) {
234
- return {
235
- ok: false,
236
- reason: formatValidationErrors("USER_REQUEST.md", userRequestValidation.errors, USER_REQUEST_TEMPLATE),
237
- };
238
- }
239
-
240
- const researchValidation = validateResearch(resContent);
241
- if (!researchValidation.ok) {
242
- return {
243
- ok: false,
244
- reason: formatValidationErrors("RESEARCH.md", researchValidation.errors, RESEARCH_TEMPLATE),
245
- };
246
- }
247
-
248
- return { ok: true };
249
- }
250
-
251
215
  case "quick": {
252
216
  return { ok: true };
253
217
  }
@@ -261,8 +225,6 @@ export function phasePipeline(taskType: TaskType): Phase[] {
261
225
  switch (taskType) {
262
226
  case "implement":
263
227
  return ["brainstorm", "plan", "implement", "done"];
264
- case "debug":
265
- return ["debug", "plan", "implement", "done"];
266
228
  case "brainstorm":
267
229
  return ["brainstorm", "plan", "implement", "done"];
268
230
  case "review":
@@ -3,7 +3,13 @@ import { mkdtempSync, rmSync, writeFileSync } from "fs";
3
3
  import { tmpdir } from "os";
4
4
  import { join } from "path";
5
5
  import { planningSystemPrompt, spawnPlanReviewers } from "./planning.js";
6
- import { getDefaultConfig } from "../config.js";
6
+ import { createPlannerAgent } from "../agents/planner.js";
7
+ import { getDefaultConfig, resolvePreset } from "../config.js";
8
+
9
+ function plannerPlanFormatLines(): string {
10
+ const p = createPlannerAgent("opus", resolvePreset(getDefaultConfig(), "planners"), { userRequest: "u", research: "r", manifest: [] }, "/out.md", []);
11
+ return p.prompt;
12
+ }
7
13
 
8
14
  describe("planningSystemPrompt self-complete directive", () => {
9
15
  it("guided synthesis instructs the agent to call pp_phase_complete when synthesis is complete", () => {
@@ -19,6 +25,26 @@ describe("planningSystemPrompt self-complete directive", () => {
19
25
  });
20
26
  });
21
27
 
28
+ describe("plan no-placeholders rule is behavior-framed and lockstep across both surfaces", () => {
29
+ const behavior = "an item that defers a decision instead of stating a concrete outcome is a plan failure";
30
+ it("phrases the rule on behavior, with TBD/TODO marked as an illustrative example only", () => {
31
+ for (const prompt of [planningSystemPrompt("/tmp/task", "guided"), plannerPlanFormatLines()]) {
32
+ expect(prompt).toContain(behavior);
33
+ // The English placeholder tokens must appear only as an example, adjacent
34
+ // to an explicit example marker — never as the literal matching rule.
35
+ expect(prompt).toMatch(/'TBD'\/'TODO'\/'decide later' is only an illustrative example/);
36
+ }
37
+ });
38
+
39
+ it("keeps the plan-format guidance identical between planning.ts and planner.ts", () => {
40
+ const fromPhase = planningSystemPrompt("/tmp/task", "guided");
41
+ const fromAgent = plannerPlanFormatLines();
42
+ const line = (p: string) => p.split("\n").find((l) => l.includes(behavior));
43
+ expect(line(fromPhase)).toBeDefined();
44
+ expect(line(fromPhase)).toBe(line(fromAgent));
45
+ });
46
+ });
47
+
22
48
  describe("spawnPlanReviewers missing prerequisites", () => {
23
49
  async function run(setup: (dir: string) => void) {
24
50
  const taskDir = mkdtempSync(join(tmpdir(), "pi-pi-plan-spawn-"));
@@ -52,7 +52,7 @@ export function planningSystemPrompt(taskDir: string, mode: TaskMode): string {
52
52
  "- Start with # Plan",
53
53
  "- ## Scope: 2-4 lines — what changes, what doesn't, critical constraints",
54
54
  "- ## Checklist: each item is - [ ] <outcome> — Done when: <observable condition>",
55
- " Each item = one independently verifiable outcome. No code snippets or file-by-file instructions.",
55
+ " Each item = one independently verifiable outcome, right-sized to the smallest chunk worth verifying on its own. No code snippets or file-by-file instructions. Nest these INSIDE the Done-when condition (do not add sections or code): (a) a verification method — how the outcome is proven (test passes, diagnostics clean, command output); (b) for items meant for parallel delegation, the interface it consumes/produces (what earlier outcomes it depends on, what later ones depend on it); (c) NO unresolved items — an item that defers a decision instead of stating a concrete outcome is a plan failure; resolve it now (in any language: a placeholder such as 'TBD'/'TODO'/'decide later' is only an illustrative example of the deferral to avoid, not the literal thing being matched).",
56
56
  "- ## Pattern constraints: include this section whenever the task adds a type, function, parser, annotation, config key, enum, or any user-facing value. For each, name the CLOSEST EXISTING analog in the codebase (found by behavior, not filename) and the exact conventions the implementer MUST mirror: data shape (prefer one existing shape over inventing parallel/duplicated state), spelling/casing of user-facing values (match existing values — never invent a new casing), and parser/validation/error-handling shape. These are acceptance criteria, not suggestions. Omit the section only if the task adds none of the above.",
57
57
  "- ## Blockers: unresolved issues blocking implementation (omit if none)",
58
58
  "Write/update the synthesized plan with pp_write_state_file / pp_edit_state_file (NOT the generic write/edit) — they keep the output compact and validate structure.",
@@ -17,4 +17,11 @@ describe("review-task reviewSystemPrompt", () => {
17
17
  expect(prompt).toContain("RESEARCH.md");
18
18
  expect(prompt).toContain("pp_write_state_file");
19
19
  });
20
+
21
+ it("keeps up-front factual scope-clarify intake, NOT the brainstorm Socratic policy", () => {
22
+ const prompt = reviewSystemPrompt("/tmp/task", "/tmp/cwd");
23
+ expect(prompt).toContain("CLARIFY SCOPE UP-FRONT");
24
+ expect(prompt).toContain("If the scope is ambiguous, ask the user before diving in.");
25
+ expect(prompt).not.toContain("CLARIFY ONE AT A TIME");
26
+ });
20
27
  });
@@ -6,7 +6,7 @@ export function reviewSystemPrompt(taskDir: string, cwd: string): string {
6
6
  "",
7
7
  `First, register all git repositories you'll work in using pp_register_repo (including the root: ${cwd}). For each, determine the base branch by examining the current branch and remote tracking.`,
8
8
  "",
9
- interactiveFlowBlock("Claude", "a GPT-family advisor"),
9
+ interactiveFlowBlock("Claude", "a GPT-family advisor", { clarify: "scope" }),
10
10
  "",
11
11
  "You are reviewing code changes. The user will describe what to review in their first chat message",
12
12
  "(e.g. a branch, a commit range, uncommitted changes, or a GitHub PR URL). There is NO pre-seeded",
@@ -23,16 +23,6 @@ describe("reviewSystemPrompt apply_feedback wording", () => {
23
23
  expect(auto).not.toContain("pp_phase_complete");
24
24
  });
25
25
 
26
- it("debug uses the artifact-review branch: DEBUG header, brainstorm-reviews dir, no code-review tail", () => {
27
- const prompt = reviewSystemPrompt("/tmp/task", 1, "debug", "guided");
28
- expect(prompt).toContain("DEBUG REVIEW CYCLE");
29
- expect(prompt).toContain("Debug reviewer outputs are ready.");
30
- expect(prompt).toContain("/tmp/task/brainstorm-reviews/");
31
- expect(prompt).not.toContain("code-reviews");
32
- expect(prompt).not.toContain("Create a fix plan");
33
- expect(prompt).not.toContain("pp_phase_complete");
34
- });
35
-
36
26
  it("brainstorm apply-feedback prompt permits artifact updates and steers to the compact tools", () => {
37
27
  const prompt = reviewSystemPrompt("/tmp/task", 1, "brainstorm", "autonomous");
38
28
  expect(prompt).toContain("artifacts/");
@@ -41,6 +31,17 @@ describe("reviewSystemPrompt apply_feedback wording", () => {
41
31
  expect(prompt).toContain("Do NOT add, rename, or remove sections in USER_REQUEST.md or RESEARCH.md");
42
32
  });
43
33
 
34
+ it("the brainstorm synthesis path (which edits state files) carries the verify-before-accepting gate", () => {
35
+ // phase !== "review" AND edit-capable (mutates USER_REQUEST/RESEARCH/artifacts),
36
+ // so plan ① requires the gate here too; its evidence is the research/artifacts,
37
+ // not tool output.
38
+ for (const mode of ["autonomous", "guided"] as const) {
39
+ const prompt = reviewSystemPrompt("/tmp/task", 1, "brainstorm", mode);
40
+ expect(prompt).toContain("Before accepting a finding, verify it against the actual");
41
+ expect(prompt).toMatch(/reject it with your reasoning rather than agreeing performatively/);
42
+ }
43
+ });
44
+
44
45
  it("autonomous plan feedback folds into a new synthesized plan, not a separate fix-plan file", () => {
45
46
  // Re-review and the transition read only the latest `*synthesized*` plan, so
46
47
  // plan-phase feedback must land there.
@@ -55,6 +56,42 @@ describe("reviewSystemPrompt apply_feedback wording", () => {
55
56
  expect(impl).toContain("Implement the fixes");
56
57
  });
57
58
 
59
+ it("EVERY edit-capable synthesis path carries the verify-before-accepting fix gate; standalone review does not", () => {
60
+ for (const args of [
61
+ ["implement", "autonomous"],
62
+ ["implement", "guided"],
63
+ ["plan", "guided"],
64
+ ["plan", "autonomous"],
65
+ ] as const) {
66
+ const prompt = reviewSystemPrompt("/tmp/task", 1, args[0], args[1]);
67
+ expect(prompt).toContain("Before accepting a finding, verify it against the actual");
68
+ expect(prompt).toMatch(/reject it with your technical reasoning rather than agreeing performatively/);
69
+ expect(prompt).toContain("blocking-severity first");
70
+ }
71
+ const standalone = reviewSystemPrompt("/tmp/task", 1, "review", "guided");
72
+ expect(standalone).not.toContain("Before accepting a finding, verify it against the actual");
73
+ });
74
+
75
+ it("plan-phase fix gate demands plan-text evidence (not afterImplement/lsp) in BOTH modes", () => {
76
+ for (const mode of ["guided", "autonomous"] as const) {
77
+ const plan = reviewSystemPrompt("/tmp/task", 1, "plan", mode);
78
+ expect(plan).toContain("corrected plan text");
79
+ expect(plan).toContain("a code fix is out of scope in the plan phase");
80
+ }
81
+ const impl = reviewSystemPrompt("/tmp/task", 1, "implement", "guided");
82
+ expect(impl).toContain("afterImplement");
83
+ });
84
+
85
+ it("plan-phase feedback lands in a new synthesized plan (never a fix-plan file or code changes) in BOTH modes", () => {
86
+ for (const mode of ["guided", "autonomous"] as const) {
87
+ const plan = reviewSystemPrompt("/tmp/task", 1, "plan", mode);
88
+ expect(plan).toContain("NEW synthesized plan");
89
+ expect(plan).not.toContain("Create a fix plan");
90
+ expect(plan).not.toContain("Implement the fixes");
91
+ expect(plan).not.toContain("Run afterImplement commands");
92
+ }
93
+ });
94
+
58
95
  it("standalone review phase omits the fix-plan/implement/afterImplement tail", () => {
59
96
  const prompt = reviewSystemPrompt("/tmp/task", 1, "review", "guided");
60
97
  expect(prompt).toContain("REVIEW CYCLE");