@workermill/agent 0.7.9 → 0.7.11
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/dist/plan-validator.d.ts +13 -1
- package/dist/plan-validator.js +103 -7
- package/dist/planner.js +11 -1
- package/package.json +1 -1
package/dist/plan-validator.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface CriticResult {
|
|
|
39
39
|
suggestedChanges?: string[];
|
|
40
40
|
}>;
|
|
41
41
|
}
|
|
42
|
-
declare const AUTO_APPROVAL_THRESHOLD =
|
|
42
|
+
declare const AUTO_APPROVAL_THRESHOLD = 85;
|
|
43
43
|
/**
|
|
44
44
|
* Parse execution plan JSON from raw Claude CLI output.
|
|
45
45
|
* Mirrors server-side parseExecutionPlan() in planning-agent-local.ts.
|
|
@@ -61,6 +61,18 @@ export declare function applyStoryCap(plan: ExecutionPlan, maxStories: number):
|
|
|
61
61
|
droppedCount: number;
|
|
62
62
|
details: string[];
|
|
63
63
|
};
|
|
64
|
+
/**
|
|
65
|
+
* Resolve file overlaps by assigning each shared file to exactly one story.
|
|
66
|
+
* When multiple stories list the same targetFile, the first story keeps it
|
|
67
|
+
* and it's removed from subsequent stories. This prevents parallel merge
|
|
68
|
+
* conflicts during consolidation — same auto-fix pattern as applyFileCap.
|
|
69
|
+
*
|
|
70
|
+
* Returns details about resolved overlaps for logging.
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolveFileOverlaps(plan: ExecutionPlan): {
|
|
73
|
+
resolvedCount: number;
|
|
74
|
+
details: string[];
|
|
75
|
+
};
|
|
64
76
|
/**
|
|
65
77
|
* Re-serialize plan as a JSON code block for posting to the API.
|
|
66
78
|
* The server-side parseExecutionPlan() expects ```json ... ``` blocks.
|
package/dist/plan-validator.js
CHANGED
|
@@ -16,7 +16,7 @@ import { generateText } from "./providers.js";
|
|
|
16
16
|
// CONSTANTS
|
|
17
17
|
// ============================================================================
|
|
18
18
|
const MAX_TARGET_FILES = 5;
|
|
19
|
-
const AUTO_APPROVAL_THRESHOLD =
|
|
19
|
+
const AUTO_APPROVAL_THRESHOLD = 85;
|
|
20
20
|
// ============================================================================
|
|
21
21
|
// PLAN PARSING
|
|
22
22
|
// ============================================================================
|
|
@@ -25,16 +25,73 @@ const AUTO_APPROVAL_THRESHOLD = 80;
|
|
|
25
25
|
* Mirrors server-side parseExecutionPlan() in planning-agent-local.ts.
|
|
26
26
|
*/
|
|
27
27
|
export function parseExecutionPlan(output) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
// Strategy 1: Find ```json ... ``` block using bracket-matching instead of regex.
|
|
29
|
+
// The lazy regex ([\s\S]*?) fails when JSON string values contain ``` (e.g., code
|
|
30
|
+
// blocks in story descriptions from PRDs with CI/CD YAML examples).
|
|
31
|
+
const jsonFenceStart = output.indexOf("```json");
|
|
32
|
+
if (jsonFenceStart !== -1) {
|
|
33
|
+
// Find the opening { after ```json
|
|
34
|
+
const searchFrom = jsonFenceStart + 7; // length of "```json"
|
|
35
|
+
const braceStart = output.indexOf("{", searchFrom);
|
|
36
|
+
if (braceStart !== -1) {
|
|
37
|
+
const extracted = extractBalancedJson(output, braceStart);
|
|
38
|
+
if (extracted) {
|
|
39
|
+
return JSON.parse(extracted);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
31
42
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
// Strategy 2: Find raw JSON with "stories" key using bracket-matching
|
|
44
|
+
const storiesIdx = output.indexOf('"stories"');
|
|
45
|
+
if (storiesIdx !== -1) {
|
|
46
|
+
// Walk backwards to find the opening {
|
|
47
|
+
const before = output.substring(0, storiesIdx);
|
|
48
|
+
const braceStart = before.lastIndexOf("{");
|
|
49
|
+
if (braceStart !== -1) {
|
|
50
|
+
const extracted = extractBalancedJson(output, braceStart);
|
|
51
|
+
if (extracted) {
|
|
52
|
+
return JSON.parse(extracted);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
35
55
|
}
|
|
36
56
|
throw new Error("Could not find JSON execution plan in output");
|
|
37
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Extract a balanced JSON object from a string starting at the given position.
|
|
60
|
+
* Properly handles nested braces, strings with escaped characters, and code
|
|
61
|
+
* blocks embedded in JSON string values (which contain triple backticks).
|
|
62
|
+
*/
|
|
63
|
+
function extractBalancedJson(text, start) {
|
|
64
|
+
let depth = 0;
|
|
65
|
+
let inString = false;
|
|
66
|
+
let escape = false;
|
|
67
|
+
for (let i = start; i < text.length; i++) {
|
|
68
|
+
const ch = text[i];
|
|
69
|
+
if (escape) {
|
|
70
|
+
escape = false;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (ch === "\\") {
|
|
74
|
+
if (inString)
|
|
75
|
+
escape = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (ch === '"') {
|
|
79
|
+
inString = !inString;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (inString)
|
|
83
|
+
continue;
|
|
84
|
+
if (ch === "{")
|
|
85
|
+
depth++;
|
|
86
|
+
else if (ch === "}") {
|
|
87
|
+
depth--;
|
|
88
|
+
if (depth === 0) {
|
|
89
|
+
return text.substring(start, i + 1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null; // Unbalanced
|
|
94
|
+
}
|
|
38
95
|
// ============================================================================
|
|
39
96
|
// FILE CAP
|
|
40
97
|
// ============================================================================
|
|
@@ -81,6 +138,45 @@ export function applyStoryCap(plan, maxStories) {
|
|
|
81
138
|
return { droppedCount, details };
|
|
82
139
|
}
|
|
83
140
|
// ============================================================================
|
|
141
|
+
// FILE OVERLAP VALIDATION
|
|
142
|
+
// ============================================================================
|
|
143
|
+
/**
|
|
144
|
+
* Resolve file overlaps by assigning each shared file to exactly one story.
|
|
145
|
+
* When multiple stories list the same targetFile, the first story keeps it
|
|
146
|
+
* and it's removed from subsequent stories. This prevents parallel merge
|
|
147
|
+
* conflicts during consolidation — same auto-fix pattern as applyFileCap.
|
|
148
|
+
*
|
|
149
|
+
* Returns details about resolved overlaps for logging.
|
|
150
|
+
*/
|
|
151
|
+
export function resolveFileOverlaps(plan) {
|
|
152
|
+
const fileOwner = new Map(); // file → first story that claims it
|
|
153
|
+
let resolvedCount = 0;
|
|
154
|
+
const details = [];
|
|
155
|
+
for (const story of plan.stories) {
|
|
156
|
+
if (!story.targetFiles || story.targetFiles.length === 0)
|
|
157
|
+
continue;
|
|
158
|
+
const kept = [];
|
|
159
|
+
const removed = [];
|
|
160
|
+
for (const file of story.targetFiles) {
|
|
161
|
+
const owner = fileOwner.get(file);
|
|
162
|
+
if (owner) {
|
|
163
|
+
// File already claimed by an earlier story — remove from this one
|
|
164
|
+
removed.push(file);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
fileOwner.set(file, story.id);
|
|
168
|
+
kept.push(file);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (removed.length > 0) {
|
|
172
|
+
story.targetFiles = kept;
|
|
173
|
+
resolvedCount += removed.length;
|
|
174
|
+
details.push(`${story.id}: removed ${removed.join(", ")} (owned by ${removed.map((f) => fileOwner.get(f)).join(", ")})`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { resolvedCount, details };
|
|
178
|
+
}
|
|
179
|
+
// ============================================================================
|
|
84
180
|
// PLAN SERIALIZATION
|
|
85
181
|
// ============================================================================
|
|
86
182
|
/**
|
package/dist/planner.js
CHANGED
|
@@ -19,7 +19,7 @@ import ora from "ora";
|
|
|
19
19
|
import { spawn, execSync } from "child_process";
|
|
20
20
|
import { findClaudePath } from "./config.js";
|
|
21
21
|
import { api } from "./api.js";
|
|
22
|
-
import { parseExecutionPlan, applyFileCap, applyStoryCap, serializePlan, runCriticValidation, formatCriticFeedback, AUTO_APPROVAL_THRESHOLD, } from "./plan-validator.js";
|
|
22
|
+
import { parseExecutionPlan, applyFileCap, applyStoryCap, resolveFileOverlaps, serializePlan, runCriticValidation, formatCriticFeedback, AUTO_APPROVAL_THRESHOLD, } from "./plan-validator.js";
|
|
23
23
|
import { generateTextWithTools } from "./ai-sdk-generate.js";
|
|
24
24
|
/** Max Planner-Critic iterations before giving up */
|
|
25
25
|
const MAX_ITERATIONS = 3;
|
|
@@ -881,6 +881,16 @@ export async function planTask(task, config, credentials) {
|
|
|
881
881
|
console.log(`${ts()} ${taskLabel} ${chalk.dim(detail)}`);
|
|
882
882
|
}
|
|
883
883
|
}
|
|
884
|
+
// 2c3. Resolve file overlaps (assign each shared file to first story only)
|
|
885
|
+
const { resolvedCount: overlapCount, details: overlapDetails } = resolveFileOverlaps(plan);
|
|
886
|
+
if (overlapCount > 0) {
|
|
887
|
+
const msg = `${PREFIX} File overlap resolved: ${overlapCount} shared file(s) de-duped across stories`;
|
|
888
|
+
console.log(`${ts()} ${taskLabel} ${chalk.yellow("⚠")} ${msg}`);
|
|
889
|
+
await postLog(task.id, msg);
|
|
890
|
+
for (const detail of overlapDetails) {
|
|
891
|
+
console.log(`${ts()} ${taskLabel} ${chalk.dim(detail)}`);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
884
894
|
console.log(`${ts()} ${taskLabel} Plan: ${chalk.bold(plan.stories.length)} stories (max ${maxStories})`);
|
|
885
895
|
await postLog(task.id, `${PREFIX} Plan generated: ${plan.stories.length} stories (${formatElapsed(elapsed)}). Running critic validation...`);
|
|
886
896
|
// 2d. Run critic validation
|