@plainconceptsplatform/workflows 1.5.0 → 1.6.10
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/route-processing.js +9 -2
- package/dist/workflow-catalog.js +1 -1
- package/loops/actions/apply-agent-output/action.yml +5 -0
- package/loops/actions/classify-route/classify-route.sh +34 -13
- package/loops/actions/decide-agent-retry/action.yml +31 -3
- package/loops/actions/decide-agent-retry/classify-failure.sh +50 -0
- package/loops/actions/housekeeping/action.yml +20 -0
- package/loops/actions/identify-gate-subject/action.yml +52 -17
- package/loops/actions/merge-train/action.yml +71 -0
- package/loops/actions/merge-train/merge-train.sh +495 -0
- package/loops/actions/push-agent-branch/action.yml +73 -45
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +290 -84
- package/loops/docs/diagrams.md +55 -13
- package/loops/scripts/compile-agent-workflows.mjs +20 -3
- package/loops/templates/ci/app-ci-dotnet-next.yml +13 -14
- package/loops/templates/ci/app-ci-node-monorepo.yml +13 -14
- package/loops/workflows/agent-implement.md +77 -32
- package/loops/workflows/agent-merge-gate.md +90 -62
- package/loops/workflows/agent-refine.md +9 -0
- package/loops/workflows/work-router.yml +285 -471
- package/package.json +1 -1
|
@@ -331,7 +331,19 @@ for (const file of readdirSync(workflowDirectory)) {
|
|
|
331
331
|
// runner rather than the run so a runner keeps its server warm between its own jobs.
|
|
332
332
|
// No double quotes here: the enclosing run: is a double-quoted YAML scalar, so a quote
|
|
333
333
|
// would have to be escaped. The default has no spaces, so bare is safe shell.
|
|
334
|
-
.replace(/\/tmp\/opencode-data(?!-\$\{\{)/g, () => '/tmp/opencode-data-${{ runner.name }}')
|
|
334
|
+
.replace(/\/tmp\/opencode-data(?!-\$\{\{)/g, () => '/tmp/opencode-data-${{ runner.name }}')
|
|
335
|
+
// The engine key falls back to FORGE_API_KEY, the one secret every repository here has. The
|
|
336
|
+
// router maps it to OPENAI_API_KEY when it CALLS a worker, but a stack child is DISPATCHED on
|
|
337
|
+
// its feature branch, and a dispatched run reads repository secrets by their own names: with
|
|
338
|
+
// no OPENAI_API_KEY or CODEX_API_KEY set, activation died on "Validate CODEX_API_KEY or
|
|
339
|
+
// OPENAI_API_KEY secret" and every stack child in Pliny-Bot failed that way for hours. gh-aw
|
|
340
|
+
// reads the key in four places, the validation step, the engine env and the redaction list
|
|
341
|
+
// included, and all of them must see the fallback or the key would run unredacted. The
|
|
342
|
+
// longest form goes first so the shorter ones do not match inside it.
|
|
343
|
+
.replaceAll('${{ secrets.CODEX_API_KEY || secrets.OPENAI_API_KEY }}',
|
|
344
|
+
'${{ secrets.CODEX_API_KEY || secrets.OPENAI_API_KEY || secrets.FORGE_API_KEY }}')
|
|
345
|
+
.replaceAll('${{ secrets.CODEX_API_KEY }}', '${{ secrets.CODEX_API_KEY || secrets.FORGE_API_KEY }}')
|
|
346
|
+
.replaceAll('${{ secrets.OPENAI_API_KEY }}', '${{ secrets.OPENAI_API_KEY || secrets.FORGE_API_KEY }}');
|
|
335
347
|
|
|
336
348
|
let rewritten = patched;
|
|
337
349
|
// A queued implement executes hours after its run was created, but actions/checkout
|
|
@@ -340,8 +352,8 @@ for (const file of readdirSync(workflowDirectory)) {
|
|
|
340
352
|
// every later push became a rebase onto a moved parent, which gh-aw's signed-commit
|
|
341
353
|
// push cannot do (it dies with "cannot rebase: You have unstaged changes" and files the
|
|
342
354
|
// pull request as an issue instead of a pull request). Checking out the branch tip at
|
|
343
|
-
// execution time makes the commit parent current main; the implement
|
|
344
|
-
//
|
|
355
|
+
// execution time makes the commit parent current main; the implement queue, shared by the
|
|
356
|
+
// children of one refine, keeps sibling merges out of the window while the agent runs.
|
|
345
357
|
//
|
|
346
358
|
// A stack child dispatches this workflow directly (`gh workflow run
|
|
347
359
|
// agent-implement.lock.yml --ref feat/N`), and there the pin must select the feature
|
|
@@ -415,6 +427,11 @@ for (const file of readdirSync(workflowDirectory)) {
|
|
|
415
427
|
for (const check of [
|
|
416
428
|
{ name: "bundle upload glob", ok: /\n( +)- name: Upload agent artifacts\n\1 if: always\(\)\n\1 continue-on-error: true\n\1 uses: actions\/upload-artifact@[^\n]*\n\1 with:\n\1 name: \$\{\{ needs\.activation\.outputs\.artifact_prefix \}\}agent\n\1 path: \|\n(?:\1 [^\n]*\n)*\1 \/tmp\/gh-aw\/aw-\*\.bundle\n/ },
|
|
417
429
|
{ name: "placeholder step intact", ok: /\n( +)- name: Write agent output placeholder if missing\n\1 if: always\(\)\n\1 run: \|\n\1 if \[ ! -f \/tmp\/gh-aw\/agent_output\.json \]; then\n\1 echo '\{"items":\[\]\}' > \/tmp\/gh-aw\/agent_output\.json\n\1 fi\n/ },
|
|
430
|
+
// Every read of the engine key carries the FORGE_API_KEY fallback; see the rewrite above.
|
|
431
|
+
{
|
|
432
|
+
name: "engine key falls back to FORGE_API_KEY",
|
|
433
|
+
ok: lock => !lock.includes("${{ secrets.OPENAI_API_KEY }}") && !lock.includes("${{ secrets.CODEX_API_KEY }}"),
|
|
434
|
+
},
|
|
418
435
|
{ name: "failed-jobs reporter disabled", ok: /- name: Report failed jobs\n[ \t]*id: report_failed_jobs\n[ \t]*if: false[^\n]*\n/ },
|
|
419
436
|
// Only agent-refine declares update_issue; the other workers never carry the tool.
|
|
420
437
|
{
|
|
@@ -280,32 +280,31 @@ jobs:
|
|
|
280
280
|
# needs contents: write this workflow does not have, and fails the run that gates deploy.
|
|
281
281
|
upload-release-assets: false
|
|
282
282
|
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
288
|
-
#
|
|
283
|
+
# Hands this run's verdict to the router's merge train, which decides whether this pull request
|
|
284
|
+
# is the one in front and what it needs. A pull request opened or updated with a bot token gets
|
|
285
|
+
# no workflow_run event for its CI (the run's actor is the bot), and the train brings people's
|
|
286
|
+
# pull requests up to date with one too, so every pull request on this repository hands over
|
|
287
|
+
# from here. workflow_dispatch is one of the two events GITHUB_TOKEN may raise, so no App token
|
|
288
|
+
# is needed, and the router treats a dispatch as trusted. The hourly schedule is the fallback.
|
|
289
289
|
#
|
|
290
290
|
# If a deploy pipeline calls this workflow with `uses:`, that caller must also grant
|
|
291
291
|
# `actions: write`. A called workflow may not request a scope its caller withheld, and the
|
|
292
292
|
# whole run fails to start with zero jobs and no annotation when it does.
|
|
293
|
-
dispatch-merge-
|
|
294
|
-
name: Dispatch merge
|
|
293
|
+
dispatch-merge-train:
|
|
294
|
+
name: Dispatch merge train
|
|
295
295
|
needs: [api, mutation-api, web, mutation-web]
|
|
296
296
|
if: >
|
|
297
297
|
always() &&
|
|
298
298
|
github.event_name == 'pull_request' &&
|
|
299
299
|
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
300
|
-
github.event.pull_request.draft == false
|
|
301
|
-
github.event.pull_request.user.type == 'Bot'
|
|
300
|
+
github.event.pull_request.draft == false
|
|
302
301
|
runs-on: agents-arc
|
|
303
302
|
timeout-minutes: 5
|
|
304
303
|
permissions:
|
|
305
304
|
contents: read
|
|
306
305
|
actions: write
|
|
307
306
|
steps:
|
|
308
|
-
- name:
|
|
307
|
+
- name: Hand this run's verdict to the merge train
|
|
309
308
|
env:
|
|
310
309
|
GH_TOKEN: ${{ github.token }}
|
|
311
310
|
REPO: ${{ github.repository }}
|
|
@@ -324,10 +323,10 @@ jobs:
|
|
|
324
323
|
done
|
|
325
324
|
# A cancelled run was superseded by a newer push, whose own run dispatches.
|
|
326
325
|
if [ "$conclusion" = cancelled ]; then
|
|
327
|
-
echo "CI was cancelled; not
|
|
326
|
+
echo "CI was cancelled; not moving the merge train."
|
|
328
327
|
exit 0
|
|
329
328
|
fi
|
|
330
|
-
echo "
|
|
329
|
+
echo "Moving the merge train for PR #$PR_NUMBER with CI $conclusion (run $RUN_ID)."
|
|
331
330
|
gh workflow run work-router.yml --repo "$REPO" --ref "$REF" \
|
|
332
|
-
-f operation=merge-
|
|
331
|
+
-f operation=merge-train -f pr-number="$PR_NUMBER" \
|
|
333
332
|
-f ci-conclusion="$conclusion" -f ci-run-id="$RUN_ID"
|
|
@@ -210,32 +210,31 @@ jobs:
|
|
|
210
210
|
# needs contents: write this workflow does not have, and fails the run that gates deploy.
|
|
211
211
|
upload-release-assets: false
|
|
212
212
|
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
#
|
|
213
|
+
# Hands this run's verdict to the router's merge train, which decides whether this pull request
|
|
214
|
+
# is the one in front and what it needs. A pull request opened or updated with a bot token gets
|
|
215
|
+
# no workflow_run event for its CI (the run's actor is the bot), and the train brings people's
|
|
216
|
+
# pull requests up to date with one too, so every pull request on this repository hands over
|
|
217
|
+
# from here. workflow_dispatch is one of the two events GITHUB_TOKEN may raise, so no App token
|
|
218
|
+
# is needed, and the router treats a dispatch as trusted. The hourly schedule is the fallback.
|
|
219
219
|
#
|
|
220
220
|
# If a deploy pipeline calls this workflow with `uses:`, that caller must also grant
|
|
221
221
|
# `actions: write`. A called workflow may not request a scope its caller withheld, and the
|
|
222
222
|
# whole run fails to start with zero jobs and no annotation when it does.
|
|
223
|
-
dispatch-merge-
|
|
224
|
-
name: Dispatch merge
|
|
223
|
+
dispatch-merge-train:
|
|
224
|
+
name: Dispatch merge train
|
|
225
225
|
needs: [lint, web, mutation-web, e2e, desktop, mobile]
|
|
226
226
|
if: >
|
|
227
227
|
always() &&
|
|
228
228
|
github.event_name == 'pull_request' &&
|
|
229
229
|
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
230
|
-
github.event.pull_request.draft == false
|
|
231
|
-
github.event.pull_request.user.type == 'Bot'
|
|
230
|
+
github.event.pull_request.draft == false
|
|
232
231
|
runs-on: agents-arc
|
|
233
232
|
timeout-minutes: 5
|
|
234
233
|
permissions:
|
|
235
234
|
contents: read
|
|
236
235
|
actions: write
|
|
237
236
|
steps:
|
|
238
|
-
- name:
|
|
237
|
+
- name: Hand this run's verdict to the merge train
|
|
239
238
|
env:
|
|
240
239
|
GH_TOKEN: ${{ github.token }}
|
|
241
240
|
REPO: ${{ github.repository }}
|
|
@@ -254,10 +253,10 @@ jobs:
|
|
|
254
253
|
done
|
|
255
254
|
# A cancelled run was superseded by a newer push, whose own run dispatches.
|
|
256
255
|
if [ "$conclusion" = cancelled ]; then
|
|
257
|
-
echo "CI was cancelled; not
|
|
256
|
+
echo "CI was cancelled; not moving the merge train."
|
|
258
257
|
exit 0
|
|
259
258
|
fi
|
|
260
|
-
echo "
|
|
259
|
+
echo "Moving the merge train for PR #$PR_NUMBER with CI $conclusion (run $RUN_ID)."
|
|
261
260
|
gh workflow run work-router.yml --repo "$REPO" --ref "$REF" \
|
|
262
|
-
-f operation=merge-
|
|
261
|
+
-f operation=merge-train -f pr-number="$PR_NUMBER" \
|
|
263
262
|
-f ci-conclusion="$conclusion" -f ci-run-id="$RUN_ID"
|
|
@@ -185,7 +185,7 @@ jobs:
|
|
|
185
185
|
fi
|
|
186
186
|
|
|
187
187
|
# A queued implement run can start long after it was dispatched. If triage concluded
|
|
188
|
-
# needs-maintainer while this run waited in
|
|
188
|
+
# needs-maintainer while this run waited in its implement queue, the issue now
|
|
189
189
|
# carries review and the verdict must stand: the reserve step clears review blindly,
|
|
190
190
|
# so eligibility has to catch it first (Pliny-Bot #372).
|
|
191
191
|
if jq -e 'index("review")' >/dev/null <<<"$labels"; then
|
|
@@ -245,6 +245,8 @@ jobs:
|
|
|
245
245
|
contents: read
|
|
246
246
|
issues: write
|
|
247
247
|
pull-requests: write
|
|
248
|
+
# reads the agent job's log and annotations, and re-enters through the router on a retry
|
|
249
|
+
actions: write
|
|
248
250
|
steps:
|
|
249
251
|
- name: Checkout workflow actions
|
|
250
252
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
@@ -433,8 +435,48 @@ jobs:
|
|
|
433
435
|
${{ env.IMPLEMENT_MARKER }}
|
|
434
436
|
${{ env.PATCH_WITHOUT_REQUEST_COMMENT }}
|
|
435
437
|
[View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
436
|
-
|
|
438
|
+
# A run that ends with nothing is not always the agent's verdict. The AI gateway can answer
|
|
439
|
+
# nothing and end the engine one turn after it began, and the runner's VM can be taken away
|
|
440
|
+
# mid-job; neither says the work was wrong, and parking them for the janitor cost up to six
|
|
441
|
+
# hours each (Pliny-Bot #693's first attempt). The shared rule repeats those, and a run that
|
|
442
|
+
# died young, within the attempt budget.
|
|
443
|
+
- name: Decide whether an empty run is worth repeating
|
|
444
|
+
id: empty_retry
|
|
437
445
|
if: needs.safe_outputs.outputs.created_pr_number == '' && !contains(needs.agent.outputs.output_types, 'create_pull_request') && !contains(needs.agent.outputs.output_types, 'noop') && needs.agent.outputs.has_patch != 'true'
|
|
446
|
+
uses: ./.github/actions/decide-agent-retry
|
|
447
|
+
with:
|
|
448
|
+
token: ${{ github.token }}
|
|
449
|
+
attempts-so-far: ${{ inputs.attempts_so_far || '0' }}
|
|
450
|
+
park-at: ${{ env.PARK_AT_ATTEMPT }}
|
|
451
|
+
under-minutes: ${{ env.RETRY_UNDER_MINUTES }}
|
|
452
|
+
- name: Report the retried empty run
|
|
453
|
+
if: steps.empty_retry.outputs.retry == 'true'
|
|
454
|
+
uses: ./.github/actions/create-issue-comment
|
|
455
|
+
with:
|
|
456
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
457
|
+
issue-number: ${{ inputs.issue-number }}
|
|
458
|
+
body: |
|
|
459
|
+
${{ env.ATTEMPT_MARKER }}
|
|
460
|
+
Attempt ${{ steps.empty_retry.outputs.next }} of ${{ env.MAX_ATTEMPTS }} ended with nothing after ${{ steps.empty_retry.outputs.minutes }} minutes (${{ steps.empty_retry.outputs.cause }}). That is what a provider or runner outage looks like, not a verdict on the work, so this is being retried.
|
|
461
|
+
The issue keeps `implement`.
|
|
462
|
+
[View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
463
|
+
- name: Send the issue back through the router
|
|
464
|
+
if: steps.empty_retry.outputs.retry == 'true'
|
|
465
|
+
env:
|
|
466
|
+
GH_TOKEN: ${{ github.token }}
|
|
467
|
+
REPO: ${{ github.repository }}
|
|
468
|
+
REF: ${{ github.event.repository.default_branch }}
|
|
469
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
470
|
+
NEXT: ${{ steps.empty_retry.outputs.next }}
|
|
471
|
+
run: |
|
|
472
|
+
set -euo pipefail
|
|
473
|
+
# The provider recovers in seconds, so pause before re-entering rather than dispatching
|
|
474
|
+
# back into the same outage.
|
|
475
|
+
sleep 30
|
|
476
|
+
gh workflow run work-router.yml --repo "$REPO" --ref "$REF" -f operation=implement -f issue-number="$ISSUE_NUMBER" -f attempts_so_far="$NEXT"
|
|
477
|
+
echo "Re-dispatched implement for #$ISSUE_NUMBER as attempt $NEXT."
|
|
478
|
+
- name: Flag a run that produced nothing
|
|
479
|
+
if: needs.safe_outputs.outputs.created_pr_number == '' && !contains(needs.agent.outputs.output_types, 'create_pull_request') && !contains(needs.agent.outputs.output_types, 'noop') && needs.agent.outputs.has_patch != 'true' && steps.empty_retry.outputs.retry != 'true'
|
|
438
480
|
uses: ./.github/actions/add-issue-labels
|
|
439
481
|
with:
|
|
440
482
|
token: ${{ steps.app-token.outputs.token }}
|
|
@@ -443,7 +485,7 @@ jobs:
|
|
|
443
485
|
${{ env.REVIEW_LABEL }}
|
|
444
486
|
${{ env.STALLED_LABEL }}
|
|
445
487
|
- name: Say the run produced nothing
|
|
446
|
-
if: needs.safe_outputs.outputs.created_pr_number == '' && !contains(needs.agent.outputs.output_types, 'create_pull_request') && !contains(needs.agent.outputs.output_types, 'noop') && needs.agent.outputs.has_patch != 'true'
|
|
488
|
+
if: needs.safe_outputs.outputs.created_pr_number == '' && !contains(needs.agent.outputs.output_types, 'create_pull_request') && !contains(needs.agent.outputs.output_types, 'noop') && needs.agent.outputs.has_patch != 'true' && steps.empty_retry.outputs.retry != 'true'
|
|
447
489
|
uses: ./.github/actions/create-issue-comment
|
|
448
490
|
with:
|
|
449
491
|
token: ${{ steps.app-token.outputs.token }}
|
|
@@ -478,36 +520,17 @@ jobs:
|
|
|
478
520
|
with:
|
|
479
521
|
client-id: ${{ secrets.BOT_APP_ID }}
|
|
480
522
|
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
|
523
|
+
# The shared rule, which this worker used to carry its own copy of. It repeats a run that died
|
|
524
|
+
# young, and now also one that was killed after a long time: the runner's VM taken away
|
|
525
|
+
# mid-job, or the AI gateway answering nothing and ending the engine one turn after it began.
|
|
481
526
|
- name: Decide whether this failure is worth repeating
|
|
482
527
|
id: decide
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
UNDER_MINUTES: ${{ env.RETRY_UNDER_MINUTES }}
|
|
490
|
-
run: |
|
|
491
|
-
set -euo pipefail
|
|
492
|
-
# The agent job belongs to this same run: a called workflow shares the caller's run id.
|
|
493
|
-
read -r started finished <<<"$(gh api "repos/$REPO/actions/runs/$RUN_ID/jobs?per_page=100" \
|
|
494
|
-
--jq '[.jobs[] | select(.name | endswith("agent"))] | last // empty
|
|
495
|
-
| "\(.started_at // "") \(.completed_at // "")"')"
|
|
496
|
-
minutes=-1
|
|
497
|
-
if [ -n "${started:-}" ] && [ -n "${finished:-}" ]; then
|
|
498
|
-
minutes=$(( ( $(date -u -d "$finished" +%s) - $(date -u -d "$started" +%s) ) / 60 ))
|
|
499
|
-
fi
|
|
500
|
-
retry=false
|
|
501
|
-
# An unknown duration is treated as a long run: never retry on a guess.
|
|
502
|
-
if [ "$minutes" -ge 0 ] && [ "$minutes" -lt "$UNDER_MINUTES" ] && [ "$ATTEMPTS" -lt "$PARK_AT" ]; then
|
|
503
|
-
retry=true
|
|
504
|
-
fi
|
|
505
|
-
{
|
|
506
|
-
echo "retry=$retry"
|
|
507
|
-
echo "next=$((ATTEMPTS + 1))"
|
|
508
|
-
echo "minutes=$minutes"
|
|
509
|
-
} >> "$GITHUB_OUTPUT"
|
|
510
|
-
echo "agent job ran for ${minutes}m; attempts so far ${ATTEMPTS}; retry=${retry}"
|
|
528
|
+
uses: ./.github/actions/decide-agent-retry
|
|
529
|
+
with:
|
|
530
|
+
token: ${{ github.token }}
|
|
531
|
+
attempts-so-far: ${{ inputs.attempts_so_far || '0' }}
|
|
532
|
+
park-at: ${{ env.PARK_AT_ATTEMPT }}
|
|
533
|
+
under-minutes: ${{ env.RETRY_UNDER_MINUTES }}
|
|
511
534
|
# The attempt is recorded before any label moves, so a failure in the steps below leaves a
|
|
512
535
|
# run that can be counted rather than an issue released with nothing to show for it.
|
|
513
536
|
- name: Report the failed attempt
|
|
@@ -518,7 +541,7 @@ jobs:
|
|
|
518
541
|
issue-number: ${{ inputs.issue-number }}
|
|
519
542
|
body: |
|
|
520
543
|
${{ env.ATTEMPT_MARKER }}
|
|
521
|
-
Attempt ${{ steps.decide.outputs.next }} of ${{ env.MAX_ATTEMPTS }} ended after ${{ steps.decide.outputs.minutes }} minutes
|
|
544
|
+
Attempt ${{ steps.decide.outputs.next }} of ${{ env.MAX_ATTEMPTS }} ended after ${{ steps.decide.outputs.minutes }} minutes without an answer (${{ steps.decide.outputs.cause }}). That is what a provider or runner outage looks like, not a verdict on the work, so this is being retried.
|
|
522
545
|
The issue keeps `implement`.
|
|
523
546
|
[View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
524
547
|
- name: Release the reservation for the retry
|
|
@@ -703,6 +726,28 @@ timeout-minutes: 180
|
|
|
703
726
|
phase skill it names, in order, and do not explore the code yourself where it says a phase
|
|
704
727
|
is skipped.
|
|
705
728
|
|
|
729
|
+
**The pipeline is these skill loads, in this order, each a call to the Skill tool.** A run
|
|
730
|
+
that edits source files without having made them has skipped the pipeline, whatever it
|
|
731
|
+
wrote instead:
|
|
732
|
+
|
|
733
|
+
1. `pc-plan-goal`, then Phase 0 as it describes.
|
|
734
|
+
2. `pc-plan-propose`, even when the issue is refined. It is what turns the issue into
|
|
735
|
+
`openspec/changes/<id>/tasks.md` with every task annotated
|
|
736
|
+
`<!-- agent: <engineer>.<tier>, depends_on: [...], touches: [...] -->`. **You never write
|
|
737
|
+
`proposal.md` or `tasks.md` yourself.** A hand-written `tasks.md` carries no annotations,
|
|
738
|
+
and without them `pc-plan-apply` has no workers to spawn, so the whole change is done by
|
|
739
|
+
you alone, in sequence, on one model. That is the failure this step exists to prevent.
|
|
740
|
+
3. `pc-plan-apply`. The tasks are implemented by the subagents it spawns, in waves, each on
|
|
741
|
+
the model tier its annotation names. **In this phase you do not edit source files
|
|
742
|
+
yourself.** If it stops because a worker cannot be resolved, report that with
|
|
743
|
+
`report_incomplete`, naming the task and the missing worker; do not do the task yourself
|
|
744
|
+
to get past it.
|
|
745
|
+
4. `pc-repo-verify`, then `pc-plan-archive`, as `pc-plan-goal` directs.
|
|
746
|
+
|
|
747
|
+
Before the first edit to a source file, check that
|
|
748
|
+
`openspec/changes/<id>/tasks.md` exists and that its tasks carry the annotation above. If it
|
|
749
|
+
does not, you have not run `pc-plan-propose`: load it now.
|
|
750
|
+
|
|
706
751
|
a. `branch` is the output mode this sandbox needs: the branch is kept, nothing is merged and
|
|
707
752
|
nothing is pushed. Without it the skill merges into the local default branch and deletes
|
|
708
753
|
the feature branch, and step 6 below then opens a pull request from a branch that is
|