@1aboveio/skills 1.0.19 → 1.0.21
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/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/resources.json +146 -146
- package/package.json +4 -4
- package/skills/app-dev/airflow/SKILL.md +1 -1
- package/skills/app-dev/android-development/SKILL.md +1 -1
- package/skills/app-dev/app-debug/SKILL.md +1 -1
- package/skills/app-dev/better-auth/SKILL.md +1 -1
- package/skills/app-dev/data-catalog/SKILL.md +1 -1
- package/skills/app-dev/data-quality/SKILL.md +1 -1
- package/skills/app-dev/ios-foundation/SKILL.md +1 -1
- package/skills/app-dev/maestro/SKILL.md +8 -1
- package/skills/app-dev/monorepo/SKILL.md +1 -1
- package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
- package/skills/app-dev/prisma-setup/SKILL.md +1 -1
- package/skills/app-dev/pyspark/SKILL.md +1 -1
- package/skills/app-dev/python-backend/SKILL.md +1 -1
- package/skills/app-dev/shadcn/SKILL.md +1 -1
- package/skills/app-dev/zod-v4/SKILL.md +1 -1
- package/skills/cicd/cloud-build/SKILL.md +1 -1
- package/skills/cicd/cloud-debug/SKILL.md +1 -1
- package/skills/cicd/cloud-deploy/SKILL.md +1 -1
- package/skills/cicd/docker/SKILL.md +1 -1
- package/skills/cicd/github-actions/SKILL.md +5 -1
- package/skills/cicd/github-actions/references/pipeline.md +26 -0
- package/skills/cicd/github-flow/SKILL.md +1 -1
- package/skills/cicd/google-cloud/SKILL.md +1 -1
- package/skills/cicd/mergify/SKILL.md +1 -1
- package/skills/cicd/podman/SKILL.md +1 -1
- package/skills/content/article-review/SKILL.md +1 -1
- package/skills/content/article-to-image/SKILL.md +1 -1
- package/skills/content/flowchart-svg/SKILL.md +1 -1
- package/skills/content/image-gen/SKILL.md +1 -1
- package/skills/content/mermaid/SKILL.md +1 -1
- package/skills/content/wechat-article-writer/SKILL.md +1 -1
- package/skills/content/wechat-publisher/SKILL.md +1 -1
- package/skills/content/wechat-search/SKILL.md +1 -1
- package/skills/content/xai-search/SKILL.md +1 -1
- package/skills/core/distill-writing/SKILL.md +1 -1
- package/skills/core/doc-to-md/SKILL.md +1 -1
- package/skills/core/editorial-review/SKILL.md +1 -1
- package/skills/core/house-style/SKILL.md +1 -1
- package/skills/core/plain-language/SKILL.md +1 -1
- package/skills/core/polish-cn/SKILL.md +1 -1
- package/skills/core/reg-parser/SKILL.md +1 -1
- package/skills/core/terminology/SKILL.md +1 -1
- package/skills/core/write-doc/SKILL.md +1 -1
- package/skills/core/write-report/SKILL.md +1 -1
- package/skills/engineering/better-review/SKILL.md +5 -4
- package/skills/engineering/better-review/references/passes.md +20 -0
- package/skills/engineering/better-review/references/result.md +14 -0
- package/skills/engineering/e2e-test/SKILL.md +8 -1
- package/skills/engineering/engineering/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
- package/skills/engineering/ensure-coverage/SKILL.md +8 -1
- package/skills/engineering/golden-path/SKILL.md +5 -1
- package/skills/engineering/harness-runtime/SKILL.md +1 -1
- package/skills/engineering/resolve-issues/SKILL.md +33 -11
- package/skills/engineering/resolve-issues/references/ci.md +15 -1
- package/skills/engineering/resolve-issues/references/delivery-progress.md +18 -0
- package/skills/engineering/resolve-issues/references/done.md +12 -0
- package/skills/engineering/resolve-issues/references/dropbox.md +31 -6
- package/skills/engineering/resolve-issues/references/expire.md +6 -3
- package/skills/engineering/resolve-issues/references/exploration.md +10 -3
- package/skills/engineering/resolve-issues/references/implementation.md +88 -16
- package/skills/engineering/resolve-issues/references/preflight.md +6 -0
- package/skills/engineering/resolve-issues/references/review.md +42 -9
- package/skills/engineering/resolve-issues/references/task-plan.md +17 -2
- package/skills/engineering/resolve-issues/scripts/dropbox.mjs +2 -1
- package/skills/engineering/resolve-issues/scripts/next-actions.mjs +79 -4
- package/skills/engineering/resolve-issues/scripts/review-verdict.mjs +63 -10
- package/skills/engineering/resolve-release/SKILL.md +1 -1
- package/skills/engineering/rush-issues/SKILL.md +1 -1
- package/skills/engineering/rush-release/SKILL.md +1 -1
- package/skills/engineering/test-quality/SKILL.md +1 -1
- package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
- package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
- package/skills/risk-compliance/contract-review/SKILL.md +1 -1
- package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
- package/skills/risk-compliance/fraud-detection/SKILL.md +1 -1
- package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/portfolio-payment-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
- package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
|
@@ -27,7 +27,8 @@ the coordinator.
|
|
|
27
27
|
not the backend suite.
|
|
28
28
|
5. **Run focused checks locally; CI runs the rest.**
|
|
29
29
|
Example: run the touched package's tests and typecheck, push, and let CI
|
|
30
|
-
run the full matrix.
|
|
30
|
+
run the full matrix. Run release campaigns only at their assigned gate;
|
|
31
|
+
implement campaign automation when the current issue requests it.
|
|
31
32
|
6. **Report in one short record and return.**
|
|
32
33
|
Example: "Persists the Charge layout per installation. Ran charge tests
|
|
33
34
|
and typecheck: pass. CI: pending. Pending: release device campaign
|
|
@@ -53,6 +54,55 @@ independent clone prepared before its first spawn; after that, never pass
|
|
|
53
54
|
`git_clone_workspace:true` again, and use `sandbox:false` only when the caller
|
|
54
55
|
accepts unrestricted writes.
|
|
55
56
|
|
|
57
|
+
## Implementation owner
|
|
58
|
+
|
|
59
|
+
Keep one implementation conversation per unit through local proof, independent review,
|
|
60
|
+
review fixes and CI repairs. Its saved conversation outlives the active worker slot: launch
|
|
61
|
+
each worker normally, single or batch as the assignment needs, and record the receipt as
|
|
62
|
+
`implementationOwner` ([task-plan.md](task-plan.md#shape)). `retain_session` is optional
|
|
63
|
+
protection from later cleanup, not a precondition for continuing: a completed conversation is
|
|
64
|
+
resumed by its `runId` whether or not retention was requested, and the harness never asks for
|
|
65
|
+
it on resume. Request it only when this caller needs the conversation protected through a long
|
|
66
|
+
wait before cleanup, using the exact argument the launched tool advertises and never inventing
|
|
67
|
+
one for a call that lacks it. A parked owner has no live process and reserves no slot. The
|
|
68
|
+
coordinator still owns scheduling, plan writes, external CI, readiness and delivery;
|
|
69
|
+
reviewers use separate conversations.
|
|
70
|
+
|
|
71
|
+
`next-actions.mjs` supplies `continuation`, `workerKind`, and, for
|
|
72
|
+
`owner-resume`, `owner`. Resume that completed attempt with
|
|
73
|
+
`subagent_resume({id: owner.runId, prompt: <current brief>})`; record the new attempt ID
|
|
74
|
+
without changing the conversation ID. Selection follows [continuation versus new launch](task-plan.md#worker-selection-on-resume): omit model/effort for ordinary continuation. Use only
|
|
75
|
+
authorized, supported explicit overrides; when a required role-only fill cannot be expressed
|
|
76
|
+
by the resume API, replace from that fill with the retained evidence instead of creating a
|
|
77
|
+
pin from its resolved model ID. Clear `worker` when an attempt
|
|
78
|
+
ends, but keep `implementationOwner` while review or CI is pending. Keep all counters and
|
|
79
|
+
earlier results across attempts. `one-by-one` fixes findings sequentially in this owner.
|
|
80
|
+
|
|
81
|
+
A missing history/workspace, an unusable saved owner, or an owner that cannot continue
|
|
82
|
+
requires an explicit replacement reason and the retained checkpoint, failures and reproducer
|
|
83
|
+
references in the replacement brief. Try the saved conversation first: a `retentionReleased`
|
|
84
|
+
marker says only that optional protection lapsed, so it is not a replacement reason by itself
|
|
85
|
+
and the conversation it names usually still resumes. Report lost continuity; never label a
|
|
86
|
+
new conversation a resume. Existing recovery/stop rules still apply, and old plans without
|
|
87
|
+
an owner use the fresh-worker path. Do not install or update the harness during a run merely
|
|
88
|
+
to obtain continuation.
|
|
89
|
+
|
|
90
|
+
### Settle session retention
|
|
91
|
+
|
|
92
|
+
When this unit settles (merged, cancelled, deferred or handed back merge-ready), release any
|
|
93
|
+
retention still held for its `implementationOwner`, `reviewSession` and `retiredSessions` with
|
|
94
|
+
`subagent_release({id: <runId>})` before its normal workspace/resource cleanup. Only a
|
|
95
|
+
conversation launched with `retain_session` has anything to release, so a normal launch skips
|
|
96
|
+
this and a second release is not owed. Record the acknowledgement on that reference when it
|
|
97
|
+
was actually held; an unavailable or refused release stays explicit rather than being
|
|
98
|
+
recorded as done.
|
|
99
|
+
|
|
100
|
+
Reopening is the ordinary case: resume the saved conversation even when its protection was
|
|
101
|
+
released, because release stops protection and leaves the history in place. Start a fresh
|
|
102
|
+
replacement only when continuation is genuinely unavailable, and carry the original
|
|
103
|
+
checkpoint/findings/counters into it. The same rule covers a released unfinished review,
|
|
104
|
+
keeping its round and existing full-review fallback.
|
|
105
|
+
|
|
56
106
|
## Brief
|
|
57
107
|
|
|
58
108
|
Add to SKILL.md's brief list: whole-spec context and neighboring unit
|
|
@@ -83,8 +133,8 @@ handback step. Never edit `findings.json`.
|
|
|
83
133
|
|
|
84
134
|
## Per-unit loop
|
|
85
135
|
|
|
86
|
-
On `resume` (in flight, no worker, code not done),
|
|
87
|
-
|
|
136
|
+
On `resume` (in flight, no worker, code not done), continue the unit's
|
|
137
|
+
[implementation owner](#implementation-owner) in its kept workspace with the current brief.
|
|
88
138
|
|
|
89
139
|
1. **Implement:** change only the unit's scope; commit coherent progress.
|
|
90
140
|
2. **Validate:** prove the criteria with the repository's checks: the
|
|
@@ -107,8 +157,10 @@ implementer in its kept workspace with the brief and its findings entry.
|
|
|
107
157
|
step, error message and evidence path the worker observed. An exit code alone is
|
|
108
158
|
not a failure; keep the first failure separate from cleanup errors. Fix workers
|
|
109
159
|
write the same file. Return
|
|
110
|
-
|
|
111
|
-
|
|
160
|
+
only that result by default. When the issue or repository requires a checklist
|
|
161
|
+
or evidence document, produce it from actual results and link CI artifacts;
|
|
162
|
+
otherwise omit artifact hashes, inventories, screenshots and copied logs.
|
|
163
|
+
For a continuation, include the current checkpoint,
|
|
112
164
|
outcome and relevant prior-result links in the existing integration notes; do not
|
|
113
165
|
duplicate the full attempt history. After focused validation, the permitted push,
|
|
114
166
|
and the result, return without waiting for external CI or PR monitoring. Report
|
|
@@ -119,13 +171,30 @@ implementer in its kept workspace with the brief and its findings entry.
|
|
|
119
171
|
not prohibit long local tests or builds needed for the assigned validation.
|
|
120
172
|
|
|
121
173
|
After 2 fix cycles without progress, or a stuck or late worker, follow
|
|
122
|
-
[expire.md](expire.md).
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
174
|
+
[expire.md](expire.md). Apply the next response through the retained owner where its
|
|
175
|
+
completed conversation and required launch controls permit continuation. A replacement
|
|
176
|
+
uses the same workspace and the findings entry, failing command/output and `git diff <base>`;
|
|
177
|
+
record why it replaces the owner and retain its session reference as above. Give replacement
|
|
178
|
+
workers those local facts rather than copying the old transcript. Keep all progress counters.
|
|
126
179
|
|
|
127
180
|
## Proof
|
|
128
181
|
|
|
182
|
+
- **Implement the acceptance contract.** Read the issue's Acceptance Criteria and
|
|
183
|
+
the preflight mapping. Reuse sufficient tests and add missing assertions at the
|
|
184
|
+
lowest level that exercises the required behavior. Ensure CI selects and executes
|
|
185
|
+
them in the assigned lane. Return test/criterion references and pending CI or manual
|
|
186
|
+
obligations in the existing result; the coordinator qualifies acceptance.
|
|
187
|
+
- **Make diagnostic failures executable.** Reuse the existing test runner/assertions.
|
|
188
|
+
A standalone probe exits nonzero for an unexpected exception, failed assertion or
|
|
189
|
+
result outside the expected contract. A catch used to add context rethrows or sets
|
|
190
|
+
a nonzero exit; log capture and cleanup preserve that failure status. For shell
|
|
191
|
+
pipelines, preserve the tested command's failure, for example with `set -o pipefail`.
|
|
192
|
+
An expected-exception test passes only when it asserts the expected exception and
|
|
193
|
+
relevant contract details; no exception or the wrong exception fails. Assess the
|
|
194
|
+
process status under that contract, rather than having the model classify log text.
|
|
195
|
+
Coordination commands that exit 0 to return a structured verdict keep that protocol;
|
|
196
|
+
read their verdict as documented. Record the command, status and failing step in
|
|
197
|
+
the existing result/failure fields; no new harness or evidence document is needed.
|
|
129
198
|
- Prove `browser` and `emulator` levels with the existing end-to-end suite;
|
|
130
199
|
with none, the unit builds the journey its proof needs
|
|
131
200
|
([expire.md](expire.md#proof-stays-with-its-unit)).
|
|
@@ -165,12 +234,11 @@ transcript.
|
|
|
165
234
|
failure ([expire.md](expire.md#infrastructure-failures)): one bounded
|
|
166
235
|
investigation that fixes what the run can; a shared blocker issue only
|
|
167
236
|
when the cause stays unfound or its fix is human-only. Keep the rest moving.
|
|
168
|
-
- Keep `proof.physical` and
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
passed.
|
|
237
|
+
- Keep `proof.physical` and explicitly assigned later release acceptance pending
|
|
238
|
+
at their named gate and owning issue. Build the automated tests the current issue
|
|
239
|
+
requests even when CI runs them as a campaign. If the current issue requests the
|
|
240
|
+
campaign's result, test implementation alone is not completion. Report manual or
|
|
241
|
+
unavailable checks as pending, never passed.
|
|
174
242
|
|
|
175
243
|
## Scheduling
|
|
176
244
|
|
|
@@ -323,11 +391,15 @@ parent another child holds.
|
|
|
323
391
|
line's PRs bottom first (`--base <targetBranch>` when it is not the default
|
|
324
392
|
branch); with the Mergify stack hooks installed, push with the `mergify`
|
|
325
393
|
skill's `stack-push.mjs` instead. Keep the body to one short record:
|
|
326
|
-
|
|
394
|
+
the issue link, the behavior delivered, the checks run with their
|
|
327
395
|
results, and any outstanding blocker or pending obligation as an issue
|
|
328
396
|
link; add the parent PR in `stack` and `chain` mode. CI posts its own run link; cite
|
|
329
397
|
CI-generated artifacts where provenance is required, never hashes or
|
|
330
398
|
logs copied by hand.
|
|
399
|
+
Use `Closes #<issue>` only when merging this PR will satisfy that issue's own
|
|
400
|
+
acceptance contract. With outstanding manual or post-merge acceptance, use
|
|
401
|
+
`Refs #<issue>` in both the PR body and commit message so merging automation
|
|
402
|
+
does not close the original issue. The coordinator closes it after acceptance.
|
|
331
403
|
3. Record the PR URL and `headSha`; once the code is done, start review while CI runs. Mark the
|
|
332
404
|
PR ready only when review passes and CI is green on the same HEAD
|
|
333
405
|
([ci.md](ci.md)).
|
|
@@ -155,6 +155,12 @@ Example: epic #12's #20 (signed release and go/no-go) and #21
|
|
|
155
155
|
(physical acceptance) stay out of the plan; the run ends when its code units
|
|
156
156
|
land instead of waiting on them.
|
|
157
157
|
|
|
158
|
+
An automated browser or emulator acceptance campaign is implementable work, including
|
|
159
|
+
when it proves already-merged features. Include its tests, CI execution and requested
|
|
160
|
+
results in scope. For mixed automated/manual criteria, keep the automation unit and
|
|
161
|
+
record the human-only remainder with its owner; report the original issue incomplete
|
|
162
|
+
until that remainder passes. Exclusion records a pending obligation, not acceptance.
|
|
163
|
+
|
|
158
164
|
## 4. Assess the plan
|
|
159
165
|
|
|
160
166
|
Follow [exploration.md](exploration.md#choose-the-owner) on the provisional
|
|
@@ -8,6 +8,13 @@ restate its rules in the brief beyond the two below.
|
|
|
8
8
|
|
|
9
9
|
## Brief
|
|
10
10
|
|
|
11
|
+
- Start a fresh reviewer conversation after each completed review round; the existing
|
|
12
|
+
scope script still chooses delta/full/skip, so freshness adds no review round or test
|
|
13
|
+
campaign. Give it retained records and code evidence as claims to verify, not an
|
|
14
|
+
implementer transcript. Launch it normally and keep its attempt reference as
|
|
15
|
+
`reviewSession`, separate from `implementationOwner`, so an unfinished same-HEAD round
|
|
16
|
+
can continue in that saved conversation; `retain_session` is optional protection, not
|
|
17
|
+
what makes it continuable. A round after a completed review is a fresh conversation.
|
|
11
18
|
- Reviewer model: a different id from the implementer's
|
|
12
19
|
([preflight.md](preflight.md#1-model-slots)). Launch the reviewer with the
|
|
13
20
|
plan's recorded reviewer fill — its `launch`, `instructions`, `selection` and
|
|
@@ -119,9 +126,23 @@ the unfinished work rather than repeating the whole assignment; preserve complet
|
|
|
119
126
|
valid passes. A wrong HEAD or invalid scope must be corrected, not carried as
|
|
120
127
|
completed proof.
|
|
121
128
|
|
|
129
|
+
On Pi, use `subagent_resume({id: reviewSession.runId, prompt: <unfinished work>})`
|
|
130
|
+
when that attempt completed as a process and its saved conversation is available;
|
|
131
|
+
update only its attempt reference, keeping this review's round, HEAD and counter.
|
|
132
|
+
A killed/lost attempt or an unavailable continuation follows replacement below. Once the
|
|
133
|
+
review record is complete, the next required review is a fresh conversation. Clear an
|
|
134
|
+
unpinned `reviewSession` directly. If it holds retention, release that protection before
|
|
135
|
+
clearing; on failure, save its exact reference and error in `retiredSessions` before clearing
|
|
136
|
+
so settlement can retry while the fresh review proceeds. Never use a reviewer as the
|
|
137
|
+
implementation owner.
|
|
138
|
+
|
|
122
139
|
If that reviewer cannot continue, state why replacement is necessary, which
|
|
123
140
|
retained evidence the replacement reads, and what it redoes before launching it.
|
|
124
|
-
|
|
141
|
+
A `retentionReleased` marker on the old reference is not a reason by itself: released
|
|
142
|
+
protection leaves the saved conversation in place, so try it again before replacing it.
|
|
143
|
+
While an old reference is still worth resuming or still needs release, keep it as an entry
|
|
144
|
+
in `retiredSessions` ([task-plan.md](task-plan.md#shape)) rather than dropping it; a later
|
|
145
|
+
release of the new session does not release the old conversation. The replacement reviews every pass with `tier: "full"`. Keep the round number,
|
|
125
146
|
`fixRound`, unit `attempt` and earlier round files; incompleteness neither resets
|
|
126
147
|
history nor counts as progress. Launch the replacement from the same recorded
|
|
127
148
|
reviewer fill, in the same structured launch arguments as [the brief](#brief)
|
|
@@ -221,10 +242,10 @@ the PR author; `github-reviews.mjs` reads them and decides what blocks.
|
|
|
221
242
|
|
|
222
243
|
- **P0/P1** block (`better-review`'s severity table defines them): fix on the
|
|
223
244
|
unit branch, validate, review again.
|
|
224
|
-
- **P2** never blocks
|
|
225
|
-
|
|
226
|
-
[
|
|
227
|
-
|
|
245
|
+
- **P2** never blocks or starts a blocking-fix round by severity alone. Route each
|
|
246
|
+
entry by the [dropbox questions](dropbox.md#route-every-problem); explicitly selected
|
|
247
|
+
[supporting P2 work](dropbox.md#supporting-p2-work) may be fixed in the unit.
|
|
248
|
+
Keep unrelated P2 in the dropbox; preserve the severity and review verdict.
|
|
228
249
|
- **P3** is skipped: no dropbox entry, no issue, no round. A P3 that still
|
|
229
250
|
reaches the result file (a posted result, an older reviewer) is ignored.
|
|
230
251
|
- Dispose of every P0-P2 finding: `resolved` on the reviewed HEAD, or `deferred`
|
|
@@ -232,13 +253,24 @@ the PR author; `github-reviews.mjs` reads them and decides what blocks.
|
|
|
232
253
|
|
|
233
254
|
## Fix loop
|
|
234
255
|
|
|
235
|
-
Give the
|
|
256
|
+
Give the [implementation owner](implementation.md#implementation-owner) the `fixBrief`
|
|
257
|
+
and act on its `action`. Keep the generated `reproducers` intact: their source rounds,
|
|
258
|
+
HEADs and original triggers identify earlier witnesses, not passing results on this
|
|
259
|
+
candidate. Read the retained cases and run against the candidate workspace at their
|
|
260
|
+
relative `cwd`; preserve their input/state and expected contract when adapting a checkout
|
|
261
|
+
path. `reproducerGaps` identifies malformed historical metadata without supplying a runnable
|
|
262
|
+
command: inspect the original record, establish a valid candidate-local case, and report any
|
|
263
|
+
remaining gap without rewriting history. Findings without an executable witness retain their
|
|
264
|
+
existing requirement evidence.
|
|
265
|
+
Add only supporting P2
|
|
266
|
+
explicitly selected under [dropbox routing](dropbox.md#supporting-p2-work), with the
|
|
267
|
+
recorded criterion and reason; the generated blocker list remains P0/P1 only.
|
|
236
268
|
|
|
237
269
|
| `action` | When | Fix worker |
|
|
238
270
|
|---|---|---|
|
|
239
271
|
| `fix` | first blocking round | Make the smallest complete fix for each finding's `rule` and close every applicable row in its `paths`; do not require centralization. |
|
|
240
272
|
| `test-first` | a P0/P1 survived the last fix | On the escalation model, for each `recurring` finding, before changing code: name the broken rule and its concrete open paths, reuse sufficient tests and add focused cases for missing proof, and see the open cases fail. Fix the rule until the applicable cases pass. A table-driven test may cover several paths; no new architecture is required. |
|
|
241
|
-
| `one-by-one` | the last fix introduced new P0/P1 |
|
|
273
|
+
| `one-by-one` | the last fix introduced new P0/P1 | The implementation owner fixes findings sequentially in its continued session, each with the smallest change that closes every row of its `paths` at the rule's owner. |
|
|
242
274
|
|
|
243
275
|
**Fix the broken rule on every applicable path, without requiring a refactor.**
|
|
244
276
|
A shared owner is useful when it makes the fix smaller and clearer; it is not a
|
|
@@ -251,8 +283,9 @@ different-person sign-in cases without inventing unrelated event combinations.
|
|
|
251
283
|
Have each fix worker run the checks its change affects and return the
|
|
252
284
|
commands and results; push, and let CI run. Then pick the new HEAD's
|
|
253
285
|
[tier](#tier-then-verdict): a fix is delta or full, never skip, so spawn the
|
|
254
|
-
next round's reviewer with the previous result file and the `fixBrief
|
|
255
|
-
|
|
286
|
+
next round's fresh reviewer with the previous result file and the `fixBrief`, naming any
|
|
287
|
+
supporting P2 changed in that patch. Deferred P2 return as findings only if now rated
|
|
288
|
+
P1; evaluate changed supporting work as part of the fix diff. Record `reviewedHead` only on a `pass` verdict. A later
|
|
256
289
|
commit, a CI fix included, needs its own `pass`: a [carry](#carry-after-a-pass) when this round passed.
|
|
257
290
|
|
|
258
291
|
A `decide` verdict pauses the fix loop on a question: a spec finding came back after a
|
|
@@ -92,6 +92,9 @@ Fleet `status`: `pending`, `in-flight`, `succeeded`, `failed`,
|
|
|
92
92
|
| `attempt`, `retries`, `escalations` | Attempt number; cycle counts; escalation-model attempts |
|
|
93
93
|
| `worker`, `workerSince`, `clock` | Slot identifier of any worker running for the unit (implementer, reviewer, fix), `null` when none; when that worker started (ISO time, for [stuck-worker checks](expire.md#waits)); attempt and cumulative wall time |
|
|
94
94
|
| `workerKind` | `implement`, `fix`, or `review`, set with `worker`; the [live-capacity scheduler](implementation.md#live-capacity) (`next-actions.mjs`) prints `worker-kind` for any other kind or none. An infrastructure diagnosis or a test-first worker is a `fix` worker. The explorer is the `explore` fleet row's worker, never a unit's; a merge-queue watcher is not a worker |
|
|
95
|
+
| `implementationOwner` | Optional saved implementation conversation: `{runId, sessionId?, cwd?, kind?, retentionReleased?}`. `runId` is its latest attempt; `kind` is `implement` (default) or `fix`, never `review`; `cwd`, when recorded, matches `workspace`. Save spawn/resume receipts here, independently of `worker`; a parked owner occupies no slot. The scheduler emits `owner-resume` for a usable owner or an explicit `ownerUnavailable` reason for replacement; `retentionReleased: true` only records that optional cleanup protection lapsed, so it never makes the owner unusable and the saved conversation stays the resume target while the harness holds it. Old plans remain readable. See [ownership](implementation.md#implementation-owner). |
|
|
96
|
+
| `reviewSession` | Optional `{runId, sessionId?, head, round, retentionReleased?}` for the current unfinished review. Keep it only for same-HEAD/same-scope continuation, and clear it when that round completes so the next completed-review round starts fresh. A `retentionReleased` marker is history, not a reason to replace: try the same round's saved conversation while it exists. It is never an implementation owner or a slot reservation. |
|
|
97
|
+
| `retiredSessions` | Optional references kept beside a replaced implementation owner or unfinished reviewer: `{runId, sessionId?, kind, retentionReleased?, reason?}`. Only for a conversation still worth resuming or still holding protection that must be released later; an ordinary resume or replacement needs no entry here, and a later release of the new session does not release an older conversation. |
|
|
95
98
|
| `estimatedLines` | Preflight assessment estimate |
|
|
96
99
|
| `sizeCheck` | Measures, `verdict` (the size check's per-unit verdict; `stop` means held), scope and proof verdicts, split or verification path; `runWhole: true` once a recommendation to run a held unit whole is accepted |
|
|
97
100
|
| `note` | SHA, expiry reason code, failure and response, or blocker; for active debugging, the immediate acceptance checkpoint, baseline/result link and next distinguishing test ([delivery progress](../SKILL.md#delivery-progress)) |
|
|
@@ -142,7 +145,10 @@ what a later launch of the slot repeats; a pair of model ids is not.
|
|
|
142
145
|
(`open`, `resolved`, `deferred`), `resolvedHead`, `dropboxId` or `followUpUrl`. Copy them
|
|
143
146
|
from `review-verdict.mjs`: on `fix` its `fix` list as `open`; on `pass` its
|
|
144
147
|
`deferred` list as `deferred` and every open P0/P1 as `resolved`. Never defer
|
|
145
|
-
a P0/P1.
|
|
148
|
+
a P0/P1. For selected [supporting P2 work](dropbox.md#supporting-p2-work), record the
|
|
149
|
+
selection in `decisions`; after verification, mark its plan finding `resolved` with
|
|
150
|
+
`resolvedHead` and put the check/result in the unit's existing note/result. Preserve
|
|
151
|
+
the original review and retire its pending dropbox entry as documented there.
|
|
146
152
|
|
|
147
153
|
`decisions` entry: id, timestamp, the human's words, what it changes,
|
|
148
154
|
`supersedes` when it replaces one, and `answers`: the run-relative file of each
|
|
@@ -153,7 +159,16 @@ recommendation's reason; its human's words are the invocation.
|
|
|
153
159
|
|
|
154
160
|
### Worker selection on resume
|
|
155
161
|
|
|
156
|
-
|
|
162
|
+
Distinguish resuming the workflow from continuing one saved conversation. For an ordinary
|
|
163
|
+
same-conversation `subagent_resume`, pass its attempt ID and new feedback, omitting role,
|
|
164
|
+
model and effort so the harness preserves its recorded recipe. The resume tool's schema
|
|
165
|
+
may differ from spawn: do not replay a role selector it cannot accept. A required changed
|
|
166
|
+
selection can continue only through controls both explicitly authorized and advertised by
|
|
167
|
+
that API; otherwise record why replacement is needed and launch fresh from the required
|
|
168
|
+
fill with its retained evidence. A resolved role default alone never authorizes a model pin.
|
|
169
|
+
Keep the original model/effort as run evidence and preserve all review/progress counters.
|
|
170
|
+
|
|
171
|
+
**A fresh or replacement launch repeats the saved fill, never a reconstruction of it.** On Pi the
|
|
157
172
|
launch authority stays the live role resolver ([shared-modules.md](shared-modules.md#model-slots)):
|
|
158
173
|
its `launch` is `{ role }` unless an explicit task override or this host's own
|
|
159
174
|
preference asked for a model or effort, and a resolved role default is never
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
// --line, any change to the file counts)? Prints { inDiff, do }: `inDiff` true means fix it in this unit.
|
|
21
21
|
// add: append one entry; the coordinator is the only writer. An implementer entry inside the
|
|
22
22
|
// unit's diff is refused (`refused` in the output) because the unit caused it. A review entry inside the diff
|
|
23
|
-
// is kept and marked `inDiff`: the
|
|
23
|
+
// is kept and marked `inDiff`: the coordinator selects supporting work by the delivery
|
|
24
|
+
// criterion, not location or severity alone. The same location, kind and
|
|
24
25
|
// summary (or, for a review, the same unit and finding id) is one entry. `--decision` records
|
|
25
26
|
// a question for the human: never posted, listed in the handback. Prints { id, duplicate, inDiff }.
|
|
26
27
|
// from-review: add every P2 in a review result that has no followUpUrl or dropboxId, and
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// next-actions.mjs --run <run dir> [--capacity-json '<snapshot>' | --slots N] [--quiet-minutes 45] [--resume] [--sync] [--json] [--peek] [--mode full|narrow|off] [--rules] [--exit-due]
|
|
6
6
|
//
|
|
7
7
|
// Reads <run>/task-plan.json (units with status, stage, worker, workerSince, workspace,
|
|
8
|
-
// dependsOn, pr, headSha, reviewedHead) and <run>/reviews/<unit>-r<N>.json (+ .fix.json; its head, complete, and
|
|
8
|
+
// implementationOwner, dependsOn, pr, headSha, reviewedHead) and <run>/reviews/<unit>-r<N>.json (+ .fix.json; its head, complete, and
|
|
9
9
|
// external.awaiting, and whether it holds a P0/P1). A unit with `worker`
|
|
10
10
|
// set is busy. Actions, in priority order:
|
|
11
11
|
//
|
|
@@ -75,6 +75,28 @@
|
|
|
75
75
|
// fetch failed, it prints `sync` instead. A run never finishes on a tree it
|
|
76
76
|
// could not read
|
|
77
77
|
//
|
|
78
|
+
// A unit's optional `implementationOwner` is the retained implement-or-fix conversation that outlives
|
|
79
|
+
// the worker slot, recorded from the harness as { runId, sessionId?, cwd?, kind }: `runId` is the
|
|
80
|
+
// attempt to resume, `kind` ("implement" or "fix", default "implement") says which worker owned it,
|
|
81
|
+
// and `sessionId` and `cwd` (the unit's workspace) identify that conversation and its tree. It is
|
|
82
|
+
// independent of `worker`, which records whichever worker is active now, so the owner survives the
|
|
83
|
+
// review phase and a later fix continues the same conversation instead of starting over.
|
|
84
|
+
//
|
|
85
|
+
// Every action that spawns a worker therefore carries the structured continuation the coordinator
|
|
86
|
+
// launches, beside its name: `continuation: "fresh"` for a first start or a new worker, launched
|
|
87
|
+
// normally, with no retention argument (a saved conversation stays continuable by its id;
|
|
88
|
+
// `retain_session: true` is optional protection a caller adds only for a long wait before cleanup).
|
|
89
|
+
// Implement and fix sessions become the unit's owner; `continuation: "owner-resume"` with
|
|
90
|
+
// `owner: { runId, kind, sessionId?, cwd? }` for a fix, a restack, or an unfinished implementation
|
|
91
|
+
// whose owner is still usable (subagent_resume({ id: owner.runId, ... }) returns a new attempt on
|
|
92
|
+
// the same conversation). `workerKind` names the slot the action launches. A review round is
|
|
93
|
+
// always a fresh reviewer: a finished review
|
|
94
|
+
// is never resumed, and a review recorded as an owner is not usable. An owner without a `runId`,
|
|
95
|
+
// with a `kind` outside implement and fix, or whose `cwd` is not the unit's workspace is not usable,
|
|
96
|
+
// so a fresh action reports ownerUnavailable rather than claiming continuity. A `retentionReleased`
|
|
97
|
+
// marker records that optional protection lapsed, so it never makes an owner unusable. A completed
|
|
98
|
+
// worker holds no slot: the owner is a reference, never an occupancy.
|
|
99
|
+
//
|
|
78
100
|
// Text output ends with one short line pointing to the skill path. With --resume or --rules it
|
|
79
101
|
// ends with RULES instead, the rules a compaction summary most often drops (in narrow and off
|
|
80
102
|
// modes, a one-line rules reminder for that mode).
|
|
@@ -120,6 +142,13 @@ import { BLOCKING } from './review-verdict.mjs'
|
|
|
120
142
|
// No skill-owned concurrency default: the caller supplies fresh host capacity.
|
|
121
143
|
// The only workers a unit runs (SKILL.md, Your role). A watcher is not a worker.
|
|
122
144
|
export const WORKER_KINDS = Object.freeze(['implement', 'fix', 'review'])
|
|
145
|
+
// The worker each worker-spawning action launches, in the plan's `workerKind` vocabulary. These are
|
|
146
|
+
// the actions that carry a structured continuation (`continuation`, `workerKind`, `owner`) for the
|
|
147
|
+
// coordinator.
|
|
148
|
+
export const WORKER_ACTION_KINDS = Object.freeze({ 'fill-slot': 'implement', resume: 'implement', 'start-fix': 'fix', restack: 'fix', 'start-review': 'review' })
|
|
149
|
+
// The kinds that may own a unit's implementation. A review is never resumed for the next round, so
|
|
150
|
+
// it is not one.
|
|
151
|
+
export const IMPLEMENTATION_OWNER_KINDS = Object.freeze(['implement', 'fix'])
|
|
123
152
|
export const SKILL_PATH = join(dirname(dirname(fileURLToPath(import.meta.url))), 'SKILL.md')
|
|
124
153
|
// Printed after every text call, and by rehydrate.mjs after a compaction.
|
|
125
154
|
export const RULES = 'Per unit, run only implement, fix, and review workers, recorded as workerKind: no planning, architecture, product, or setup workers. You own preflight findings; use an optional explorer on the fleet explore row only for concrete planning unknowns (exploration.md). You coordinate: never edit product code. In full mode take every step from next-actions.mjs; in every mode the run ends only when it reports done. A HEAD after a passing review is carried; any other new HEAD takes the tier rereview-scope.mjs picks, never yours. Decide before you defer: a question a worker or review raises is answered and recorded, then the unit continues; retrying or deferring never answers it. Never block on the human: take the automatic answer and keep going. Fix the root cause, never only its symptom. Before each scheduling pass, refresh subagent_settings and pass its snapshot with --capacity-json; do not reuse a run-start budget or subtract workflow workers again.'
|
|
@@ -176,6 +205,25 @@ export function normalizeCapacity(input) {
|
|
|
176
205
|
const hasWorker = (u) => u.worker !== undefined && u.worker !== null && u.worker !== ''
|
|
177
206
|
const codeDone = (u) => u.stage === 'done'
|
|
178
207
|
|
|
208
|
+
// A unit's usable implementation owner, normalized to { runId, kind, sessionId?, cwd? }, or null.
|
|
209
|
+
// Usable means it names the attempt to resume (`runId`), its `kind` is an implement or fix worker
|
|
210
|
+
// (never a reviewer: a finished review is never resumed for the next round), and, when both are
|
|
211
|
+
// recorded, its `cwd` is the unit's workspace (a conversation cannot be resumed in another tree).
|
|
212
|
+
// A plan that records nothing here keeps today's behavior: a fresh worker.
|
|
213
|
+
export function implementationOwnerOf(unit) {
|
|
214
|
+
const saved = unit?.implementationOwner
|
|
215
|
+
if (!saved || typeof saved !== 'object' || Array.isArray(saved)) return null
|
|
216
|
+
if (saved.cwd !== undefined && saved.cwd !== null && typeof saved.cwd !== 'string') return null
|
|
217
|
+
const runId = typeof saved.runId === 'string' ? saved.runId.trim() : ''
|
|
218
|
+
const kind = saved.kind === undefined ? 'implement' : saved.kind
|
|
219
|
+
if (!runId || !IMPLEMENTATION_OWNER_KINDS.includes(kind)) return null
|
|
220
|
+
const cwd = typeof saved.cwd === 'string' ? saved.cwd.trim() : ''
|
|
221
|
+
const workspace = typeof unit.workspace === 'string' ? unit.workspace.trim() : ''
|
|
222
|
+
if (cwd && workspace && cwd !== workspace) return null
|
|
223
|
+
const sessionId = typeof saved.sessionId === 'string' ? saved.sessionId.trim() : ''
|
|
224
|
+
return { runId, kind, ...(sessionId ? { sessionId } : {}), ...(cwd ? { cwd } : {}) }
|
|
225
|
+
}
|
|
226
|
+
|
|
179
227
|
export function workspaceCleanupSettled(unit) {
|
|
180
228
|
const result = unit.lifecycle?.cleanup
|
|
181
229
|
return typeof unit.headSha === 'string' && unit.headSha.length > 0 && result?.headSha === unit.headSha &&
|
|
@@ -338,6 +386,18 @@ export function nextActions(plan, reviews = {}, capacity, { resume = false, now,
|
|
|
338
386
|
}
|
|
339
387
|
const actions = []
|
|
340
388
|
const add = (action, u, detail) => actions.push({ action, unit: String(u.id), detail })
|
|
389
|
+
// Keep owners through delivery and their saved conversations through later repair.
|
|
390
|
+
// A new review round starts fresh even when the preceding session remains available.
|
|
391
|
+
const continuationFor = (u, workerKind) => {
|
|
392
|
+
const owner = workerKind === 'review' ? null : implementationOwnerOf(u)
|
|
393
|
+
if (owner) return { continuation: 'owner-resume', workerKind, owner }
|
|
394
|
+
const ownerUnavailable = workerKind !== 'review' && u.implementationOwner != null
|
|
395
|
+
? 'Saved implementationOwner is malformed, names another role, or does not match the unit workspace; record replacement and retain its evidence.' : undefined
|
|
396
|
+
return { continuation: 'fresh', workerKind, ...(ownerUnavailable ? { ownerUnavailable } : {}) }
|
|
397
|
+
}
|
|
398
|
+
// Appended to an owner-resumed action: the work is the same, so the action keeps its name; only
|
|
399
|
+
// the launch changes, and it is a resume rather than a second worker for the unit.
|
|
400
|
+
const ownerNote = (id, owner) => `; launch it as a resume of unit ${id}'s implementation owner: subagent_resume({ id: ${JSON.stringify(owner.runId)}, prompt: <that brief or task> }) returns a new attempt on the same conversation${owner.sessionId ? ` (session ${owner.sessionId})` : ''}${owner.cwd ? ` in ${owner.cwd}` : ''}; record the returned id as implementationOwner.runId and keep the owner through review`
|
|
341
401
|
const managed = (u) => plan.workspaceLifecycle === 1 || u.lifecycle?.version === 1
|
|
342
402
|
for (const u of units) {
|
|
343
403
|
if (TERMINAL.has(u.status) && hasWorker(u) && (u.status !== 'blocked' || reasonOk(u))) {
|
|
@@ -346,7 +406,7 @@ export function nextActions(plan, reviews = {}, capacity, { resume = false, now,
|
|
|
346
406
|
}
|
|
347
407
|
if (!managed(u) || u.status !== 'succeeded' || !u.workspace) continue
|
|
348
408
|
if (!hasWorker(u) && !workspaceCleanupSettled(u)) {
|
|
349
|
-
add('cleanup-workspace', u, `unit ${u.id} landed: record lifecycle.cleanup with state applying and headSha ${u.headSha ?? '(record the delivered HEAD first)'}, then run cleanup-runs.mjs cleanup --run <run> --unit ${u.id} --revision <saved planRevision> --apply --json; inspect and persist the returned lifecycle. A refusal is retained with its reason, never complete; do this before finish and alongside independent units`)
|
|
409
|
+
add('cleanup-workspace', u, `unit ${u.id} landed: release any retain_session protection it still holds and record that acknowledgement (implementation.md#settle-session-retention), then record lifecycle.cleanup with state applying and headSha ${u.headSha ?? '(record the delivered HEAD first)'}, then run cleanup-runs.mjs cleanup --run <run> --unit ${u.id} --revision <saved planRevision> --apply --json; inspect and persist the returned lifecycle. A refusal is retained with its reason, never complete; do this before finish and alongside independent units`)
|
|
350
410
|
}
|
|
351
411
|
}
|
|
352
412
|
const chainReady = []
|
|
@@ -637,6 +697,16 @@ export function nextActions(plan, reviews = {}, capacity, { resume = false, now,
|
|
|
637
697
|
// One restack per unit per pass: the first reason found; the next pass sees the new HEAD.
|
|
638
698
|
const restacked = new Set()
|
|
639
699
|
for (let i = 0; i < actions.length; i++) if (actions[i].action === 'restack') { if (restacked.has(actions[i].unit)) actions.splice(i--, 1); else restacked.add(actions[i].unit) }
|
|
700
|
+
// One place attaches each worker action's continuation, so no site can spawn a worker without
|
|
701
|
+
// saying whether it starts fresh or resumes the unit's owner.
|
|
702
|
+
for (const a of actions) {
|
|
703
|
+
const workerKind = WORKER_ACTION_KINDS[a.action]
|
|
704
|
+
if (!workerKind) continue
|
|
705
|
+
const continuation = continuationFor(byId.get(a.unit), workerKind)
|
|
706
|
+
Object.assign(a, continuation)
|
|
707
|
+
if (continuation.owner) a.detail += ownerNote(a.unit, continuation.owner)
|
|
708
|
+
if (continuation.ownerUnavailable) a.detail += `; ${continuation.ownerUnavailable}`
|
|
709
|
+
}
|
|
640
710
|
const busy = units.filter(hasWorker).length
|
|
641
711
|
// The Harness already counted our workers along with other work and reservations.
|
|
642
712
|
// Without a capacity source every worker-needing action is due: the harness spawn limit governs.
|
|
@@ -652,9 +722,14 @@ export function nextActions(plan, reviews = {}, capacity, { resume = false, now,
|
|
|
652
722
|
const reviewReady = a.action !== 'start-review' || u.lifecycle?.resources?.some((r) => r.role === 'review' && r.state === 'ready')
|
|
653
723
|
const needsPreparation = managed(u) && (u.lifecycle?.workspace?.state !== 'ready' || !reviewReady)
|
|
654
724
|
const removalPending = managed(u) && u.lifecycle?.cleanup?.state === 'applying'
|
|
655
|
-
|
|
725
|
+
// A gating action keeps the worker action's continuation, so the launch stays explicit after
|
|
726
|
+
// the workspace is prepared or its cleanup is resolved.
|
|
727
|
+
const launch = { unit: a.unit, continuation: a.continuation, workerKind: a.workerKind,
|
|
728
|
+
...(a.owner ? { owner: a.owner } : {}),
|
|
729
|
+
...(a.ownerUnavailable ? { ownerUnavailable: a.ownerUnavailable } : {}) }
|
|
730
|
+
const admitted = removalPending ? { action: 'recover-workspace', ...launch,
|
|
656
731
|
detail: `before ${a.action} for unit ${a.unit}, resolve its applying cleanup: inspect cleanup-runs.mjs recover --repo <repo-root> --json, collect any running cleanup operation's terminal result, and verify its receipt and every owned resource before updating lifecycle. Never admit a worker while removal of its environment is unresolved` }
|
|
657
|
-
: needsPreparation ? { action: 'prepare-workspace',
|
|
732
|
+
: needsPreparation ? { action: 'prepare-workspace', ...launch,
|
|
658
733
|
detail: `before ${a.action} for unit ${a.unit}, record its exact workspace, branch, base SHA and lifecycle preparation intent${reviewReady ? '' : ', including its one review clone'}; run cleanup-runs.mjs prepare --run <run> --unit ${a.unit} --revision <saved planRevision> --apply --json and persist its returned ready identity. Reuse an existing matching workspace; never reset, replace or create another one on retry` } : a
|
|
659
734
|
due.push(unlimited ? { ...admitted, detail: `${admitted.detail} [needs a free worker slot]` } : admitted)
|
|
660
735
|
free--
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
// codes below for one release (DEPRECATED: read `verdict` instead). Old exit codes:
|
|
29
29
|
// 0 pass: no P0/P1 on --head (or a confirmed skip); record `reviewedHead`; ready
|
|
30
30
|
// once CI is green
|
|
31
|
-
// 1 fix: the printed `fixBrief` file holds the P0/P1
|
|
32
|
-
//
|
|
31
|
+
// 1 fix: the printed `fixBrief` file holds the P0/P1 blocker list. The coordinator
|
|
32
|
+
// may also name supporting P2 work selected in the plan. `action` says how to fix, from a
|
|
33
33
|
// comparison with the previous round's file (`<unit>-r<N-1>.json` beside
|
|
34
34
|
// it, or --prior):
|
|
35
35
|
// fix first blocking round: fix each finding's rule across its
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
// focused cases for missing proof, see the open cases fail,
|
|
40
40
|
// then fix until all pass, with the
|
|
41
41
|
// escalation model
|
|
42
|
-
// one-by-one only new P0/P1, introduced by the last fix:
|
|
43
|
-
//
|
|
42
|
+
// one-by-one only new P0/P1, introduced by the last fix: the owner fixes
|
|
43
|
+
// findings in turn, the smallest change that closes
|
|
44
44
|
// every row across the applicable paths
|
|
45
45
|
// 5 wait: no P0/P1 is open, but a GitHub reviewer whose changes request a fix brief
|
|
46
46
|
// already carried has not re-reviewed (`awaiting`, from github-reviews.mjs merge);
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
// on a skip record too, which carries only the reviewer's approval.
|
|
67
67
|
|
|
68
68
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
69
|
-
import { basename, dirname, join } from 'node:path'
|
|
69
|
+
import { basename, dirname, isAbsolute, join } from 'node:path'
|
|
70
70
|
|
|
71
71
|
import { execFileSync } from 'node:child_process'
|
|
72
72
|
|
|
@@ -101,10 +101,26 @@ const isCarry = (review) => reviewTier(review) === 'skip' && review?.carry === A
|
|
|
101
101
|
// A blocking finding names its rule and every path that must hold it, at least one open; a P0/P1
|
|
102
102
|
// with every row closed is resolved and belongs out of the file. Checked on the current review
|
|
103
103
|
// only, so earlier records written without the fields stay readable as priors.
|
|
104
|
+
function reproducerProblems(repro, label) {
|
|
105
|
+
const problems = []
|
|
106
|
+
if (!isRecord(repro) || !isText(repro.command) || !isText(repro.expected)) {
|
|
107
|
+
problems.push(`${label}.reproducer needs a command and expected result`)
|
|
108
|
+
}
|
|
109
|
+
if (!isText(repro?.cwd) || isAbsolute(repro.cwd) || repro.cwd.startsWith('\\') || /^[A-Za-z]:/.test(repro.cwd)
|
|
110
|
+
|| repro.cwd.replaceAll('\\', '/').split('/').includes('..')) {
|
|
111
|
+
problems.push(`${label}.reproducer.cwd must be relative to the candidate workspace, without parent traversal`)
|
|
112
|
+
}
|
|
113
|
+
if (isRecord(repro) && Object.hasOwn(repro, 'evidence') && !isText(repro.evidence)) {
|
|
114
|
+
problems.push(`${label}.reproducer.evidence must name retained evidence when present`)
|
|
115
|
+
}
|
|
116
|
+
return problems
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
function blockingShapeProblems(f, label) {
|
|
105
120
|
const problems = []
|
|
106
121
|
if (!isText(f?.trigger)) problems.push(`${label} (P0/P1) has no trigger`)
|
|
107
122
|
if (!isText(f?.rule)) problems.push(`${label} (P0/P1) has no rule`)
|
|
123
|
+
if (Object.hasOwn(f, 'reproducer')) problems.push(...reproducerProblems(f.reproducer, label))
|
|
108
124
|
if (!Array.isArray(f?.paths) || f.paths.length === 0) return [...problems, `${label} (P0/P1) has no paths rows`]
|
|
109
125
|
for (const [j, row] of f.paths.entries()) {
|
|
110
126
|
if (!isText(row?.path)) problems.push(`${label}.paths[${j}] has no path`)
|
|
@@ -213,6 +229,35 @@ export function roundsWithoutNewLow(review, history = []) {
|
|
|
213
229
|
return since
|
|
214
230
|
}
|
|
215
231
|
|
|
232
|
+
// Preserve concrete witnesses for the still-broken rule, including earlier triggers.
|
|
233
|
+
// These are source references, not claims that the candidate has passed them.
|
|
234
|
+
function fixReproducers(blocking, records) {
|
|
235
|
+
const ids = new Set(blocking.map((finding) => finding.id))
|
|
236
|
+
const lastPass = records.findLastIndex((record) => isRecord(record)
|
|
237
|
+
&& Array.isArray(record.findings) && reviewCompletionProblems(record).length === 0
|
|
238
|
+
&& !record.findings.some((finding) => BLOCKING.includes(finding?.severity)))
|
|
239
|
+
const seen = new Set()
|
|
240
|
+
const out = []
|
|
241
|
+
const gaps = []
|
|
242
|
+
for (const record of records.slice(lastPass + 1)) {
|
|
243
|
+
for (const finding of Array.isArray(record?.findings) ? record.findings : []) {
|
|
244
|
+
if (!ids.has(finding?.id) || !BLOCKING.includes(finding.severity) || !Object.hasOwn(finding, 'reproducer')) continue
|
|
245
|
+
const key = JSON.stringify([finding.id, finding.trigger, finding.reproducer])
|
|
246
|
+
if (seen.has(key)) continue
|
|
247
|
+
seen.add(key)
|
|
248
|
+
const problems = reproducerProblems(finding.reproducer, finding.id)
|
|
249
|
+
if (problems.length) {
|
|
250
|
+
gaps.push({ finding: finding.id, round: record.round, head: record.head,
|
|
251
|
+
trigger: finding.trigger, reason: problems.join('; ') })
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
out.push({ finding: finding.id, round: record.round, head: record.head,
|
|
255
|
+
trigger: finding.trigger, reproducer: structuredClone(finding.reproducer) })
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return { reproducers: out, reproducerGaps: gaps }
|
|
259
|
+
}
|
|
260
|
+
|
|
216
261
|
// How to act on a blocking round. better-review keeps a still-open finding's id across
|
|
217
262
|
// rounds, so a shared id means the last fix did not resolve it.
|
|
218
263
|
// `history` is every record before `prior`, oldest first, or a function that reads them; it is
|
|
@@ -294,7 +339,12 @@ export function reviewVerdict(review, head, prior = null, { confirmSkip, earlier
|
|
|
294
339
|
return awaiting.length ? { verdict: 'wait', ...base, ...skipped } : { verdict: 'pass', reviewedHead: head, ...base, ...skipped }
|
|
295
340
|
}
|
|
296
341
|
if (skip && blocking.some((f) => !GITHUB_ID.test(String(f.id)))) throw new ReviewIncomplete('a skip record cannot hold findings; spawn the reviewer')
|
|
297
|
-
|
|
342
|
+
let retainedHistory
|
|
343
|
+
const readHistoryOnce = () => retainedHistory ??= typeof history === 'function' ? history() : history
|
|
344
|
+
const action = fixAction(review, blocking, prior, readHistoryOnce, { decision })
|
|
345
|
+
const witnesses = action.verdict === 'fix'
|
|
346
|
+
? fixReproducers(blocking, [...readHistoryOnce(), prior, review]) : {}
|
|
347
|
+
return { ...action, ...base, fix: blocking, ...witnesses }
|
|
298
348
|
}
|
|
299
349
|
|
|
300
350
|
// The record --carry writes for `head`: the previous round's approval, carried with no
|
|
@@ -333,12 +383,13 @@ export function carryRecord(prior, head, identity = {}) {
|
|
|
333
383
|
}
|
|
334
384
|
|
|
335
385
|
const INSTRUCTIONS = {
|
|
336
|
-
'fix': 'Fix
|
|
386
|
+
'fix': 'Fix these blocking findings. For each, make the smallest complete fix for its `rule` across the applicable paths and close every row in its `paths`. Use a shared owner when useful, but do not require a refactor; focused fixes on individual paths are acceptable.',
|
|
337
387
|
'test-first': 'The findings listed in `recurring` survived a fix: the last fix closed one path and missed others. For each, before changing code: name the rule the bug breaks; list every code path that can break it, including the trigger of this finding id in each earlier round\'s review file beside this one; reuse sufficient tests and add focused cases from the concrete triggers, then run them to see it fail on each open path. One table-driven test may cover several paths. Then make the smallest complete fix across the applicable paths until every test passes. Use a shared owner when useful, but do not require a refactor; focused fixes on individual paths are acceptable. "Fixed" is every path\'s test passing. Fix the rest as usual.',
|
|
338
|
-
'one-by-one': 'The last fix introduced these findings. Fix them
|
|
388
|
+
'one-by-one': 'The last fix introduced these findings. Fix them sequentially in this assignment, with the smallest complete change that closes every row of its `paths`. Use a shared owner when useful, but do not require a refactor; focused fixes on individual paths are acceptable.',
|
|
339
389
|
}
|
|
340
390
|
|
|
341
|
-
// The
|
|
391
|
+
// The generated blocker list stays P0/P1. Supporting P2 selections come from the
|
|
392
|
+
// coordinator's plan decision and accompanying brief; P3 is always skipped.
|
|
342
393
|
export function fixBriefOf(result) {
|
|
343
394
|
const fixRound = hasFixRound(result) ? result.fixRound : result.round
|
|
344
395
|
if (!isCounter(result.round) || !isCounter(fixRound) || fixRound > result.round) {
|
|
@@ -350,9 +401,11 @@ export function fixBriefOf(result) {
|
|
|
350
401
|
fixRound,
|
|
351
402
|
head: result.head,
|
|
352
403
|
action: result.action,
|
|
353
|
-
instruction: `${INSTRUCTIONS[result.action]}${result.decision ? ` Decision ${result.decision} in the plan's \`decisions\` settles what the recurring spec finding requires: fix it as that decision reads.` : ''}
|
|
404
|
+
instruction: `${INSTRUCTIONS[result.action]}${result.decision ? ` Decision ${result.decision} in the plan's \`decisions\` settles what the recurring spec finding requires: fix it as that decision reads.` : ''} Read each retained reproducer before using it; run against the candidate workspace at its workspace-relative cwd, preserving the recorded trigger and expected contract. Earlier results are baseline evidence, not candidate passes. A reproducerGaps entry is a source reference, not a runnable command: inspect its original review and establish a valid candidate-local case, retaining the original evidence and reporting any remaining gap. Keep subsequent failures of the affected operation in this repair until its existing criteria pass or an existing stop condition applies. Also perform only supporting P2 work explicitly selected by the coordinator in the plan and accompanying brief, naming the existing authorized outcome, verification or delivery it enables. P2 remains nonblocking; defer unselected P2 to the dropbox or a follow-up issue. Skip P3. Preserve the acceptance bar and existing authorization boundaries.`,
|
|
354
405
|
...(result.decision ? { decision: result.decision } : {}),
|
|
355
406
|
recurring: result.recurring,
|
|
407
|
+
reproducers: result.reproducers ?? [],
|
|
408
|
+
reproducerGaps: result.reproducerGaps ?? [],
|
|
356
409
|
fix: result.fix,
|
|
357
410
|
}
|
|
358
411
|
}
|
|
@@ -4,7 +4,7 @@ description: "Slash/explicit-only (/resolve-release). Full GitHub Flow release f
|
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
metadata:
|
|
6
6
|
workflow-role: coordinator
|
|
7
|
-
release-version: 0.
|
|
7
|
+
release-version: 0.50.1
|
|
8
8
|
---
|
|
9
9
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
10
10
|
# Resolve release
|