planpong 0.1.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 (83) hide show
  1. package/README.md +110 -0
  2. package/dist/bin/planpong-mcp.d.ts +2 -0
  3. package/dist/bin/planpong-mcp.js +7 -0
  4. package/dist/bin/planpong-mcp.js.map +1 -0
  5. package/dist/bin/planpong.d.ts +2 -0
  6. package/dist/bin/planpong.js +13 -0
  7. package/dist/bin/planpong.js.map +1 -0
  8. package/dist/src/cli/commands/plan.d.ts +2 -0
  9. package/dist/src/cli/commands/plan.js +128 -0
  10. package/dist/src/cli/commands/plan.js.map +1 -0
  11. package/dist/src/cli/commands/review.d.ts +2 -0
  12. package/dist/src/cli/commands/review.js +156 -0
  13. package/dist/src/cli/commands/review.js.map +1 -0
  14. package/dist/src/cli/ui.d.ts +11 -0
  15. package/dist/src/cli/ui.js +65 -0
  16. package/dist/src/cli/ui.js.map +1 -0
  17. package/dist/src/config/defaults.d.ts +2 -0
  18. package/dist/src/config/defaults.js +12 -0
  19. package/dist/src/config/defaults.js.map +1 -0
  20. package/dist/src/config/loader.d.ts +17 -0
  21. package/dist/src/config/loader.js +74 -0
  22. package/dist/src/config/loader.js.map +1 -0
  23. package/dist/src/core/convergence.d.ts +10 -0
  24. package/dist/src/core/convergence.js +56 -0
  25. package/dist/src/core/convergence.js.map +1 -0
  26. package/dist/src/core/loop.d.ts +53 -0
  27. package/dist/src/core/loop.js +256 -0
  28. package/dist/src/core/loop.js.map +1 -0
  29. package/dist/src/core/operations.d.ts +68 -0
  30. package/dist/src/core/operations.js +323 -0
  31. package/dist/src/core/operations.js.map +1 -0
  32. package/dist/src/core/session.d.ts +14 -0
  33. package/dist/src/core/session.js +77 -0
  34. package/dist/src/core/session.js.map +1 -0
  35. package/dist/src/mcp/server.d.ts +2 -0
  36. package/dist/src/mcp/server.js +109 -0
  37. package/dist/src/mcp/server.js.map +1 -0
  38. package/dist/src/mcp/tools/get-feedback.d.ts +2 -0
  39. package/dist/src/mcp/tools/get-feedback.js +109 -0
  40. package/dist/src/mcp/tools/get-feedback.js.map +1 -0
  41. package/dist/src/mcp/tools/list-sessions.d.ts +2 -0
  42. package/dist/src/mcp/tools/list-sessions.js +61 -0
  43. package/dist/src/mcp/tools/list-sessions.js.map +1 -0
  44. package/dist/src/mcp/tools/revise.d.ts +2 -0
  45. package/dist/src/mcp/tools/revise.js +84 -0
  46. package/dist/src/mcp/tools/revise.js.map +1 -0
  47. package/dist/src/mcp/tools/start-review.d.ts +2 -0
  48. package/dist/src/mcp/tools/start-review.js +105 -0
  49. package/dist/src/mcp/tools/start-review.js.map +1 -0
  50. package/dist/src/mcp/tools/status.d.ts +2 -0
  51. package/dist/src/mcp/tools/status.js +83 -0
  52. package/dist/src/mcp/tools/status.js.map +1 -0
  53. package/dist/src/prompts/planner.d.ts +3 -0
  54. package/dist/src/prompts/planner.js +96 -0
  55. package/dist/src/prompts/planner.js.map +1 -0
  56. package/dist/src/prompts/reviewer.d.ts +11 -0
  57. package/dist/src/prompts/reviewer.js +70 -0
  58. package/dist/src/prompts/reviewer.js.map +1 -0
  59. package/dist/src/providers/claude.d.ts +8 -0
  60. package/dist/src/providers/claude.js +77 -0
  61. package/dist/src/providers/claude.js.map +1 -0
  62. package/dist/src/providers/codex.d.ts +8 -0
  63. package/dist/src/providers/codex.js +83 -0
  64. package/dist/src/providers/codex.js.map +1 -0
  65. package/dist/src/providers/registry.d.ts +4 -0
  66. package/dist/src/providers/registry.js +17 -0
  67. package/dist/src/providers/registry.js.map +1 -0
  68. package/dist/src/providers/types.d.ts +18 -0
  69. package/dist/src/providers/types.js +2 -0
  70. package/dist/src/providers/types.js.map +1 -0
  71. package/dist/src/schemas/config.d.ts +75 -0
  72. package/dist/src/schemas/config.js +14 -0
  73. package/dist/src/schemas/config.js.map +1 -0
  74. package/dist/src/schemas/feedback.d.ts +95 -0
  75. package/dist/src/schemas/feedback.js +24 -0
  76. package/dist/src/schemas/feedback.js.map +1 -0
  77. package/dist/src/schemas/revision.d.ts +116 -0
  78. package/dist/src/schemas/revision.js +17 -0
  79. package/dist/src/schemas/revision.js.map +1 -0
  80. package/dist/src/schemas/session.d.ts +79 -0
  81. package/dist/src/schemas/session.js +16 -0
  82. package/dist/src/schemas/session.js.map +1 -0
  83. package/package.json +52 -0
