@sebastianandreasson/pi-autonomous-agents 0.2.0 → 0.3.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/README.md +12 -1
- package/SETUP.md +6 -0
- package/docs/PI_SUPERVISOR.md +7 -9
- package/package.json +3 -3
- package/pi.config.json +1 -0
- package/src/pi-client.mjs +37 -0
- package/src/pi-config.mjs +48 -17
- package/src/pi-history.mjs +2 -0
- package/src/pi-preflight.mjs +48 -17
- package/src/pi-prompts.mjs +292 -103
- package/src/pi-repo.mjs +6 -3
- package/src/pi-rpc-adapter.mjs +31 -0
- package/src/pi-supervisor.mjs +408 -26
- package/src/pi-telemetry.mjs +14 -1
- package/templates/pi.config.example.json +2 -1
package/src/pi-prompts.mjs
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
|
|
3
|
+
function clampLines(text, maxLines) {
|
|
4
|
+
const normalized = String(text ?? '').trim()
|
|
5
|
+
if (normalized === '') {
|
|
6
|
+
return ''
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const lines = normalized.split('\n')
|
|
10
|
+
if (!Number.isFinite(maxLines) || maxLines <= 0 || lines.length <= maxLines) {
|
|
11
|
+
return normalized
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const remaining = lines.length - maxLines
|
|
15
|
+
return `${lines.slice(0, maxLines).join('\n')}\n... (${remaining} more lines omitted)`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatFeedbackSection(label, text, maxLines) {
|
|
19
|
+
const excerpt = clampLines(text, maxLines)
|
|
20
|
+
if (excerpt === '') {
|
|
21
|
+
return ''
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return `\n${label}:\n${excerpt}\n`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatChangedFilesSection(files, maxFiles) {
|
|
28
|
+
const list = Array.isArray(files) ? files.filter(Boolean) : []
|
|
29
|
+
if (list.length === 0) {
|
|
30
|
+
return '- No file changes were detected from the prior turn.'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const limit = Number.isFinite(maxFiles) && maxFiles > 0 ? maxFiles : list.length
|
|
34
|
+
const visible = list.slice(0, limit)
|
|
35
|
+
const remaining = list.length - visible.length
|
|
36
|
+
const lines = visible.map((file) => `- ${file}`)
|
|
37
|
+
if (remaining > 0) {
|
|
38
|
+
lines.push(`- ... and ${remaining} more files`)
|
|
39
|
+
}
|
|
40
|
+
return lines.join('\n')
|
|
41
|
+
}
|
|
42
|
+
|
|
3
43
|
function displayPath(config, filePath) {
|
|
4
44
|
const relativePath = path.relative(config.cwd, filePath)
|
|
5
45
|
if (
|
|
@@ -13,22 +53,25 @@ function displayPath(config, filePath) {
|
|
|
13
53
|
return path.basename(filePath)
|
|
14
54
|
}
|
|
15
55
|
|
|
16
|
-
function formatVisualFeedback(visualFeedback) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return `\nLatest visual feedback from prior runs:\n${text}\n`
|
|
56
|
+
function formatVisualFeedback(config, visualFeedback) {
|
|
57
|
+
return formatFeedbackSection(
|
|
58
|
+
'Latest visual feedback from prior runs',
|
|
59
|
+
visualFeedback,
|
|
60
|
+
configMaxLines(config, 'maxVisualFeedbackLines', 20),
|
|
61
|
+
)
|
|
23
62
|
}
|
|
24
63
|
|
|
25
|
-
function formatTesterFeedback(testerFeedback) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
64
|
+
function formatTesterFeedback(config, testerFeedback) {
|
|
65
|
+
return formatFeedbackSection(
|
|
66
|
+
'Latest tester feedback from prior runs',
|
|
67
|
+
testerFeedback,
|
|
68
|
+
configMaxLines(config, 'maxTesterFeedbackLines', 32),
|
|
69
|
+
)
|
|
70
|
+
}
|
|
30
71
|
|
|
31
|
-
|
|
72
|
+
function configMaxLines(config, key, fallback) {
|
|
73
|
+
const value = Number(config?.[key])
|
|
74
|
+
return Number.isFinite(value) && value > 0 ? value : fallback
|
|
32
75
|
}
|
|
33
76
|
|
|
34
77
|
function indentBlock(text, prefix = '') {
|
|
@@ -54,92 +97,195 @@ function staleEditRecoveryRules() {
|
|
|
54
97
|
].join('\n')
|
|
55
98
|
}
|
|
56
99
|
|
|
100
|
+
function repoInstructionsAuthorityLine(config, instructionsFile, usesBundledInstructions) {
|
|
101
|
+
if (usesBundledInstructions) {
|
|
102
|
+
return ''
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return `Repo-local instructions in ${displayPath(config, instructionsFile)} are the primary role contract. Follow them over package defaults when they differ.\n`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function testerPassOwnershipRules(config) {
|
|
109
|
+
if (config.commitMode === 'plan') {
|
|
110
|
+
return {
|
|
111
|
+
successRule: '- If your verdict is PASS, do not run git add or git commit yourself. Provide a commit plan for the harness to execute.',
|
|
112
|
+
isolationRule: '- The commit plan must include only the files related to this task. If the working tree is too messy to isolate safely, use VERDICT: BLOCKED instead of guessing.',
|
|
113
|
+
extraRule: '- If you can produce a PASS, include the commit plan in the same response. Avoid making the harness ask for a second commit-only pass.',
|
|
114
|
+
successFormat: [
|
|
115
|
+
'If and only if your verdict is PASS, also include exactly this commit plan block before the verdict line:',
|
|
116
|
+
'- COMMIT_MESSAGE: <one-line commit message>',
|
|
117
|
+
'- COMMIT_FILES:',
|
|
118
|
+
'- path/to/file-one',
|
|
119
|
+
'- path/to/file-two',
|
|
120
|
+
'',
|
|
121
|
+
'Do not add commentary on the same lines as COMMIT_MESSAGE or COMMIT_FILES. Put only the message value after COMMIT_MESSAGE:, then one file path per line under COMMIT_FILES:.',
|
|
122
|
+
].join('\n'),
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
successRule: '- If your verdict is PASS, stage only the files related to this task and create the git commit yourself before the verdict line.',
|
|
128
|
+
isolationRule: '- If the working tree is too messy to isolate safely, use VERDICT: BLOCKED instead of guessing.',
|
|
129
|
+
extraRule: '- Use git status before committing, stage only the related files, and create one concise commit message in the format <type>(<scope>): <summary> when possible.',
|
|
130
|
+
successFormat: [
|
|
131
|
+
'If and only if your verdict is PASS, include exactly this block before the verdict line after creating the commit:',
|
|
132
|
+
'- COMMIT_CREATED: true',
|
|
133
|
+
'- COMMIT_MESSAGE: <one-line commit message>',
|
|
134
|
+
'- COMMIT_SHA: <short-or-full-sha>',
|
|
135
|
+
].join('\n'),
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
57
139
|
export function buildMainPrompt(config, options = {}) {
|
|
58
140
|
const taskFile = displayPath(config, config.taskFile)
|
|
59
141
|
const instructionsFile = displayPath(config, config.developerInstructionsFile)
|
|
60
|
-
const visualFeedbackSection = formatVisualFeedback(options.visualFeedback)
|
|
61
|
-
const testerFeedbackSection = formatTesterFeedback(options.testerFeedback)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
142
|
+
const visualFeedbackSection = formatVisualFeedback(config, options.visualFeedback)
|
|
143
|
+
const testerFeedbackSection = formatTesterFeedback(config, options.testerFeedback)
|
|
144
|
+
const authorityLine = repoInstructionsAuthorityLine(
|
|
145
|
+
config,
|
|
146
|
+
config.developerInstructionsFile,
|
|
147
|
+
config.usingBundledDeveloperInstructions,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if (!config.usingBundledDeveloperInstructions) {
|
|
151
|
+
return `Read ${taskFile} and ${instructionsFile}.
|
|
152
|
+
${authorityLine}${visualFeedbackSection}
|
|
65
153
|
${testerFeedbackSection}
|
|
66
154
|
|
|
67
155
|
Work only on the current phase.
|
|
68
156
|
Select the first unchecked actionable checkbox in phase order.
|
|
69
|
-
Complete
|
|
157
|
+
Complete one coherent task, or at most 2 tightly related unchecked tasks if they are naturally done together.
|
|
70
158
|
|
|
71
|
-
|
|
159
|
+
Harness rules:
|
|
72
160
|
- Start by checking git status so you know whether unrelated changes already exist.
|
|
73
161
|
- Update code, config, and docs only as needed for the selected task.
|
|
74
162
|
- Tick only the checkbox items that are actually completed.
|
|
75
|
-
- Do not select "Done when" checkboxes as the active task unless the implementation items in that section are already satisfied.
|
|
76
|
-
- If you discover missing prerequisite work, add a new unchecked checkbox under the same phase, then complete only what is necessary.
|
|
77
|
-
- Do not skip to a later phase unless the current task is blocked.
|
|
78
163
|
- If blocked, add a brief note directly under the relevant task in ${taskFile} explaining the blocker, then stop.
|
|
79
|
-
- Do not create
|
|
80
|
-
|
|
81
|
-
- If dependencies must change, edit package.json only, then stop.
|
|
82
|
-
- Prefer the smallest viable implementation that fully satisfies the selected checkbox.
|
|
83
|
-
- Avoid broad refactors unless the selected task explicitly requires them.
|
|
84
|
-
${indentBlock(innerLoopValidationRules(config.testCommand), '\t')}
|
|
85
|
-
- Trust tool results over your own guesses. If a read tool shows file contents, use that exact output instead of arguing with it.
|
|
86
|
-
- Do not repeatedly rewrite the same file because you suspect a formatting issue. Read once, identify the exact mismatch, then make one focused fix.
|
|
87
|
-
${indentBlock(staleEditRecoveryRules(), '\t')}
|
|
88
|
-
- Do not create the final commit during the developer pass. Leave a clean diff for the tester to validate and commit if it passes.
|
|
164
|
+
- Do not create the final commit during the developer pass.
|
|
165
|
+
${staleEditRecoveryRules()}
|
|
89
166
|
|
|
90
167
|
Before stopping:
|
|
91
168
|
- Tick completed checkbox items in ${taskFile}.
|
|
92
|
-
|
|
93
|
-
|
|
169
|
+
- Keep changes scoped to one coherent step.
|
|
170
|
+
- Stop after finishing that step.`
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return `Read ${taskFile} and ${instructionsFile}.
|
|
174
|
+
${authorityLine}${visualFeedbackSection}
|
|
175
|
+
${testerFeedbackSection}
|
|
176
|
+
|
|
177
|
+
Do one current-phase unchecked task.
|
|
178
|
+
|
|
179
|
+
Rules:
|
|
180
|
+
- Start with git status.
|
|
181
|
+
- Select the first unchecked actionable checkbox in phase order.
|
|
182
|
+
- Keep changes minimal and scoped.
|
|
183
|
+
- Tick only completed items.
|
|
184
|
+
- If blocked, note it under the task in ${taskFile} and stop.
|
|
185
|
+
- Do not touch lockfiles, generated files, or unrelated assets.
|
|
186
|
+
- Do not commit in the developer pass.
|
|
187
|
+
${innerLoopValidationRules(config.testCommand)}
|
|
188
|
+
${staleEditRecoveryRules()}
|
|
189
|
+
|
|
190
|
+
Before stopping:
|
|
191
|
+
- Tick completed checkbox items in ${taskFile}.
|
|
192
|
+
- Stop after one coherent step.`
|
|
94
193
|
}
|
|
95
194
|
|
|
96
195
|
export function buildFixPrompt(config, recentVerificationOutput, options = {}) {
|
|
97
196
|
const taskFile = displayPath(config, config.taskFile)
|
|
98
197
|
const instructionsFile = displayPath(config, config.developerInstructionsFile)
|
|
99
|
-
const visualFeedbackSection = formatVisualFeedback(options.visualFeedback)
|
|
100
|
-
const testerFeedbackSection = formatTesterFeedback(options.testerFeedback)
|
|
198
|
+
const visualFeedbackSection = formatVisualFeedback(config, options.visualFeedback)
|
|
199
|
+
const testerFeedbackSection = formatTesterFeedback(config, options.testerFeedback)
|
|
200
|
+
const authorityLine = repoInstructionsAuthorityLine(
|
|
201
|
+
config,
|
|
202
|
+
config.developerInstructionsFile,
|
|
203
|
+
config.usingBundledDeveloperInstructions,
|
|
204
|
+
)
|
|
205
|
+
const findings = clampLines(recentVerificationOutput, configMaxLines(config, 'maxVerificationExcerptLines', 40))
|
|
206
|
+
|
|
207
|
+
if (!config.usingBundledDeveloperInstructions) {
|
|
208
|
+
return `Read ${taskFile} and ${instructionsFile}.
|
|
209
|
+
${authorityLine}${visualFeedbackSection}
|
|
210
|
+
${testerFeedbackSection}
|
|
211
|
+
|
|
212
|
+
The tester step found a real problem in the current implementation. Fix only the product behavior related to the current phase and current task.
|
|
213
|
+
|
|
214
|
+
Recent tester findings:
|
|
215
|
+
${findings}
|
|
216
|
+
|
|
217
|
+
Harness rules:
|
|
218
|
+
- Start by checking git status so you know which files are already dirty.
|
|
219
|
+
- Do not paper over product bugs by weakening tests.
|
|
220
|
+
- Keep changes minimal and focused on the failing behavior.
|
|
221
|
+
- Do not perform speculative cleanup or unrelated refactors in this pass.
|
|
222
|
+
- Do not create the final commit during the developer fix pass.
|
|
223
|
+
${staleEditRecoveryRules()}
|
|
224
|
+
|
|
225
|
+
Before stopping:
|
|
226
|
+
- Tick any checkbox in ${taskFile} only if it is now actually complete.
|
|
227
|
+
- Stop after one coherent fix.`
|
|
228
|
+
}
|
|
101
229
|
|
|
102
230
|
return `Read ${taskFile} and ${instructionsFile}.
|
|
103
|
-
${visualFeedbackSection}
|
|
231
|
+
${authorityLine}${visualFeedbackSection}
|
|
104
232
|
${testerFeedbackSection}
|
|
105
233
|
|
|
106
234
|
The tester step found a real problem in the current implementation. Fix only the product behavior related to the current phase and current task.
|
|
107
235
|
|
|
108
236
|
Recent tester findings:
|
|
109
|
-
${
|
|
237
|
+
${findings}
|
|
110
238
|
|
|
111
239
|
Rules:
|
|
112
|
-
- Start
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
- Do not create
|
|
117
|
-
|
|
118
|
-
- If dependencies must change, edit package.json only, then stop.
|
|
119
|
-
- Keep changes minimal and focused on the failing behavior.
|
|
120
|
-
${indentBlock(innerLoopValidationRules(config.testCommand), '\t')}
|
|
121
|
-
- Trust tool results over your own guesses. If a read tool shows file contents, use that exact output instead of arguing with it.
|
|
122
|
-
- Do not repeatedly rewrite the same file because you suspect a formatting issue. Read once, identify the exact mismatch, then make one focused fix.
|
|
123
|
-
${indentBlock(staleEditRecoveryRules(), '\t')}
|
|
124
|
-
- Do not create the final commit during the developer fix pass. Leave the repaired diff for the tester to re-check and commit if it passes.
|
|
240
|
+
- Start with git status.
|
|
241
|
+
- Keep the fix narrow.
|
|
242
|
+
- Do not weaken tests to hide product bugs.
|
|
243
|
+
- Do not perform speculative cleanup or unrelated refactors.
|
|
244
|
+
- Do not create the final commit.
|
|
245
|
+
${staleEditRecoveryRules()}
|
|
125
246
|
|
|
126
247
|
Before stopping:
|
|
127
|
-
|
|
128
|
-
|
|
248
|
+
- Tick any checkbox in ${taskFile} only if it is now actually complete.
|
|
249
|
+
- Stop after one coherent fix.`
|
|
129
250
|
}
|
|
130
251
|
|
|
131
252
|
export function buildSteeringPrompt(config, reason, options = {}) {
|
|
132
253
|
const taskFile = displayPath(config, config.taskFile)
|
|
133
|
-
const
|
|
134
|
-
const
|
|
254
|
+
const instructionsFile = displayPath(config, config.developerInstructionsFile)
|
|
255
|
+
const visualFeedbackSection = formatVisualFeedback(config, options.visualFeedback)
|
|
256
|
+
const testerFeedbackSection = formatTesterFeedback(config, options.testerFeedback)
|
|
257
|
+
const authorityLine = repoInstructionsAuthorityLine(
|
|
258
|
+
config,
|
|
259
|
+
config.developerInstructionsFile,
|
|
260
|
+
config.usingBundledDeveloperInstructions,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if (!config.usingBundledDeveloperInstructions) {
|
|
264
|
+
return `Continue from the current repo state.
|
|
265
|
+
Read ${taskFile} and ${instructionsFile}.
|
|
266
|
+
${authorityLine}${visualFeedbackSection}
|
|
267
|
+
${testerFeedbackSection}
|
|
268
|
+
|
|
269
|
+
Reason for this follow-up: ${reason}
|
|
270
|
+
|
|
271
|
+
Select the first unchecked actionable checkbox in the current phase, complete one coherent task, tick completed items, run any repo-local verification required by your role instructions, and stop.
|
|
272
|
+
|
|
273
|
+
Additional harness guardrails:
|
|
274
|
+
- Start by checking git status.
|
|
275
|
+
- Do not repeat the same tool call over and over.
|
|
276
|
+
- If you already read a file, use that context instead of rereading it unless something changed.
|
|
277
|
+
- If an edit fails once, reread the file before retrying. Do not repeat the same exact edit attempt.
|
|
278
|
+
- If you are stuck, make the smallest decisive next action or stop and state the blocker.`
|
|
279
|
+
}
|
|
135
280
|
|
|
136
281
|
return `Continue from the current repo state.
|
|
137
|
-
${
|
|
282
|
+
Read ${taskFile} and ${instructionsFile}.
|
|
283
|
+
${authorityLine}${visualFeedbackSection}
|
|
138
284
|
${testerFeedbackSection}
|
|
139
285
|
|
|
140
286
|
Reason for this follow-up: ${reason}
|
|
141
287
|
|
|
142
|
-
|
|
288
|
+
Select the first unchecked actionable checkbox in the current phase, complete one coherent task, tick completed items, run verification, and stop.
|
|
143
289
|
|
|
144
290
|
Additional guardrails:
|
|
145
291
|
- Do not repeat the same tool call over and over.
|
|
@@ -160,18 +306,69 @@ export function buildTesterPrompt(config, {
|
|
|
160
306
|
}) {
|
|
161
307
|
const taskFile = displayPath(config, config.taskFile)
|
|
162
308
|
const instructionsFile = displayPath(config, config.testerInstructionsFile)
|
|
163
|
-
const visualFeedbackSection = formatVisualFeedback(visualFeedback)
|
|
164
|
-
const testerFeedbackSection = formatTesterFeedback(testerFeedback)
|
|
165
|
-
const changedFilesSection =
|
|
166
|
-
|
|
167
|
-
|
|
309
|
+
const visualFeedbackSection = formatVisualFeedback(config, visualFeedback)
|
|
310
|
+
const testerFeedbackSection = formatTesterFeedback(config, testerFeedback)
|
|
311
|
+
const changedFilesSection = formatChangedFilesSection(
|
|
312
|
+
changedFiles,
|
|
313
|
+
configMaxLines(config, 'maxPromptChangedFiles', 10),
|
|
314
|
+
)
|
|
315
|
+
const compactDeveloperNotes = clampLines(
|
|
316
|
+
developerNotes || '(none provided)',
|
|
317
|
+
configMaxLines(config, 'maxPromptNotesLines', 16),
|
|
318
|
+
)
|
|
168
319
|
const verificationCommand = config.testCommand.trim() === '' ? '(not configured)' : config.testCommand
|
|
169
320
|
const visualCaptureNote = config.visualReviewEnabled
|
|
170
|
-
? `\n-
|
|
321
|
+
? `\n- Keep the screenshot capture flow working so the harness still produces current visual artifacts for review.`
|
|
171
322
|
: ''
|
|
323
|
+
const authorityLine = repoInstructionsAuthorityLine(
|
|
324
|
+
config,
|
|
325
|
+
config.testerInstructionsFile,
|
|
326
|
+
config.usingBundledTesterInstructions,
|
|
327
|
+
)
|
|
328
|
+
const passOwnership = testerPassOwnershipRules(config)
|
|
329
|
+
|
|
330
|
+
if (!config.usingBundledTesterInstructions) {
|
|
331
|
+
return `Read ${taskFile} and ${instructionsFile}.
|
|
332
|
+
${authorityLine}${visualFeedbackSection}
|
|
333
|
+
${testerFeedbackSection}
|
|
334
|
+
|
|
335
|
+
You are the TESTER role. You are reviewing the most recent developer work from an independent quality and functionality perspective.
|
|
336
|
+
|
|
337
|
+
Current phase: ${phase}
|
|
338
|
+
Current task: ${task}
|
|
339
|
+
Reason for this tester pass: ${reason}
|
|
340
|
+
|
|
341
|
+
Developer notes:
|
|
342
|
+
${compactDeveloperNotes}
|
|
343
|
+
|
|
344
|
+
Files changed by the developer:
|
|
345
|
+
${changedFilesSection}
|
|
346
|
+
|
|
347
|
+
Rules:
|
|
348
|
+
- Start with git status.
|
|
349
|
+
- Follow repo-local tester instructions for what to verify and which commands to run.
|
|
350
|
+
- Prefer one focused review pass.
|
|
351
|
+
- If blocked or inconclusive, return VERDICT: BLOCKED.
|
|
352
|
+
- Do not hide real bugs with brittle tests.
|
|
353
|
+
- ${passOwnership.successRule.slice(2)}
|
|
354
|
+
- ${passOwnership.isolationRule.slice(2)}
|
|
355
|
+
- ${passOwnership.extraRule.slice(2)}${visualCaptureNote}
|
|
356
|
+
|
|
357
|
+
Before the verdict line, include a short section in plain text with:
|
|
358
|
+
- Observed flow:
|
|
359
|
+
- Player-facing result:
|
|
360
|
+
- Regression check:
|
|
361
|
+
|
|
362
|
+
${passOwnership.successFormat}
|
|
363
|
+
|
|
364
|
+
Before stopping, end your final response with exactly one verdict line:
|
|
365
|
+
- VERDICT: PASS
|
|
366
|
+
- VERDICT: FAIL
|
|
367
|
+
- VERDICT: BLOCKED`
|
|
368
|
+
}
|
|
172
369
|
|
|
173
370
|
return `Read ${taskFile} and ${instructionsFile}.
|
|
174
|
-
${visualFeedbackSection}
|
|
371
|
+
${authorityLine}${visualFeedbackSection}
|
|
175
372
|
${testerFeedbackSection}
|
|
176
373
|
|
|
177
374
|
You are the TESTER role. You are reviewing the most recent developer work from an independent quality and functionality perspective.
|
|
@@ -181,47 +378,28 @@ Current task: ${task}
|
|
|
181
378
|
Reason for this tester pass: ${reason}
|
|
182
379
|
|
|
183
380
|
Developer notes:
|
|
184
|
-
${
|
|
381
|
+
${compactDeveloperNotes}
|
|
185
382
|
|
|
186
383
|
Files changed by the developer:
|
|
187
384
|
${changedFilesSection}
|
|
188
385
|
|
|
189
|
-
|
|
190
|
-
-
|
|
191
|
-
- Add or update verification focused on the changed behavior.
|
|
192
|
-
- Prefer browser-driven checks and targeted tests over broad rewrites.
|
|
386
|
+
Rules:
|
|
387
|
+
- Start with git status.
|
|
193
388
|
- Run the repo verification command yourself: ${verificationCommand}
|
|
194
389
|
${indentBlock(innerLoopValidationRules(verificationCommand), '\t')}
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
- If
|
|
198
|
-
${
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
- Start by checking git status so you can separate this task from unrelated dirty files.
|
|
202
|
-
- Prefer editing tests, fixtures, and minimal observability hooks.
|
|
203
|
-
- Avoid editing product code unless a tiny testability hook is essential and does not change user-facing behavior.
|
|
204
|
-
- If you find a real product bug or incomplete functionality, do not hide it with brittle tests.
|
|
205
|
-
- If blocked by tooling or environment, state the blocker clearly.
|
|
206
|
-
- Trust tool results over your own guesses. If a read tool shows file contents, use that exact output instead of arguing with it.
|
|
207
|
-
${indentBlock(staleEditRecoveryRules(), '\t')}
|
|
208
|
-
- Treat "the player cannot start, continue, select, buy, unlock, or exit correctly" as a FAIL even if the code compiles.
|
|
209
|
-
- Before PASS, identify at least one concrete player-visible success path you exercised and one thing you checked for regressions.
|
|
210
|
-
- If your verdict is PASS and the verification command succeeded, do not run git add or git commit yourself. Instead, provide a commit plan for the harness to execute.
|
|
211
|
-
- The commit plan must include only the files related to this task. If the working tree is too messy to isolate safely, use VERDICT: BLOCKED instead of guessing.
|
|
212
|
-
- Use a concise commit message in the format <type>(<scope>): <summary> when possible.
|
|
213
|
-
- Stop after one coherent tester pass.
|
|
390
|
+
- Prefer one focused browser-driven review pass.
|
|
391
|
+
- Do not hide real bugs with brittle tests.
|
|
392
|
+
- If blocked or inconclusive, return VERDICT: BLOCKED.
|
|
393
|
+
${indentBlock(passOwnership.successRule, '\t')}
|
|
394
|
+
${indentBlock(passOwnership.isolationRule, '\t')}
|
|
395
|
+
${indentBlock(passOwnership.extraRule, '\t')}${visualCaptureNote}
|
|
214
396
|
|
|
215
397
|
Before the verdict line, include a short section in plain text with:
|
|
216
398
|
- Observed flow:
|
|
217
399
|
- Player-facing result:
|
|
218
400
|
- Regression check:
|
|
219
401
|
|
|
220
|
-
|
|
221
|
-
- COMMIT_MESSAGE: <one-line commit message>
|
|
222
|
-
- COMMIT_FILES:
|
|
223
|
-
- path/to/file-one
|
|
224
|
-
- path/to/file-two
|
|
402
|
+
${indentBlock(passOwnership.successFormat, '\t')}
|
|
225
403
|
|
|
226
404
|
Before stopping, end your final response with exactly one verdict line:
|
|
227
405
|
- VERDICT: PASS
|
|
@@ -240,14 +418,24 @@ export function buildCommitPrompt(config, {
|
|
|
240
418
|
}) {
|
|
241
419
|
const taskFile = displayPath(config, config.taskFile)
|
|
242
420
|
const instructionsFile = displayPath(config, config.testerInstructionsFile)
|
|
243
|
-
const visualFeedbackSection = formatVisualFeedback(visualFeedback)
|
|
244
|
-
const testerFeedbackSection = formatTesterFeedback(testerFeedback)
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
421
|
+
const visualFeedbackSection = formatVisualFeedback(config, visualFeedback)
|
|
422
|
+
const testerFeedbackSection = formatTesterFeedback(config, testerFeedback)
|
|
423
|
+
const authorityLine = repoInstructionsAuthorityLine(
|
|
424
|
+
config,
|
|
425
|
+
config.testerInstructionsFile,
|
|
426
|
+
config.usingBundledTesterInstructions,
|
|
427
|
+
)
|
|
428
|
+
const changedFilesSection = formatChangedFilesSection(
|
|
429
|
+
changedFiles,
|
|
430
|
+
configMaxLines(config, 'maxPromptChangedFiles', 10),
|
|
431
|
+
)
|
|
432
|
+
const compactDeveloperNotes = clampLines(
|
|
433
|
+
developerNotes || '(none provided)',
|
|
434
|
+
configMaxLines(config, 'maxPromptNotesLines', 16),
|
|
435
|
+
)
|
|
248
436
|
|
|
249
437
|
return `Read ${taskFile} and ${instructionsFile}.
|
|
250
|
-
${visualFeedbackSection}
|
|
438
|
+
${authorityLine}${visualFeedbackSection}
|
|
251
439
|
${testerFeedbackSection}
|
|
252
440
|
|
|
253
441
|
You are the TESTER role. The implementation already passed functional review, but the final commit was not created.
|
|
@@ -257,7 +445,7 @@ Current task: ${task}
|
|
|
257
445
|
Reason for this follow-up: ${reason}
|
|
258
446
|
|
|
259
447
|
Developer/tester notes:
|
|
260
|
-
${
|
|
448
|
+
${compactDeveloperNotes}
|
|
261
449
|
|
|
262
450
|
Files currently dirty:
|
|
263
451
|
${changedFilesSection}
|
|
@@ -265,10 +453,9 @@ ${changedFilesSection}
|
|
|
265
453
|
Your job now is commit-plan finalization only. Do not run git commands yourself.
|
|
266
454
|
|
|
267
455
|
Rules:
|
|
268
|
-
- Start
|
|
456
|
+
- Start with git status.
|
|
269
457
|
- Do not change product code, tests, docs, or TODO items in this pass.
|
|
270
458
|
- Select only the files related to this task.
|
|
271
|
-
- Use a concise commit message in the format <type>(<scope>): <summary> when possible.
|
|
272
459
|
- If the working tree is too messy to isolate safely, do not guess. End with VERDICT: BLOCKED.
|
|
273
460
|
|
|
274
461
|
If you can isolate the correct commit, include exactly this block before the verdict line:
|
|
@@ -277,6 +464,8 @@ If you can isolate the correct commit, include exactly this block before the ver
|
|
|
277
464
|
- path/to/file-one
|
|
278
465
|
- path/to/file-two
|
|
279
466
|
|
|
467
|
+
Do not add commentary on the same lines as COMMIT_MESSAGE or COMMIT_FILES. Put only the message value after COMMIT_MESSAGE:, then one file path per line under COMMIT_FILES:.
|
|
468
|
+
|
|
280
469
|
Before stopping, end your final response with exactly one verdict line:
|
|
281
470
|
- VERDICT: PASS
|
|
282
471
|
- VERDICT: BLOCKED`
|
package/src/pi-repo.mjs
CHANGED
|
@@ -74,13 +74,16 @@ function normalizeStatusPath(statusPath) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function parseStatusLine(line) {
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
if (line.trim() === '') {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (line.length < 4 || line[2] !== ' ') {
|
|
79
82
|
return null
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
const renamedMarker = ' -> '
|
|
83
|
-
const pathText =
|
|
86
|
+
const pathText = line.slice(3)
|
|
84
87
|
if (pathText.includes(renamedMarker)) {
|
|
85
88
|
const [, nextPath] = pathText.split(renamedMarker)
|
|
86
89
|
return normalizeStatusPath(nextPath)
|
package/src/pi-rpc-adapter.mjs
CHANGED
|
@@ -121,6 +121,7 @@ async function run() {
|
|
|
121
121
|
const pending = new Map()
|
|
122
122
|
let requestCounter = 0
|
|
123
123
|
const streamTerminal = request.streamTerminal === true
|
|
124
|
+
const requestedModel = typeof request.model === 'string' ? request.model : ''
|
|
124
125
|
const loopRepeatThreshold = Number.isFinite(Number(request.loopRepeatThreshold))
|
|
125
126
|
? Number(request.loopRepeatThreshold)
|
|
126
127
|
: 12
|
|
@@ -416,6 +417,9 @@ async function run() {
|
|
|
416
417
|
await waitForAgentEnd()
|
|
417
418
|
|
|
418
419
|
if (heartbeatTimedOut) {
|
|
420
|
+
const toolCalls = events.filter((event) => event.type === 'tool_execution_start').length
|
|
421
|
+
const toolErrors = events.filter((event) => event.type === 'tool_execution_end' && event.isError).length
|
|
422
|
+
const messageUpdates = events.filter((event) => event.type === 'message_update').length
|
|
419
423
|
console.log(JSON.stringify({
|
|
420
424
|
sessionId: request.sessionId ?? '',
|
|
421
425
|
sessionFile: request.sessionFile ?? '',
|
|
@@ -438,6 +442,15 @@ async function run() {
|
|
|
438
442
|
continueAccepted ? 'continue_accepted=true' : '',
|
|
439
443
|
continueRejected ? 'continue_rejected=true' : '',
|
|
440
444
|
].join(' '),
|
|
445
|
+
role: '',
|
|
446
|
+
model: requestedModel,
|
|
447
|
+
toolCalls,
|
|
448
|
+
toolErrors,
|
|
449
|
+
messageUpdates,
|
|
450
|
+
stopReason: '',
|
|
451
|
+
loopDetected: false,
|
|
452
|
+
loopSignature: '',
|
|
453
|
+
terminalReason: 'heartbeat_timeout',
|
|
441
454
|
}))
|
|
442
455
|
return
|
|
443
456
|
}
|
|
@@ -464,6 +477,15 @@ async function run() {
|
|
|
464
477
|
: assistantError !== '' || (assistantText === '' && toolCalls === 0 && messageUpdates === 0)
|
|
465
478
|
? 'failed'
|
|
466
479
|
: 'success'
|
|
480
|
+
const terminalReason = loopDetected
|
|
481
|
+
? 'loop_detected'
|
|
482
|
+
: assistantError !== ''
|
|
483
|
+
? 'assistant_error'
|
|
484
|
+
: assistantStopReason === 'length'
|
|
485
|
+
? 'assistant_stop_length'
|
|
486
|
+
: status === 'failed'
|
|
487
|
+
? 'empty_agent_turn'
|
|
488
|
+
: 'agent_completed'
|
|
467
489
|
const notes = [
|
|
468
490
|
`PI session ${state.data.sessionId}`,
|
|
469
491
|
`pi_pid=${child.pid ?? 'unknown'}`,
|
|
@@ -494,6 +516,15 @@ async function run() {
|
|
|
494
516
|
status,
|
|
495
517
|
output,
|
|
496
518
|
notes,
|
|
519
|
+
role: '',
|
|
520
|
+
model: requestedModel,
|
|
521
|
+
toolCalls,
|
|
522
|
+
toolErrors,
|
|
523
|
+
messageUpdates,
|
|
524
|
+
stopReason: assistantStopReason,
|
|
525
|
+
loopDetected,
|
|
526
|
+
loopSignature,
|
|
527
|
+
terminalReason,
|
|
497
528
|
}))
|
|
498
529
|
} finally {
|
|
499
530
|
if (heartbeatInterval) {
|