@ilya-lesikov/pi-pi 0.7.0 → 0.9.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.
- package/3p/pi-plannotator/apps/pi-extension/README.md +6 -5
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +25 -10
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.code-review.test.ts +172 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +50 -12
- package/3p/pi-plannotator/apps/pi-extension/server/project.test.ts +45 -0
- package/3p/pi-plannotator/apps/pi-extension/server/project.ts +7 -6
- package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +41 -25
- package/3p/pi-subagents/src/agent-manager.ts +34 -1
- package/3p/pi-subagents/src/agent-runner.ts +66 -33
- package/3p/pi-subagents/src/index.ts +3 -38
- package/3p/pi-subagents/src/types.ts +4 -0
- package/extensions/orchestrator/agents/advisor.ts +35 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +7 -7
- package/extensions/orchestrator/agents/code-reviewer.ts +27 -11
- package/extensions/orchestrator/agents/constraints.test.ts +82 -0
- package/extensions/orchestrator/agents/constraints.ts +66 -2
- package/extensions/orchestrator/agents/deep-debugger.ts +35 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +7 -7
- package/extensions/orchestrator/agents/planner.ts +9 -8
- package/extensions/orchestrator/agents/prompts.test.ts +120 -0
- package/extensions/orchestrator/agents/reviewer.ts +48 -0
- package/extensions/orchestrator/agents/task.ts +6 -20
- package/extensions/orchestrator/agents/tool-routing.ts +39 -1
- package/extensions/orchestrator/ai-comment-cleanup.test.ts +89 -0
- package/extensions/orchestrator/ai-comment-cleanup.ts +73 -0
- package/extensions/orchestrator/command-handlers.test.ts +47 -0
- package/extensions/orchestrator/command-handlers.ts +44 -28
- package/extensions/orchestrator/config.test.ts +40 -1
- package/extensions/orchestrator/config.ts +18 -2
- package/extensions/orchestrator/context.test.ts +54 -0
- package/extensions/orchestrator/context.ts +81 -2
- package/extensions/orchestrator/custom-footer.test.ts +91 -0
- package/extensions/orchestrator/custom-footer.ts +20 -20
- package/extensions/orchestrator/event-handlers.test.ts +412 -2
- package/extensions/orchestrator/event-handlers.ts +556 -227
- package/extensions/orchestrator/flant-infra.test.ts +25 -0
- package/extensions/orchestrator/flant-infra.ts +91 -0
- package/extensions/orchestrator/index.ts +1 -1
- package/extensions/orchestrator/integration.test.ts +411 -20
- package/extensions/orchestrator/messages.test.ts +30 -0
- package/extensions/orchestrator/messages.ts +6 -0
- package/extensions/orchestrator/model-registry.test.ts +48 -1
- package/extensions/orchestrator/model-registry.ts +43 -1
- package/extensions/orchestrator/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/extensions/orchestrator/orchestrator.test.ts +113 -0
- package/extensions/orchestrator/orchestrator.ts +197 -60
- package/extensions/orchestrator/phases/brainstorm.ts +20 -27
- package/extensions/orchestrator/phases/implementation.test.ts +11 -0
- package/extensions/orchestrator/phases/implementation.ts +4 -6
- package/extensions/orchestrator/phases/machine.test.ts +36 -0
- package/extensions/orchestrator/phases/machine.ts +11 -1
- package/extensions/orchestrator/phases/planning.test.ts +16 -0
- package/extensions/orchestrator/phases/planning.ts +11 -4
- package/extensions/orchestrator/phases/review-task.test.ts +20 -0
- package/extensions/orchestrator/phases/review-task.ts +47 -22
- package/extensions/orchestrator/phases/review.test.ts +34 -0
- package/extensions/orchestrator/phases/review.ts +44 -6
- package/extensions/orchestrator/plannotator.ts +9 -6
- package/extensions/orchestrator/pp-menu.test.ts +207 -1
- package/extensions/orchestrator/pp-menu.ts +514 -402
- package/extensions/orchestrator/pp-state-tools.test.ts +192 -0
- package/extensions/orchestrator/pp-state-tools.ts +249 -0
- package/extensions/orchestrator/rate-limit-fallback-flow.test.ts +128 -0
- package/extensions/orchestrator/rate-limit-fallback.test.ts +98 -0
- package/extensions/orchestrator/rate-limit-fallback.ts +243 -0
- package/extensions/orchestrator/state.test.ts +9 -0
- package/extensions/orchestrator/state.ts +15 -0
- package/extensions/orchestrator/test-helpers.ts +4 -1
- package/extensions/orchestrator/transition-controller.test.ts +100 -0
- package/extensions/orchestrator/transition-controller.ts +61 -3
- package/extensions/orchestrator/usage-tracker.ts +5 -1
- package/extensions/orchestrator/validate-artifacts.test.ts +20 -0
- package/extensions/orchestrator/validate-artifacts.ts +2 -2
- package/package.json +1 -1
- package/AGENTS.md +0 -28
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VariantConfig } from "../config.js";
|
|
2
|
-
import { loadAllContextFiles } from "../context.js";
|
|
2
|
+
import { loadAllContextFiles, formatManifestBlock } from "../context.js";
|
|
3
3
|
import { resolveModel, getModelInfo } from "../model-registry.js";
|
|
4
4
|
import type { RepoInfo } from "../repo-utils.js";
|
|
5
5
|
import { buildRepoContext } from "./repo-context.js";
|
|
@@ -8,7 +8,7 @@ import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK } from "./tool-
|
|
|
8
8
|
export function createPlannerAgent(
|
|
9
9
|
variant: string,
|
|
10
10
|
variants: Record<string, VariantConfig>,
|
|
11
|
-
taskArtifacts: { userRequest: string; research: string },
|
|
11
|
+
taskArtifacts: { userRequest: string; research: string; manifest?: { title: string; path: string }[] },
|
|
12
12
|
outputPath: string,
|
|
13
13
|
contextDirs: string[],
|
|
14
14
|
phase?: string,
|
|
@@ -51,14 +51,15 @@ export function createPlannerAgent(
|
|
|
51
51
|
"- ## Scope: 2-4 lines — what changes, what doesn't, critical constraints",
|
|
52
52
|
"- ## Checklist: each item is - [ ] <outcome> — Done when: <observable condition>",
|
|
53
53
|
" Each item = one independently verifiable outcome. No code snippets or file-by-file instructions.",
|
|
54
|
+
"- ## Pattern constraints: include whenever the task adds a type, function, parser, annotation, config key, enum, or user-facing value. For each, name the CLOSEST EXISTING analog (found by behavior, not filename) and the conventions the implementer MUST mirror: data shape (prefer one existing shape over parallel/duplicated state), spelling/casing of user-facing values (match existing — never invent a new casing), and parser/validation/error-handling shape. Acceptance criteria, not suggestions. Omit only if the task adds none of the above.",
|
|
54
55
|
"- ## Blockers: unresolved issues blocking implementation (omit if none)",
|
|
55
56
|
"- No other top-level sections allowed",
|
|
56
|
-
"- Describe outcomes, not code-level mechanics",
|
|
57
|
+
"- Describe outcomes, not code-level mechanics, EXCEPT in ## Pattern constraints where the concrete analog and conventions are required",
|
|
57
58
|
"",
|
|
58
|
-
"
|
|
59
|
-
'- Agent(subagent_type="
|
|
60
|
-
'- Agent(subagent_type="
|
|
61
|
-
"Spawn multiple
|
|
59
|
+
"You may spawn ONLY explore/librarian subagents (subagent_type is REQUIRED — calls without it are rejected):",
|
|
60
|
+
'- Agent(subagent_type="explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
61
|
+
'- Agent(subagent_type="librarian", ...) — external docs, library APIs, web research.',
|
|
62
|
+
"Spawn multiple explore agents in parallel for broad searches. Do NOT spawn task, advisor, deep-debugger, or reviewer.",
|
|
62
63
|
"</task>",
|
|
63
64
|
"",
|
|
64
65
|
// --- dynamic suffix ---
|
|
@@ -74,7 +75,7 @@ export function createPlannerAgent(
|
|
|
74
75
|
taskArtifacts.research,
|
|
75
76
|
...(repoContext ? [repoContext] : []),
|
|
76
77
|
"",
|
|
77
|
-
|
|
78
|
+
formatManifestBlock(taskArtifacts.manifest ?? []),
|
|
78
79
|
].join("\n"),
|
|
79
80
|
};
|
|
80
81
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { getDefaultConfig, resolvePreset } from "../config.js";
|
|
3
|
+
import { DELEGATION_BLOCK } from "./tool-routing.js";
|
|
4
|
+
import { createAdvisorAgent } from "./advisor.js";
|
|
5
|
+
import { createDeepDebuggerAgent } from "./deep-debugger.js";
|
|
6
|
+
import { createReviewerAgent } from "./reviewer.js";
|
|
7
|
+
import { createTaskAgent } from "./task.js";
|
|
8
|
+
import { createPlannerAgent } from "./planner.js";
|
|
9
|
+
import { createPlanReviewerAgent } from "./plan-reviewer.js";
|
|
10
|
+
import { createCodeReviewerAgent } from "./code-reviewer.js";
|
|
11
|
+
import { createBrainstormReviewerAgent } from "./brainstorm-reviewer.js";
|
|
12
|
+
|
|
13
|
+
const config = getDefaultConfig();
|
|
14
|
+
|
|
15
|
+
describe("DELEGATION_BLOCK", () => {
|
|
16
|
+
it("covers all six free-form agents with lowercase registry names", () => {
|
|
17
|
+
for (const name of ["explore", "librarian", "task", "advisor", "deep-debugger", "reviewer"]) {
|
|
18
|
+
expect(DELEGATION_BLOCK).toContain(name);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("states the reviewer and deep-debugger gating explicitly", () => {
|
|
23
|
+
expect(DELEGATION_BLOCK).toContain("ONLY when the user explicitly asks");
|
|
24
|
+
expect(DELEGATION_BLOCK).toMatch(/deep-debugger diagnoses/i);
|
|
25
|
+
expect(DELEGATION_BLOCK).toContain("must NOT write the actual fix");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("new free-form agent factories", () => {
|
|
30
|
+
it("advisor is read-only (no write/edit) and reasons in Diagnosis/Options/Recommendation", () => {
|
|
31
|
+
const a = createAdvisorAgent(config);
|
|
32
|
+
expect(a.frontmatter.tools).not.toContain("write");
|
|
33
|
+
expect(a.frontmatter.tools).not.toContain("edit");
|
|
34
|
+
expect(a.prompt).toContain("READ-ONLY");
|
|
35
|
+
expect(a.prompt).toContain("Diagnosis");
|
|
36
|
+
expect(a.prompt).toContain("Recommendation");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("deep-debugger has write/edit but restricts writes to diagnosis only", () => {
|
|
40
|
+
const d = createDeepDebuggerAgent(config);
|
|
41
|
+
expect(d.frontmatter.tools).toContain("write");
|
|
42
|
+
expect(d.frontmatter.tools).toContain("edit");
|
|
43
|
+
expect(d.prompt).toContain("DIAGNOSIS ONLY");
|
|
44
|
+
expect(d.prompt).toContain("MUST NOT write the actual fix");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("reviewer is read-only, retains bash for git diff, and is verdict-first", () => {
|
|
48
|
+
const r = createReviewerAgent(config);
|
|
49
|
+
expect(r.frontmatter.tools).toContain("bash");
|
|
50
|
+
expect(r.frontmatter.tools).not.toContain("write");
|
|
51
|
+
expect(r.frontmatter.tools).not.toContain("edit");
|
|
52
|
+
expect(r.prompt).toContain("git diff");
|
|
53
|
+
expect(r.prompt).toContain("VERY FIRST LINE");
|
|
54
|
+
expect(r.frontmatter.description).toContain("only when the user asks");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("task factory no longer bakes artifacts and stays explore/librarian-only", () => {
|
|
59
|
+
it("takes only config (no baked artifact arg) and does not inline USER REQUEST / SYNTHESIZED PLAN", () => {
|
|
60
|
+
expect(createTaskAgent.length).toBe(1);
|
|
61
|
+
const t = createTaskAgent(config);
|
|
62
|
+
expect(t.prompt).not.toContain("=== USER REQUEST ===");
|
|
63
|
+
expect(t.prompt).not.toContain("=== SYNTHESIZED PLAN ===");
|
|
64
|
+
expect(t.prompt).not.toContain("Do NOT re-read them from disk");
|
|
65
|
+
expect(t.prompt).toContain("ONLY explore/librarian");
|
|
66
|
+
expect(t.prompt).toContain("Do NOT spawn task, advisor, deep-debugger, or reviewer");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("phased factory prompts: manifest guidance replaces the do-not-re-read trailer", () => {
|
|
71
|
+
const planners = resolvePreset(config, "planners");
|
|
72
|
+
const planReviewers = resolvePreset(config, "planReviewers");
|
|
73
|
+
const codeReviewers = resolvePreset(config, "codeReviewers");
|
|
74
|
+
const brainstormReviewers = resolvePreset(config, "brainstormReviewers");
|
|
75
|
+
const manifest = [{ title: "Design Doc", path: "/t/artifacts/design.md" }];
|
|
76
|
+
|
|
77
|
+
it("planner lists manifest paths and restricts spawns to explore/librarian", () => {
|
|
78
|
+
const p = createPlannerAgent("opus", planners, { userRequest: "u", research: "r", manifest }, "/out.md", []);
|
|
79
|
+
expect(p.prompt).toContain("/t/artifacts/design.md");
|
|
80
|
+
expect(p.prompt).toContain("read them from disk with the read tool");
|
|
81
|
+
expect(p.prompt).toContain("Do NOT spawn task, advisor, deep-debugger, or reviewer");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("plan-reviewer lists manifest paths and restricts spawns", () => {
|
|
85
|
+
const p = createPlanReviewerAgent(
|
|
86
|
+
"opus",
|
|
87
|
+
planReviewers,
|
|
88
|
+
{ userRequest: "u", research: "r", synthesizedPlan: "p", manifest },
|
|
89
|
+
"/out.md",
|
|
90
|
+
[],
|
|
91
|
+
);
|
|
92
|
+
expect(p.prompt).toContain("/t/artifacts/design.md");
|
|
93
|
+
expect(p.prompt).toContain("Do NOT spawn task, advisor, deep-debugger, or reviewer");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("code-reviewer lists manifest paths and restricts spawns", () => {
|
|
97
|
+
const c = createCodeReviewerAgent(
|
|
98
|
+
"opus",
|
|
99
|
+
codeReviewers,
|
|
100
|
+
{ userRequest: "u", research: "r", synthesizedPlan: "p", manifest },
|
|
101
|
+
"/out.md",
|
|
102
|
+
[],
|
|
103
|
+
);
|
|
104
|
+
expect(c.prompt).toContain("/t/artifacts/design.md");
|
|
105
|
+
expect(c.prompt).toContain("Do NOT spawn task, advisor, deep-debugger, or reviewer");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("brainstorm-reviewer restricts spawns and lists manifest paths when provided", () => {
|
|
109
|
+
const b = createBrainstormReviewerAgent(
|
|
110
|
+
"opus",
|
|
111
|
+
brainstormReviewers,
|
|
112
|
+
{ userRequest: "u", research: "r", manifest },
|
|
113
|
+
"/out.md",
|
|
114
|
+
[],
|
|
115
|
+
);
|
|
116
|
+
expect(b.prompt).toContain("Do NOT spawn task, advisor, deep-debugger, or reviewer");
|
|
117
|
+
expect(b.prompt).toContain("/t/artifacts/design.md");
|
|
118
|
+
expect(b.prompt).toContain("read them from disk with the read tool");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { PiPiConfig } from "../config.js";
|
|
2
|
+
import { resolveModel } from "../model-registry.js";
|
|
3
|
+
import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK } from "./tool-routing.js";
|
|
4
|
+
|
|
5
|
+
export function createReviewerAgent(config: PiPiConfig) {
|
|
6
|
+
return {
|
|
7
|
+
frontmatter: {
|
|
8
|
+
description: "Code reviewer for changes/diffs with severity-rated findings — spawn only when the user asks for a review (pi-pi)",
|
|
9
|
+
tools: `read, bash, grep, find, ls, lsp, ast_search, ${ALL_CBM_TOOLS}, ${EXA_TOOLS}`,
|
|
10
|
+
model: resolveModel(config.agents.subagents.simple.reviewer.model),
|
|
11
|
+
thinking: config.agents.subagents.simple.reviewer.thinking,
|
|
12
|
+
max_turns: 120,
|
|
13
|
+
prompt_mode: "replace",
|
|
14
|
+
},
|
|
15
|
+
prompt: [
|
|
16
|
+
"<constraints>",
|
|
17
|
+
"You are a code REVIEWER. You review implementation changes for bugs, correctness, and quality.",
|
|
18
|
+
"You are READ-ONLY: you MUST NOT implement, fix, or modify any source code.",
|
|
19
|
+
"Begin your review with the verdict on the VERY FIRST LINE: `VERDICT: APPROVE` or `VERDICT: NEEDS_CHANGES`.",
|
|
20
|
+
"</constraints>",
|
|
21
|
+
"",
|
|
22
|
+
PRINCIPLES_BLOCK,
|
|
23
|
+
"",
|
|
24
|
+
TOOLS_BLOCK,
|
|
25
|
+
"",
|
|
26
|
+
"<task>",
|
|
27
|
+
"Steps:",
|
|
28
|
+
"1. Run `git diff` to see all changes (try HEAD~1, main, or the appropriate base).",
|
|
29
|
+
"2. Run cbm_changes for symbol-level impact and blast radius.",
|
|
30
|
+
"3. Read changed files for full context; run lsp diagnostics on them.",
|
|
31
|
+
"4. Use lsp findReferences to check callers of modified functions.",
|
|
32
|
+
"",
|
|
33
|
+
"Review criteria: logic errors, off-by-ones, null/edge handling, race conditions; correctness vs intent; error handling and type safety; missing or untested paths.",
|
|
34
|
+
"",
|
|
35
|
+
"Evidence: every CRITICAL or MAJOR finding MUST cite file:line or quoted code. Never assert a problem without reading the code. Low-confidence concerns go under OPEN QUESTIONS.",
|
|
36
|
+
"",
|
|
37
|
+
"Format — verdict on the FIRST LINE, then:",
|
|
38
|
+
"VERDICT: APPROVE | NEEDS_CHANGES",
|
|
39
|
+
"- CRITICAL: (must fix — file:line evidence)",
|
|
40
|
+
"- MAJOR: (should fix — evidence)",
|
|
41
|
+
"- MINOR: (nice to have)",
|
|
42
|
+
"- OPEN QUESTIONS: (low-confidence / speculative)",
|
|
43
|
+
"",
|
|
44
|
+
"Return the full review as your result.",
|
|
45
|
+
"</task>",
|
|
46
|
+
].join("\n"),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -2,11 +2,7 @@ import type { PiPiConfig } from "../config.js";
|
|
|
2
2
|
import { resolveModel } from "../model-registry.js";
|
|
3
3
|
import { TOOLS_BLOCK, ALL_CBM_TOOLS, EXA_TOOLS, PRINCIPLES_BLOCK, FAILURE_RECOVERY } from "./tool-routing.js";
|
|
4
4
|
|
|
5
|
-
export function createTaskAgent(
|
|
6
|
-
config: PiPiConfig,
|
|
7
|
-
subtaskDescription: string,
|
|
8
|
-
taskArtifacts: { userRequest: string; synthesizedPlan: string },
|
|
9
|
-
) {
|
|
5
|
+
export function createTaskAgent(config: PiPiConfig) {
|
|
10
6
|
return {
|
|
11
7
|
frontmatter: {
|
|
12
8
|
description: "Implementation subtask (pi-pi)",
|
|
@@ -32,24 +28,14 @@ export function createTaskAgent(
|
|
|
32
28
|
FAILURE_RECOVERY,
|
|
33
29
|
"",
|
|
34
30
|
"<task>",
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
"- You may spawn ONLY explore/librarian subagents (subagent_type is REQUIRED — calls without it are rejected):",
|
|
32
|
+
' Agent(subagent_type="explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
33
|
+
' Agent(subagent_type="librarian", ...) — external docs, library APIs, web research.',
|
|
34
|
+
" Do NOT spawn task, advisor, deep-debugger, or reviewer subagents.",
|
|
38
35
|
"- Before modifying a function, use lsp findReferences to understand all callers",
|
|
39
36
|
"- After editing files, run lsp diagnostics and fix errors before moving on",
|
|
37
|
+
"- Your subtask and task context (USER_REQUEST, RESEARCH, and a manifest of additional documents) are provided in the spawn message. Read the manifested files from disk if relevant.",
|
|
40
38
|
"</task>",
|
|
41
|
-
"",
|
|
42
|
-
// --- dynamic suffix ---
|
|
43
|
-
"=== YOUR SUBTASK ===",
|
|
44
|
-
subtaskDescription,
|
|
45
|
-
"",
|
|
46
|
-
"=== USER REQUEST (for context) ===",
|
|
47
|
-
taskArtifacts.userRequest,
|
|
48
|
-
"",
|
|
49
|
-
"=== SYNTHESIZED PLAN (for broader context) ===",
|
|
50
|
-
taskArtifacts.synthesizedPlan,
|
|
51
|
-
"",
|
|
52
|
-
"The artifacts above are already in your context. Do NOT re-read them from disk.",
|
|
53
39
|
].join("\n"),
|
|
54
40
|
};
|
|
55
41
|
}
|
|
@@ -7,8 +7,9 @@ export const PRINCIPLES_BLOCK = [
|
|
|
7
7
|
"- Understand before modifying. Read the code, trace callers, check types BEFORE editing. Compiling ≠ correct.",
|
|
8
8
|
"- Smallest viable change. Do what was asked, nothing more. Don't broaden scope, don't refactor adjacent code.",
|
|
9
9
|
"- No temporary artifacts. No console.log, TODO, HACK, debugger, or commented-out code left behind.",
|
|
10
|
+
"- DO NOT WRITE COMMENTS. This is a hard rule, not a preference. Almost every comment an LLM writes is noise: it restates the code, repeats the function/variable name, narrates the obvious, or labels sections. NEVER write any of those. The ONLY allowed comments are (1) a genuine WHY that the code cannot express — a non-obvious constraint, workaround, or gotcha a reader would otherwise get wrong, or (2) required public-API/doc-comment syntax. If a comment restates WHAT the code does, delete it. When unsure, do not comment. Match the existing comment density of the surrounding code — if neighbors have none, add none.",
|
|
10
11
|
"- Evidence over claims. 'It should work' is not proof. Show fresh tool output (lsp diagnostics, test results, build output).",
|
|
11
|
-
"- Match existing patterns.
|
|
12
|
+
"- Match existing patterns. Before adding a type, function, or user-facing value, find how the codebase already solves the most similar problem — search by behavior, not by filename — and mirror its shape, naming, error handling, and conventions. Reading one neighboring file is not enough.",
|
|
12
13
|
"- Be concise and dense: minimum words, no preamble/filler/restatement. Don't narrate what you're about to do or just did.",
|
|
13
14
|
"- Think critically. Push back when something seems wrong, and state concerns before implementing.",
|
|
14
15
|
"</principles>",
|
|
@@ -31,6 +32,12 @@ const TOOL_ROUTING_BODY = [
|
|
|
31
32
|
"Pass the base branch (the branch this work will be merged into). Must call at the start of each task " +
|
|
32
33
|
"before doing any work.",
|
|
33
34
|
"",
|
|
35
|
+
"**pp_checkout_pr_head** (review phase, PR-scoped only): after resolving a repo's PR (e.g. `gh pr view " +
|
|
36
|
+
"--json headRefName,headRefOid`), call this once per repo to land it on its PR head before reviewing. " +
|
|
37
|
+
"The extension fast-forwards a clean branch to the head; if the tree is dirty, on a different branch, or " +
|
|
38
|
+
"diverged it HALTS and returns a message to relay to the user. Do NOT call it for a " +
|
|
39
|
+
"branch/commit-range/uncommitted-changes review, and never run `git checkout` yourself.",
|
|
40
|
+
"",
|
|
34
41
|
"Find code by concept or behavior:",
|
|
35
42
|
"- Multi-repo: cbm_search, cbm_search_code, cbm_trace, cbm_changes accept optional project_path (absolute repo path). If omitted, they use the root project.",
|
|
36
43
|
"- cbm_search: natural-language search (query='deploy release chart')",
|
|
@@ -76,3 +83,34 @@ const TOOL_ROUTING_BODY = [
|
|
|
76
83
|
];
|
|
77
84
|
|
|
78
85
|
export const TOOLS_BLOCK = ["<tools>", ...TOOL_ROUTING_BODY, "</tools>"].join("\n");
|
|
86
|
+
|
|
87
|
+
// Single source of truth for main-agent delegation guidance. Injected into the MAIN
|
|
88
|
+
// agent prompt in every phase. Registry-consistent lowercase agent names.
|
|
89
|
+
export const DELEGATION_BLOCK = [
|
|
90
|
+
"<delegation>",
|
|
91
|
+
"Prefer delegating over doing wide or deep work yourself. Subagents run in parallel, dig",
|
|
92
|
+
"deeper, and keep YOUR context clean. subagent_type is REQUIRED (calls without it are rejected).",
|
|
93
|
+
"",
|
|
94
|
+
"USE a subagent when:",
|
|
95
|
+
'- Locating code / mapping a flow ("where is X", "how does Y connect") → explore',
|
|
96
|
+
"- External library / API / framework knowledge → librarian",
|
|
97
|
+
'- A judgment call (design tradeoff, "is this correct", "why is this broken") → advisor',
|
|
98
|
+
"- A test/build that keeps failing after one real attempt → deep-debugger",
|
|
99
|
+
"- A self-contained, parallelizable implementation slice → task",
|
|
100
|
+
"- A code review of your changes — ONLY when the user explicitly asks → reviewer",
|
|
101
|
+
"",
|
|
102
|
+
"If a task is broad or multi-part (investigate/analyze/refactor/audit/migrate a system,",
|
|
103
|
+
"subsystem, flow, or the codebase — anything not pinned to one known file/symbol), OPEN with",
|
|
104
|
+
"2–3 parallel `explore` subagents before reading code yourself. Use 4+ only for audits,",
|
|
105
|
+
"migrations, or multi-repo/system work. Give each explore an orthogonal prompt (no duplicate",
|
|
106
|
+
"mapping). explore FINDS; advisor JUDGES.",
|
|
107
|
+
"",
|
|
108
|
+
"Do NOT delegate when:",
|
|
109
|
+
"- You already know the exact file/symbol — just read/edit it directly.",
|
|
110
|
+
"- The task is a single narrow change or a trivial lookup (delegation overhead > work).",
|
|
111
|
+
"- You are mid-edit on a file you understand — keep going; don't spawn to \"map\" it.",
|
|
112
|
+
"- deep-debugger for a trivial/obvious error, or advisor for a plain lookup.",
|
|
113
|
+
"deep-debugger diagnoses only — it must NOT write the actual fix. Do NOT spawn reviewer unless",
|
|
114
|
+
"the user explicitly asks (the automatic review panel already covers implement/review phases).",
|
|
115
|
+
"</delegation>",
|
|
116
|
+
].join("\n");
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { stripAiCommentsFromContent, isAiCommentOnlyChange } from "./ai-comment-cleanup.js";
|
|
3
|
+
|
|
4
|
+
describe("stripAiCommentsFromContent", () => {
|
|
5
|
+
it("drops full-line AI_COMMENT markers in various comment syntaxes", () => {
|
|
6
|
+
const input = [
|
|
7
|
+
"const x = 1;",
|
|
8
|
+
"// AI_COMMENT: this is wrong",
|
|
9
|
+
" # AI_COMMENT: python style",
|
|
10
|
+
"<!-- AI_COMMENT: markdown -->",
|
|
11
|
+
"-- AI_COMMENT: sql style",
|
|
12
|
+
"const y = 2;",
|
|
13
|
+
].join("\n");
|
|
14
|
+
const { content, removed } = stripAiCommentsFromContent(input);
|
|
15
|
+
expect(removed).toBe(4);
|
|
16
|
+
expect(content).toBe(["const x = 1;", "const y = 2;"].join("\n"));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("strips a trailing AI_COMMENT marker but keeps the code", () => {
|
|
20
|
+
const input = "const z = 3; // AI_COMMENT: off-by-one?";
|
|
21
|
+
const { content, removed } = stripAiCommentsFromContent(input);
|
|
22
|
+
expect(removed).toBe(1);
|
|
23
|
+
expect(content).toBe("const z = 3;");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("leaves prose that merely mentions the token untouched", () => {
|
|
27
|
+
const input = "This paragraph explains what AI_COMMENT: means in docs.";
|
|
28
|
+
const { content, removed } = stripAiCommentsFromContent(input);
|
|
29
|
+
expect(removed).toBe(0);
|
|
30
|
+
expect(content).toBe(input);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("is a no-op when no token present", () => {
|
|
34
|
+
const input = "line one\nline two";
|
|
35
|
+
expect(stripAiCommentsFromContent(input)).toEqual({ content: input, removed: 0 });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("does NOT corrupt a marker that lives inside a string literal", () => {
|
|
39
|
+
const cases = [
|
|
40
|
+
`const s = "// AI_COMMENT: example";`,
|
|
41
|
+
`const AI_COMMENT_MARKER_SYNTAX = "// AI_COMMENT: ...";`,
|
|
42
|
+
`log('# AI_COMMENT: note');`,
|
|
43
|
+
"const t = `<!-- AI_COMMENT: x -->`;",
|
|
44
|
+
];
|
|
45
|
+
for (const input of cases) {
|
|
46
|
+
expect(stripAiCommentsFromContent(input)).toEqual({ content: input, removed: 0 });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("strips a real trailing marker even when the code has an earlier closed string", () => {
|
|
51
|
+
const input = `url = "http://x"; // AI_COMMENT: note`;
|
|
52
|
+
const { content, removed } = stripAiCommentsFromContent(input);
|
|
53
|
+
expect(removed).toBe(1);
|
|
54
|
+
expect(content).toBe(`url = "http://x";`);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("isAiCommentOnlyChange", () => {
|
|
59
|
+
it("allows inserting an AI_COMMENT marker line", () => {
|
|
60
|
+
const before = "const x = 1;\nconst y = 2;";
|
|
61
|
+
const after = "const x = 1;\n// AI_COMMENT: off-by-one?\nconst y = 2;";
|
|
62
|
+
expect(isAiCommentOnlyChange(before, after)).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("allows removing an AI_COMMENT marker line", () => {
|
|
66
|
+
const before = "const x = 1;\n// AI_COMMENT: note\nconst y = 2;";
|
|
67
|
+
const after = "const x = 1;\nconst y = 2;";
|
|
68
|
+
expect(isAiCommentOnlyChange(before, after)).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("allows a trailing AI_COMMENT marker", () => {
|
|
72
|
+
expect(isAiCommentOnlyChange("const x = 1;", "const x = 1; // AI_COMMENT: hm")).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("rejects an actual code change", () => {
|
|
76
|
+
expect(isAiCommentOnlyChange("const x = 1;", "const x = 2;")).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("rejects a fix disguised alongside a marker", () => {
|
|
80
|
+
const before = "const x = 1;";
|
|
81
|
+
const after = "const x = 2; // AI_COMMENT: fixed";
|
|
82
|
+
expect(isAiCommentOnlyChange(before, after)).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("treats an identical write as allowed", () => {
|
|
86
|
+
expect(isAiCommentOnlyChange("same", "same")).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export const AI_COMMENT_TOKEN = "AI_COMMENT:";
|
|
2
|
+
|
|
3
|
+
// Remove reviewer→user `AI_COMMENT:` markers from a single file's content.
|
|
4
|
+
// - A line whose comment content is ONLY an AI_COMMENT marker is dropped entirely
|
|
5
|
+
// (covers `// AI_COMMENT: ...`, `#`, `--`, `;`, `<!-- ... -->`, `/* ... */`).
|
|
6
|
+
// - A code line with a trailing `<comment-open> AI_COMMENT: ...` is stripped back
|
|
7
|
+
// to the code, dropping the trailing marker only.
|
|
8
|
+
// Anything else containing the token is left untouched (do not corrupt prose).
|
|
9
|
+
// True when `prefix` ends inside an unclosed string literal (single, double, or
|
|
10
|
+
// backtick quote). Used to avoid stripping a `// AI_COMMENT:` that lives inside a
|
|
11
|
+
// string rather than being a real trailing comment. Escaped quotes are honored.
|
|
12
|
+
function insideStringLiteral(prefix: string): boolean {
|
|
13
|
+
let quote: string | null = null;
|
|
14
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
15
|
+
const ch = prefix[i];
|
|
16
|
+
if (ch === "\\") {
|
|
17
|
+
i++;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (quote) {
|
|
21
|
+
if (ch === quote) quote = null;
|
|
22
|
+
} else if (ch === '"' || ch === "'" || ch === "`") {
|
|
23
|
+
quote = ch;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return quote !== null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function stripAiCommentsFromContent(content: string): { content: string; removed: number } {
|
|
30
|
+
if (!content.includes(AI_COMMENT_TOKEN)) return { content, removed: 0 };
|
|
31
|
+
const lines = content.split("\n");
|
|
32
|
+
const out: string[] = [];
|
|
33
|
+
let removed = 0;
|
|
34
|
+
|
|
35
|
+
const fullLineComment = /^\s*(?:\/\/+|#+|--|;+|\/\*|<!--)\s*AI_COMMENT:.*?(?:\*\/|-->)?\s*$/;
|
|
36
|
+
const trailingComment = /(\s*(?:\/\/+|#+|--|;+|\/\*|<!--)\s*AI_COMMENT:.*?(?:\*\/|-->)?)\s*$/;
|
|
37
|
+
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
if (!line.includes(AI_COMMENT_TOKEN)) {
|
|
40
|
+
out.push(line);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (fullLineComment.test(line)) {
|
|
44
|
+
removed += 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const match = trailingComment.exec(line);
|
|
48
|
+
// Only strip a trailing marker when the code BEFORE the comment opener has
|
|
49
|
+
// balanced quotes — otherwise the "// AI_COMMENT:" is inside a string literal
|
|
50
|
+
// (e.g. `const s = "// AI_COMMENT: x"`) and stripping it would corrupt source.
|
|
51
|
+
if (match && !insideStringLiteral(line.slice(0, match.index))) {
|
|
52
|
+
const stripped = line.slice(0, match.index);
|
|
53
|
+
if (stripped.trim().length > 0) {
|
|
54
|
+
out.push(stripped);
|
|
55
|
+
removed += 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Token present but not in a strippable trailing-comment position — leave as-is.
|
|
60
|
+
out.push(line);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { content: out.join("\n"), removed };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// True when the change from `before` to `after` only inserts and/or removes
|
|
67
|
+
// `AI_COMMENT:` markers — i.e. stripping all such markers from both sides yields
|
|
68
|
+
// identical text. Used to enforce the review-phase read-only exception at the
|
|
69
|
+
// tool-call gate (the agent may touch AI_COMMENT markers and nothing else).
|
|
70
|
+
export function isAiCommentOnlyChange(before: string, after: string): boolean {
|
|
71
|
+
if (before === after) return true;
|
|
72
|
+
return stripAiCommentsFromContent(before).content === stripAiCommentsFromContent(after).content;
|
|
73
|
+
}
|
|
@@ -309,6 +309,29 @@ describe("transitionToNextPhase", () => {
|
|
|
309
309
|
expect(cleanupSpy).toHaveBeenCalledTimes(1);
|
|
310
310
|
});
|
|
311
311
|
|
|
312
|
+
it("does not re-run afterImplement when it already ran for this implement phase (#1)", async () => {
|
|
313
|
+
const pi = makePi();
|
|
314
|
+
const orchestrator = new Orchestrator(pi as any);
|
|
315
|
+
const taskDir = makeTempDir();
|
|
316
|
+
orchestrator.cwd = taskDir;
|
|
317
|
+
orchestrator.config = makeConfig() as any;
|
|
318
|
+
orchestrator.active = makeTransitionTask(taskDir, "implement");
|
|
319
|
+
orchestrator.active.state.repos = [{ path: taskDir, isRoot: true }];
|
|
320
|
+
orchestrator.active.state.afterImplementRan = true;
|
|
321
|
+
orchestrator.active.modifiedFiles = new Set([join(taskDir, "src", "root.ts")]);
|
|
322
|
+
const ctx = makeTransitionCtx();
|
|
323
|
+
vi.spyOn(machineModule, "validateExitCriteria").mockReturnValue({ ok: true });
|
|
324
|
+
const runSpy = vi.spyOn(commandsModule, "runAfterImplement").mockReturnValue([{ ok: true, command: "npm test", output: "ok" }]);
|
|
325
|
+
vi.spyOn(orchestrator, "cleanupActive").mockImplementation(async () => {
|
|
326
|
+
orchestrator.active = null;
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const result = await transitionToNextPhase(orchestrator, ctx);
|
|
330
|
+
|
|
331
|
+
expect(result.ok).toBe(true);
|
|
332
|
+
expect(runSpy).not.toHaveBeenCalled();
|
|
333
|
+
});
|
|
334
|
+
|
|
312
335
|
it("clears reviewCycle and review bookkeeping on the done transition (Issue 4)", async () => {
|
|
313
336
|
const pi = makePi();
|
|
314
337
|
const orchestrator = new Orchestrator(pi as any);
|
|
@@ -509,6 +532,30 @@ describe("transitionToNextPhase", () => {
|
|
|
509
532
|
expect(cleanupSpy).toHaveBeenCalledTimes(1);
|
|
510
533
|
});
|
|
511
534
|
|
|
535
|
+
it("completing a task (next === done) requests a discard transition", async () => {
|
|
536
|
+
const pi = makePi();
|
|
537
|
+
const orchestrator = new Orchestrator(pi as any);
|
|
538
|
+
const taskDir = makeTempDir();
|
|
539
|
+
orchestrator.cwd = taskDir;
|
|
540
|
+
orchestrator.config = makeConfig() as any;
|
|
541
|
+
orchestrator.active = makeTransitionTask(taskDir, "quick");
|
|
542
|
+
const ctx = makeTransitionCtx();
|
|
543
|
+
vi.spyOn(machineModule, "validateExitCriteria").mockReturnValue({ ok: true });
|
|
544
|
+
vi.spyOn(orchestrator, "cleanupActive").mockImplementation(async () => {
|
|
545
|
+
orchestrator.active = null;
|
|
546
|
+
});
|
|
547
|
+
vi.spyOn(orchestrator, "abortAllSubagents").mockImplementation(() => undefined);
|
|
548
|
+
const reqSpy = vi.spyOn(orchestrator.transitionController, "requestTransition").mockReturnValue(Promise.resolve());
|
|
549
|
+
|
|
550
|
+
const result = await transitionToNextPhase(orchestrator, ctx);
|
|
551
|
+
|
|
552
|
+
expect(result).toEqual({ ok: true });
|
|
553
|
+
const doneCall = reqSpy.mock.calls.find(([req]: any[]) => req.kind === "done");
|
|
554
|
+
expect(doneCall).toBeDefined();
|
|
555
|
+
expect((doneCall![0] as any).discard).toBe(true);
|
|
556
|
+
expect((doneCall![0] as any).summary).toContain("DISCARD");
|
|
557
|
+
});
|
|
558
|
+
|
|
512
559
|
it("skips afterImplement when phase is not implement", async () => {
|
|
513
560
|
const pi = makePi();
|
|
514
561
|
const orchestrator = new Orchestrator(pi as any);
|
|
@@ -13,6 +13,41 @@ function isEnabled(value: { enabled?: boolean }): boolean {
|
|
|
13
13
|
return value.enabled !== false;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// Runs the configured afterImplement commands per repo (root + extra repos when
|
|
17
|
+
// enabled), grouping by the repo each modified file belongs to. Returns an error
|
|
18
|
+
// string when any command fails (same wording the transition surfaces), or null
|
|
19
|
+
// on success. Shared by the guided/autonomous implement→done transition AND the
|
|
20
|
+
// autonomous terminal handoff, which opens the guided menu instead of advancing.
|
|
21
|
+
export function runAfterImplementForActive(orchestrator: Orchestrator): string | null {
|
|
22
|
+
if (!orchestrator.active) return null;
|
|
23
|
+
const repos = orchestrator.active.state.repos ?? [];
|
|
24
|
+
const grouped = groupFilesByRepo(repos, [...orchestrator.active.modifiedFiles]);
|
|
25
|
+
const afterResults: Array<{ ok: boolean; command: string; output: string }> = [];
|
|
26
|
+
for (const [repoPath] of grouped) {
|
|
27
|
+
if (!repoPath) continue;
|
|
28
|
+
const repo = repos.find((r) => r.path === repoPath);
|
|
29
|
+
if (!repo) continue;
|
|
30
|
+
if (repo.isRoot) {
|
|
31
|
+
afterResults.push(...runAfterImplement(
|
|
32
|
+
orchestrator.config.commands.afterImplement,
|
|
33
|
+
orchestrator.config.performance.commands.afterImplement,
|
|
34
|
+
orchestrator.cwd,
|
|
35
|
+
));
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (!orchestrator.config.general.loadExtraRepoConfigs) continue;
|
|
39
|
+
const extraCommands = loadRepoAfterImplementCommands(repoPath);
|
|
40
|
+
if (!extraCommands || Object.keys(extraCommands).length === 0) continue;
|
|
41
|
+
afterResults.push(...runAfterImplement(extraCommands, orchestrator.config.performance.commands.afterImplement, repoPath));
|
|
42
|
+
}
|
|
43
|
+
const failures = afterResults.filter((r) => !r.ok);
|
|
44
|
+
if (failures.length > 0) {
|
|
45
|
+
const failureText = failures.map((f) => `${f.command}: ${f.output}`).join("\n");
|
|
46
|
+
return `afterImplement commands failed:\n${failureText}\n\nFix these issues before advancing.`;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
16
51
|
export async function transitionToNextPhase(
|
|
17
52
|
orchestrator: Orchestrator,
|
|
18
53
|
ctx: any,
|
|
@@ -39,36 +74,16 @@ export async function transitionToNextPhase(
|
|
|
39
74
|
const next = nextPhase(orchestrator.active.type, currentPhase);
|
|
40
75
|
if (!next) return { ok: false, error: "No next phase available." };
|
|
41
76
|
|
|
42
|
-
if (currentPhase === "implement") {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
for (const [repoPath] of grouped) {
|
|
47
|
-
if (!repoPath) continue;
|
|
48
|
-
const repo = repos.find((r) => r.path === repoPath);
|
|
49
|
-
if (!repo) continue;
|
|
50
|
-
if (repo.isRoot) {
|
|
51
|
-
afterResults.push(...runAfterImplement(
|
|
52
|
-
orchestrator.config.commands.afterImplement,
|
|
53
|
-
orchestrator.config.performance.commands.afterImplement,
|
|
54
|
-
orchestrator.cwd,
|
|
55
|
-
));
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (!orchestrator.config.general.loadExtraRepoConfigs) continue;
|
|
59
|
-
const extraCommands = loadRepoAfterImplementCommands(repoPath);
|
|
60
|
-
if (!extraCommands || Object.keys(extraCommands).length === 0) continue;
|
|
61
|
-
afterResults.push(...runAfterImplement(extraCommands, orchestrator.config.performance.commands.afterImplement, repoPath));
|
|
62
|
-
}
|
|
63
|
-
const failures = afterResults.filter((r) => !r.ok);
|
|
64
|
-
if (failures.length > 0) {
|
|
65
|
-
const failureText = failures.map((f) => `${f.command}: ${f.output}`).join("\n");
|
|
66
|
-
return { ok: false, error: `afterImplement commands failed:\n${failureText}\n\nFix these issues before advancing.` };
|
|
67
|
-
}
|
|
77
|
+
if (currentPhase === "implement" && !orchestrator.active.state.afterImplementRan) {
|
|
78
|
+
const afterError = runAfterImplementForActive(orchestrator);
|
|
79
|
+
if (afterError) return { ok: false, error: afterError };
|
|
80
|
+
orchestrator.active.state.afterImplementRan = true;
|
|
68
81
|
}
|
|
69
82
|
|
|
70
83
|
orchestrator.active.state.phase = next;
|
|
71
84
|
orchestrator.active.state.reviewCycle = null;
|
|
85
|
+
orchestrator.active.state.plannotatorCursor = undefined;
|
|
86
|
+
orchestrator.active.state.afterImplementRan = false;
|
|
72
87
|
orchestrator.active.state.reviewPass = 0;
|
|
73
88
|
orchestrator.active.state.reviewApprovedClean = false;
|
|
74
89
|
orchestrator.active.reviewPass = 0;
|
|
@@ -101,7 +116,8 @@ export async function transitionToNextPhase(
|
|
|
101
116
|
// Route the task-done compaction through the controller as a "done" target.
|
|
102
117
|
void orchestrator.transitionController.requestTransition({
|
|
103
118
|
kind: "done",
|
|
104
|
-
|
|
119
|
+
discard: true,
|
|
120
|
+
summary: `Task "${name}" (${type}) is finished — DISCARD its entire conversation. Do NOT carry forward, reference, or act on any of this task's messages, phase, plan, or aborted turns; the next task starts from a clean slate.`,
|
|
105
121
|
});
|
|
106
122
|
ctx.ui.notify("Task completed!", "info");
|
|
107
123
|
return { ok: true };
|
|
@@ -151,7 +167,7 @@ export function registerCommandHandlers(orchestrator: Orchestrator): void {
|
|
|
151
167
|
const { showPpMenu } = await import("./pp-menu.js");
|
|
152
168
|
const text = await showPpMenu(orchestrator, ctx, "command");
|
|
153
169
|
if (text) {
|
|
154
|
-
orchestrator.safeSendUserMessage(`[PI-PI] ${text}`);
|
|
170
|
+
orchestrator.safeSendUserMessage(text.startsWith("[PI-PI]") ? text : `[PI-PI] ${text}`);
|
|
155
171
|
}
|
|
156
172
|
},
|
|
157
173
|
});
|