@@ -0,0 +1,74 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { PlanpongConfigSchema, } from "../schemas/config.js";
5
+ import { DEFAULT_CONFIG } from "./defaults.js";
6
+ const CONFIG_FILENAMES = [
7
+ "planpong.yaml",
8
+ "planpong.yml",
9
+ ".planpong.yaml",
10
+ ".planpong.yml",
11
+ ];
12
+ /**
13
+ * Search upward from `cwd` for a config file. Returns the parsed
14
+ * contents or null if no file is found.
15
+ */
16
+ function findConfigFile(cwd) {
17
+ let dir = cwd;
18
+ const root = "/";
19
+ while (true) {
20
+ for (const filename of CONFIG_FILENAMES) {
21
+ const candidate = join(dir, filename);
22
+ if (existsSync(candidate)) {
23
+ const raw = readFileSync(candidate, "utf-8");
24
+ return parseYaml(raw);
25
+ }
26
+ }
27
+ const parent = join(dir, "..");
28
+ if (parent === dir || dir === root)
29
+ break;
30
+ dir = parent;
31
+ }
32
+ return null;
33
+ }
34
+ export function loadConfig(options) {
35
+ const fileConfig = findConfigFile(options.cwd) ?? {};
36
+ const overrides = options.overrides ?? {};
37
+ // Merge: defaults < file < CLI overrides
38
+ const merged = {
39
+ planner: {
40
+ provider: overrides.plannerProvider ??
41
+ fileConfig.planner?.provider ??
42
+ DEFAULT_CONFIG.planner.provider,
43
+ model: overrides.plannerModel ??
44
+ fileConfig.planner?.model ??
45
+ DEFAULT_CONFIG.planner.model,
46
+ effort: overrides.plannerEffort ??
47
+ fileConfig.planner?.effort ??
48
+ DEFAULT_CONFIG.planner.effort,
49
+ },
50
+ reviewer: {
51
+ provider: overrides.reviewerProvider ??
52
+ fileConfig.reviewer?.provider ??
53
+ DEFAULT_CONFIG.reviewer.provider,
54
+ model: overrides.reviewerModel ??
55
+ fileConfig.reviewer?.model ??
56
+ DEFAULT_CONFIG.reviewer.model,
57
+ effort: overrides.reviewerEffort ??
58
+ fileConfig.reviewer?.effort ??
59
+ DEFAULT_CONFIG.reviewer.effort,
60
+ },
61
+ plans_dir: overrides.plansDir ??
62
+ fileConfig.plans_dir ??
63
+ DEFAULT_CONFIG.plans_dir,
64
+ max_rounds: overrides.maxRounds ??
65
+ fileConfig.max_rounds ??
66
+ DEFAULT_CONFIG.max_rounds,
67
+ human_in_loop: overrides.autonomous !== undefined
68
+ ? !overrides.autonomous
69
+ : (fileConfig.human_in_loop ??
70
+ DEFAULT_CONFIG.human_in_loop),
71
+ };
72
+ return PlanpongConfigSchema.parse(merged);
73
+ }
74
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src/config/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type ReviewFeedback } from "../schemas/feedback.js";
2
+ import { type PlannerRevision } from "../schemas/revision.js";
3
+ /**
4
+ * Extract JSON from between sentinel tags. Falls back to finding JSON in
5
+ * code fences, then tries parsing the entire content as JSON.
6
+ */
7
+ export declare function extractJSON(content: string, tag: string): string | null;
8
+ export declare function parseFeedback(content: string): ReviewFeedback;
9
+ export declare function parseRevision(content: string): PlannerRevision;
10
+ export declare function isConverged(feedback: ReviewFeedback): boolean;
@@ -0,0 +1,56 @@
1
+ import { ReviewFeedbackSchema, } from "../schemas/feedback.js";
2
+ import { PlannerRevisionSchema, } from "../schemas/revision.js";
3
+ /**
4
+ * Extract JSON from between sentinel tags. Falls back to finding JSON in
5
+ * code fences, then tries parsing the entire content as JSON.
6
+ */
7
+ export function extractJSON(content, tag) {
8
+ // Try sentinel tags first: <planpong-feedback>...</planpong-feedback>
9
+ const tagPattern = new RegExp(`<${tag}>\\s*([\\s\\S]*?)\\s*</${tag}>`, "i");
10
+ const tagMatch = content.match(tagPattern);
11
+ if (tagMatch?.[1])
12
+ return tagMatch[1].trim();
13
+ // Try JSON code fence: ```json ... ```
14
+ const fencePattern = /```(?:json)?\s*([\s\S]*?)```/;
15
+ const fenceMatch = content.match(fencePattern);
16
+ if (fenceMatch?.[1])
17
+ return fenceMatch[1].trim();
18
+ // Try to find a JSON object in the content
19
+ const jsonPattern = /\{[\s\S]*\}/;
20
+ const jsonMatch = content.match(jsonPattern);
21
+ if (jsonMatch?.[0])
22
+ return jsonMatch[0].trim();
23
+ return null;
24
+ }
25
+ export function parseFeedback(content) {
26
+ const json = extractJSON(content, "planpong-feedback");
27
+ if (!json) {
28
+ throw new Error("Could not extract feedback JSON from reviewer output. Expected <planpong-feedback> tags, JSON code fence, or raw JSON object.");
29
+ }
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(json);
33
+ }
34
+ catch {
35
+ throw new Error(`Invalid JSON in reviewer output:\n${json.slice(0, 200)}`);
36
+ }
37
+ return ReviewFeedbackSchema.parse(parsed);
38
+ }
39
+ export function parseRevision(content) {
40
+ const json = extractJSON(content, "planpong-revision");
41
+ if (!json) {
42
+ throw new Error("Could not extract revision JSON from planner output. Expected <planpong-revision> tags, JSON code fence, or raw JSON object.");
43
+ }
44
+ let parsed;
45
+ try {
46
+ parsed = JSON.parse(json);
47
+ }
48
+ catch {
49
+ throw new Error(`Invalid JSON in planner output:\n${json.slice(0, 200)}`);
50
+ }
51
+ return PlannerRevisionSchema.parse(parsed);
52
+ }
53
+ export function isConverged(feedback) {
54
+ return feedback.verdict !== "needs_revision";
55
+ }
56
+ //# sourceMappingURL=convergence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convergence.js","sourceRoot":"","sources":["../../../src/core/convergence.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,GAErB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,qBAAqB,GAEtB,MAAM,wBAAwB,CAAC;AAEhC;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,GAAW;IACtD,sEAAsE;IACtE,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,0BAA0B,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC;IAC5E,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAE7C,uCAAuC;IACvC,MAAM,YAAY,GAAG,8BAA8B,CAAC;IACpD,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC/C,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC;QAAE,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAEjD,2CAA2C;IAC3C,MAAM,WAAW,GAAG,aAAa,CAAC;IAClC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC7C,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAE/C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IACvD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,+HAA+H,CAChI,CAAC;IACJ,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,qCAAqC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,oBAAoB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IACvD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,8HAA8H,CAC/H,CAAC;IACJ,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,qBAAqB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAwB;IAClD,OAAO,QAAQ,CAAC,OAAO,KAAK,gBAAgB,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,53 @@
1
+ import type { Provider } from "../providers/types.js";
2
+ import type { PlanpongConfig } from "../schemas/config.js";
3
+ import type { ReviewFeedback } from "../schemas/feedback.js";
4
+ import type { PlannerRevision } from "../schemas/revision.js";
5
+ import { type RoundSeverity } from "./operations.js";
6
+ export type { RoundSeverity } from "./operations.js";
7
+ export interface LoopCallbacks {
8
+ onPlanGenerated(planPath: string, content: string): Promise<void>;
9
+ onReviewStarting(round: number): void;
10
+ onReviewComplete(round: number, feedback: ReviewFeedback): Promise<void>;
11
+ onRevisionStarting(round: number): void;
12
+ onRevisionComplete(round: number, revision: PlannerRevision): Promise<void>;
13
+ onConverged(round: number, feedback: ReviewFeedback): void;
14
+ onMaxRoundsReached(round: number): void;
15
+ onHashMismatch(planPath: string, autonomous: boolean): Promise<"overwrite" | "abort">;
16
+ /** Return true to continue, false to abort */
17
+ confirmContinue(message: string): Promise<boolean>;
18
+ }
19
+ export interface LoopOptions {
20
+ requirements: string;
21
+ cwd: string;
22
+ config: PlanpongConfig;
23
+ plannerProvider: Provider;
24
+ reviewerProvider: Provider;
25
+ planName?: string;
26
+ callbacks: LoopCallbacks;
27
+ }
28
+ export interface ReviewOptions {
29
+ planPath: string;
30
+ cwd: string;
31
+ config: PlanpongConfig;
32
+ plannerProvider: Provider;
33
+ reviewerProvider: Provider;
34
+ callbacks: LoopCallbacks;
35
+ }
36
+ export interface ReviewResult {
37
+ status: "approved" | "max_rounds" | "aborted";
38
+ rounds: number;
39
+ issueTrajectory: RoundSeverity[];
40
+ accepted: number;
41
+ rejected: number;
42
+ deferred: number;
43
+ planPath: string;
44
+ sessionId: string;
45
+ elapsed: number;
46
+ }
47
+ export declare function runLoop(options: LoopOptions): Promise<void>;
48
+ /**
49
+ * Review an existing plan file through adversarial refinement.
50
+ * Skips plan generation — starts directly at the review cycle.
51
+ * Returns structured result for programmatic consumption.
52
+ */
53
+ export declare function runReviewLoop(options: ReviewOptions): Promise<ReviewResult>;
@@ -0,0 +1,256 @@
1
+ import { readFileSync, writeFileSync, existsSync, copyFileSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ import { buildInitialPlanPrompt } from "../prompts/planner.js";
4
+ import { createSession, writeSessionState } from "./session.js";
5
+ import { hashFile, buildStatusLine, updatePlanStatusLine, formatProviderLabel, initReviewSession, runReviewRound, runRevisionRound, finalizeApproved, } from "./operations.js";
6
+ function resolvePlanSlug(plansDir, name) {
7
+ const slug = name ??
8
+ `plan-${new Date().toISOString().slice(0, 10)}-${Date.now().toString(36)}`;
9
+ let filename = `${slug}.md`;
10
+ let fullPath = join(plansDir, filename);
11
+ let counter = 2;
12
+ while (existsSync(fullPath)) {
13
+ filename = `${slug}-${counter}.md`;
14
+ fullPath = join(plansDir, filename);
15
+ counter++;
16
+ }
17
+ return filename;
18
+ }
19
+ function countLines(text) {
20
+ return text.split("\n").length;
21
+ }
22
+ export async function runLoop(options) {
23
+ const { requirements, cwd, config, plannerProvider, reviewerProvider, planName, callbacks, } = options;
24
+ const plansDir = join(cwd, config.plans_dir);
25
+ if (!existsSync(plansDir)) {
26
+ const { mkdirSync } = await import("node:fs");
27
+ mkdirSync(plansDir, { recursive: true });
28
+ }
29
+ const startTime = Date.now();
30
+ // Step 1-2: Generate initial plan
31
+ const planPrompt = buildInitialPlanPrompt(requirements, config.plans_dir);
32
+ const planResponse = await plannerProvider.invoke(planPrompt, {
33
+ cwd,
34
+ model: config.planner.model,
35
+ effort: config.planner.effort,
36
+ });
37
+ if (planResponse.exitCode !== 0) {
38
+ throw new Error(`Planner failed (exit ${planResponse.exitCode}):\n${planResponse.content.slice(0, 500)}`);
39
+ }
40
+ // Step 3: Write plan to disk
41
+ const filename = resolvePlanSlug(plansDir, planName);
42
+ const planPath = join(plansDir, filename);
43
+ const relativePlanPath = relative(cwd, planPath);
44
+ let planContent = planResponse.content;
45
+ const initialStatusLine = `**planpong:** R0/${config.max_rounds} | ${formatProviderLabel(config.planner)} → ${formatProviderLabel(config.reviewer)} | Awaiting review`;
46
+ planContent = updatePlanStatusLine(planContent, initialStatusLine);
47
+ writeFileSync(planPath, planContent);
48
+ const initialLineCount = countLines(planContent);
49
+ const session = createSession(cwd, relativePlanPath, config.planner, config.reviewer, hashFile(planPath));
50
+ session.status = "in_review";
51
+ writeSessionState(cwd, session);
52
+ await callbacks.onPlanGenerated(planPath, planContent);
53
+ // Step 4: Human pause
54
+ if (config.human_in_loop) {
55
+ const shouldContinue = await callbacks.confirmContinue("Plan generated. Continue to review?");
56
+ if (!shouldContinue) {
57
+ session.status = "aborted";
58
+ writeSessionState(cwd, session);
59
+ return;
60
+ }
61
+ }
62
+ // Tracking stats
63
+ const issueTrajectory = [];
64
+ let totalAccepted = 0;
65
+ let totalRejected = 0;
66
+ let totalDeferred = 0;
67
+ // Review loop
68
+ for (let round = 1; round <= config.max_rounds; round++) {
69
+ session.currentRound = round;
70
+ writeSessionState(cwd, session);
71
+ const preHash = hashFile(planPath);
72
+ // Send to reviewer
73
+ callbacks.onReviewStarting(round);
74
+ const reviewResult = await runReviewRound(session, cwd, config, reviewerProvider);
75
+ issueTrajectory.push(reviewResult.severity);
76
+ await callbacks.onReviewComplete(round, reviewResult.feedback);
77
+ // Check convergence
78
+ if (reviewResult.converged) {
79
+ finalizeApproved(session, cwd, config, issueTrajectory, totalAccepted, totalRejected, totalDeferred, startTime, initialLineCount);
80
+ callbacks.onConverged(round, reviewResult.feedback);
81
+ return;
82
+ }
83
+ // Human pause
84
+ if (config.human_in_loop) {
85
+ const shouldContinue = await callbacks.confirmContinue(`Round ${round}: ${reviewResult.feedback.issues.length} issues found. Continue to revision?`);
86
+ if (!shouldContinue) {
87
+ session.status = "aborted";
88
+ writeSessionState(cwd, session);
89
+ return;
90
+ }
91
+ }
92
+ // Send to planner for revision
93
+ callbacks.onRevisionStarting(round);
94
+ const revisionResult = await runRevisionRound(session, cwd, config, plannerProvider);
95
+ totalAccepted += revisionResult.accepted;
96
+ totalRejected += revisionResult.rejected;
97
+ totalDeferred += revisionResult.deferred;
98
+ await callbacks.onRevisionComplete(round, revisionResult.revision);
99
+ // Check plan file hash (external modification detection)
100
+ const postHash = hashFile(planPath);
101
+ if (postHash !== preHash) {
102
+ if (!config.human_in_loop) {
103
+ const backupPath = `${planPath}.bak.${round}`;
104
+ copyFileSync(planPath, backupPath);
105
+ process.stderr.write(`Warning: Plan file modified externally during round ${round}. Backup saved to ${backupPath}\n`);
106
+ }
107
+ else {
108
+ const action = await callbacks.onHashMismatch(planPath, false);
109
+ if (action === "abort") {
110
+ session.status = "aborted";
111
+ writeSessionState(cwd, session);
112
+ return;
113
+ }
114
+ }
115
+ }
116
+ // Update status line in plan
117
+ planContent = readFileSync(planPath, "utf-8");
118
+ const currentLines = countLines(planContent);
119
+ const linesAdded = Math.max(0, currentLines - initialLineCount);
120
+ const linesRemoved = Math.max(0, initialLineCount - currentLines);
121
+ const elapsed = Date.now() - startTime;
122
+ const statusLine = buildStatusLine(session, config, issueTrajectory, totalAccepted, totalRejected, totalDeferred, linesAdded, linesRemoved, elapsed);
123
+ planContent = updatePlanStatusLine(planContent, statusLine);
124
+ writeFileSync(planPath, planContent);
125
+ session.planHash = hashFile(planPath);
126
+ writeSessionState(cwd, session);
127
+ }
128
+ // Max rounds reached
129
+ callbacks.onMaxRoundsReached(config.max_rounds);
130
+ session.status = "aborted";
131
+ writeSessionState(cwd, session);
132
+ }
133
+ /**
134
+ * Review an existing plan file through adversarial refinement.
135
+ * Skips plan generation — starts directly at the review cycle.
136
+ * Returns structured result for programmatic consumption.
137
+ */
138
+ export async function runReviewLoop(options) {
139
+ const { planPath, cwd, config, plannerProvider, reviewerProvider, callbacks, } = options;
140
+ const startTime = Date.now();
141
+ const { session, planContent } = initReviewSession(planPath, cwd, config);
142
+ const initialLineCount = countLines(planContent);
143
+ await callbacks.onPlanGenerated(planPath, planContent);
144
+ // Tracking stats
145
+ const issueTrajectory = [];
146
+ let totalAccepted = 0;
147
+ let totalRejected = 0;
148
+ let totalDeferred = 0;
149
+ // Review loop
150
+ for (let round = 1; round <= config.max_rounds; round++) {
151
+ session.currentRound = round;
152
+ writeSessionState(cwd, session);
153
+ const preHash = hashFile(planPath);
154
+ // Send to reviewer
155
+ callbacks.onReviewStarting(round);
156
+ const reviewResult = await runReviewRound(session, cwd, config, reviewerProvider);
157
+ issueTrajectory.push(reviewResult.severity);
158
+ await callbacks.onReviewComplete(round, reviewResult.feedback);
159
+ // Check convergence
160
+ if (reviewResult.converged) {
161
+ finalizeApproved(session, cwd, config, issueTrajectory, totalAccepted, totalRejected, totalDeferred, startTime, initialLineCount);
162
+ callbacks.onConverged(round, reviewResult.feedback);
163
+ return {
164
+ status: "approved",
165
+ rounds: round,
166
+ issueTrajectory,
167
+ accepted: totalAccepted,
168
+ rejected: totalRejected,
169
+ deferred: totalDeferred,
170
+ planPath,
171
+ sessionId: session.id,
172
+ elapsed: Date.now() - startTime,
173
+ };
174
+ }
175
+ // Human pause
176
+ if (config.human_in_loop) {
177
+ const shouldContinue = await callbacks.confirmContinue(`Round ${round}: ${reviewResult.feedback.issues.length} issues found. Continue to revision?`);
178
+ if (!shouldContinue) {
179
+ session.status = "aborted";
180
+ writeSessionState(cwd, session);
181
+ return {
182
+ status: "aborted",
183
+ rounds: round,
184
+ issueTrajectory,
185
+ accepted: totalAccepted,
186
+ rejected: totalRejected,
187
+ deferred: totalDeferred,
188
+ planPath,
189
+ sessionId: session.id,
190
+ elapsed: Date.now() - startTime,
191
+ };
192
+ }
193
+ }
194
+ // Send to planner for revision
195
+ callbacks.onRevisionStarting(round);
196
+ const revisionResult = await runRevisionRound(session, cwd, config, plannerProvider);
197
+ totalAccepted += revisionResult.accepted;
198
+ totalRejected += revisionResult.rejected;
199
+ totalDeferred += revisionResult.deferred;
200
+ await callbacks.onRevisionComplete(round, revisionResult.revision);
201
+ // Check plan file hash
202
+ const postHash = hashFile(planPath);
203
+ if (postHash !== preHash) {
204
+ if (!config.human_in_loop) {
205
+ const backupPath = `${planPath}.bak.${round}`;
206
+ copyFileSync(planPath, backupPath);
207
+ process.stderr.write(`Warning: Plan file modified externally during round ${round}. Backup saved to ${backupPath}\n`);
208
+ }
209
+ else {
210
+ const action = await callbacks.onHashMismatch(planPath, false);
211
+ if (action === "abort") {
212
+ session.status = "aborted";
213
+ writeSessionState(cwd, session);
214
+ return {
215
+ status: "aborted",
216
+ rounds: round,
217
+ issueTrajectory,
218
+ accepted: totalAccepted,
219
+ rejected: totalRejected,
220
+ deferred: totalDeferred,
221
+ planPath,
222
+ sessionId: session.id,
223
+ elapsed: Date.now() - startTime,
224
+ };
225
+ }
226
+ }
227
+ }
228
+ // Update status line in plan
229
+ const currentPlan = readFileSync(planPath, "utf-8");
230
+ const currentLines = countLines(currentPlan);
231
+ const linesAdded = Math.max(0, currentLines - initialLineCount);
232
+ const linesRemoved = Math.max(0, initialLineCount - currentLines);
233
+ const elapsed = Date.now() - startTime;
234
+ const statusLine = buildStatusLine(session, config, issueTrajectory, totalAccepted, totalRejected, totalDeferred, linesAdded, linesRemoved, elapsed);
235
+ const updatedPlan = updatePlanStatusLine(currentPlan, statusLine);
236
+ writeFileSync(planPath, updatedPlan);
237
+ session.planHash = hashFile(planPath);
238
+ writeSessionState(cwd, session);
239
+ }
240
+ // Max rounds reached
241
+ callbacks.onMaxRoundsReached(config.max_rounds);
242
+ session.status = "aborted";
243
+ writeSessionState(cwd, session);
244
+ return {
245
+ status: "max_rounds",
246
+ rounds: config.max_rounds,
247
+ issueTrajectory,
248
+ accepted: totalAccepted,
249
+ rejected: totalRejected,
250
+ deferred: totalDeferred,
251
+ planPath,
252
+ sessionId: session.id,
253
+ elapsed: Date.now() - startTime,
254
+ };
255
+ }
256
+ //# sourceMappingURL=loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.js","sourceRoot":"","sources":["../../../src/core/loop.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import type { Provider } from "../providers/types.js";
2
+ import type { PlanpongConfig, ProviderConfig } from "../schemas/config.js";
3
+ import type { ReviewFeedback } from "../schemas/feedback.js";
4
+ import type { PlannerRevision } from "../schemas/revision.js";
5
+ import type { Session } from "../schemas/session.js";
6
+ export interface RoundSeverity {
7
+ P1: number;
8
+ P2: number;
9
+ P3: number;
10
+ }
11
+ export interface ReviewRoundResult {
12
+ round: number;
13
+ feedback: ReviewFeedback;
14
+ severity: RoundSeverity;
15
+ converged: boolean;
16
+ }
17
+ export interface RevisionRoundResult {
18
+ round: number;
19
+ revision: PlannerRevision;
20
+ accepted: number;
21
+ rejected: number;
22
+ deferred: number;
23
+ planUpdated: boolean;
24
+ }
25
+ export interface SessionInit {
26
+ session: Session;
27
+ planContent: string;
28
+ config: PlanpongConfig;
29
+ }
30
+ export declare function hashFile(path: string): string;
31
+ export declare function formatRoundSeverity(round: RoundSeverity): string;
32
+ export declare function formatTrajectory(trajectory: RoundSeverity[]): string;
33
+ export declare function severityFromFeedback(feedback: ReviewFeedback): RoundSeverity;
34
+ export declare function formatTallies(accepted: number, rejected: number, deferred: number): string;
35
+ export declare function formatDuration(ms: number): string;
36
+ export declare function formatProviderLabel(provider: ProviderConfig): string;
37
+ export interface SessionStats {
38
+ issueTrajectory: RoundSeverity[];
39
+ totalAccepted: number;
40
+ totalRejected: number;
41
+ totalDeferred: number;
42
+ }
43
+ export declare function computeSessionStats(cwd: string, sessionId: string, currentRound: number): SessionStats;
44
+ export declare function buildStatusLine(session: Session, config: PlanpongConfig, issueTrajectory: RoundSeverity[], accepted: number, rejected: number, deferred: number, linesAdded: number, linesRemoved: number, elapsed: number): string;
45
+ /**
46
+ * Build and write the status line to the plan file.
47
+ * Used by both CLI and MCP paths after each round.
48
+ */
49
+ export declare function writeStatusLineToPlan(session: Session, cwd: string, config: PlanpongConfig, suffix?: string): string;
50
+ export declare function updatePlanStatusLine(planContent: string, statusLine: string): string;
51
+ /**
52
+ * Initialize a review session for an existing plan file.
53
+ * Validates the file exists, creates a session directory, and writes
54
+ * an initial status line to the plan.
55
+ */
56
+ export declare function initReviewSession(planPath: string, cwd: string, config: PlanpongConfig): SessionInit;
57
+ /**
58
+ * Run a single review round: send current plan to the reviewer for critique.
59
+ */
60
+ export declare function runReviewRound(session: Session, cwd: string, config: PlanpongConfig, reviewerProvider: Provider): Promise<ReviewRoundResult>;
61
+ /**
62
+ * Run a single revision round: send plan + feedback to the planner for revision.
63
+ */
64
+ export declare function runRevisionRound(session: Session, cwd: string, config: PlanpongConfig, plannerProvider: Provider): Promise<RevisionRoundResult>;
65
+ /**
66
+ * Mark the session as approved and update the plan's status line.
67
+ */
68
+ export declare function finalizeApproved(session: Session, cwd: string, config: PlanpongConfig, issueTrajectory: RoundSeverity[], totalAccepted: number, totalRejected: number, totalDeferred: number, startTime: number, initialLineCount: number): void;