@1aboveio/skills 1.0.19 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/.codex-plugin/resources.json +143 -143
  3. package/package.json +4 -4
  4. package/skills/app-dev/airflow/SKILL.md +1 -1
  5. package/skills/app-dev/android-development/SKILL.md +1 -1
  6. package/skills/app-dev/app-debug/SKILL.md +1 -1
  7. package/skills/app-dev/better-auth/SKILL.md +1 -1
  8. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  9. package/skills/app-dev/data-quality/SKILL.md +1 -1
  10. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  11. package/skills/app-dev/maestro/SKILL.md +1 -1
  12. package/skills/app-dev/monorepo/SKILL.md +1 -1
  13. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  14. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  15. package/skills/app-dev/pyspark/SKILL.md +1 -1
  16. package/skills/app-dev/python-backend/SKILL.md +1 -1
  17. package/skills/app-dev/shadcn/SKILL.md +1 -1
  18. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  19. package/skills/cicd/cloud-build/SKILL.md +1 -1
  20. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  21. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  22. package/skills/cicd/docker/SKILL.md +1 -1
  23. package/skills/cicd/github-actions/SKILL.md +1 -1
  24. package/skills/cicd/github-flow/SKILL.md +1 -1
  25. package/skills/cicd/google-cloud/SKILL.md +1 -1
  26. package/skills/cicd/mergify/SKILL.md +1 -1
  27. package/skills/cicd/podman/SKILL.md +1 -1
  28. package/skills/content/article-review/SKILL.md +1 -1
  29. package/skills/content/article-to-image/SKILL.md +1 -1
  30. package/skills/content/flowchart-svg/SKILL.md +1 -1
  31. package/skills/content/image-gen/SKILL.md +1 -1
  32. package/skills/content/mermaid/SKILL.md +1 -1
  33. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  34. package/skills/content/wechat-publisher/SKILL.md +1 -1
  35. package/skills/content/wechat-search/SKILL.md +1 -1
  36. package/skills/content/xai-search/SKILL.md +1 -1
  37. package/skills/core/distill-writing/SKILL.md +1 -1
  38. package/skills/core/doc-to-md/SKILL.md +1 -1
  39. package/skills/core/editorial-review/SKILL.md +1 -1
  40. package/skills/core/house-style/SKILL.md +1 -1
  41. package/skills/core/plain-language/SKILL.md +1 -1
  42. package/skills/core/polish-cn/SKILL.md +1 -1
  43. package/skills/core/reg-parser/SKILL.md +1 -1
  44. package/skills/core/terminology/SKILL.md +1 -1
  45. package/skills/core/write-doc/SKILL.md +1 -1
  46. package/skills/core/write-report/SKILL.md +1 -1
  47. package/skills/engineering/better-review/SKILL.md +5 -4
  48. package/skills/engineering/better-review/references/passes.md +15 -0
  49. package/skills/engineering/better-review/references/result.md +14 -0
  50. package/skills/engineering/e2e-test/SKILL.md +1 -1
  51. package/skills/engineering/engineering/SKILL.md +1 -1
  52. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  53. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  54. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  55. package/skills/engineering/golden-path/SKILL.md +1 -1
  56. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  57. package/skills/engineering/resolve-issues/SKILL.md +11 -8
  58. package/skills/engineering/resolve-issues/references/ci.md +3 -1
  59. package/skills/engineering/resolve-issues/references/delivery-progress.md +16 -0
  60. package/skills/engineering/resolve-issues/references/done.md +7 -0
  61. package/skills/engineering/resolve-issues/references/dropbox.md +31 -6
  62. package/skills/engineering/resolve-issues/references/expire.md +6 -3
  63. package/skills/engineering/resolve-issues/references/implementation.md +67 -6
  64. package/skills/engineering/resolve-issues/references/review.md +42 -9
  65. package/skills/engineering/resolve-issues/references/task-plan.md +17 -2
  66. package/skills/engineering/resolve-issues/scripts/dropbox.mjs +2 -1
  67. package/skills/engineering/resolve-issues/scripts/next-actions.mjs +79 -4
  68. package/skills/engineering/resolve-issues/scripts/review-verdict.mjs +63 -10
  69. package/skills/engineering/resolve-release/SKILL.md +1 -1
  70. package/skills/engineering/rush-issues/SKILL.md +1 -1
  71. package/skills/engineering/rush-release/SKILL.md +1 -1
  72. package/skills/engineering/test-quality/SKILL.md +1 -1
  73. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  74. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  75. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  76. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  77. package/skills/risk-compliance/fraud-detection/SKILL.md +1 -1
  78. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  79. package/skills/risk-compliance/portfolio-payment-analysis/SKILL.md +1 -1
  80. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  81. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
@@ -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
- const admitted = removalPending ? { action: 'recover-workspace', unit: a.unit,
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', unit: a.unit,
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 findings only; it is
32
- // the fix worker's whole finding list. `action` says how to fix, from a
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: one finding
43
- // per fix worker, in turn, the smallest change that closes
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
- return { ...fixAction(review, blocking, prior, history, { decision }), ...base, fix: blocking }
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 only these 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.',
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 one at a time, one fix worker each, 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. Touch nothing else.',
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 fix worker's brief carries only P0/P1 findings; P2/P3 never reach it.
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.` : ''} Every other finding is out of scope: a P2 is deferred to the dropbox or a follow-up issue, and a P3 is skipped.`,
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.49.1
7
+ release-version: 0.50.0
8
8
  ---
9
9
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
10
10
  # Resolve release
@@ -6,7 +6,7 @@ metadata:
6
6
  workflow-role: coordinator
7
7
  dependencies:
8
8
  - resolve-issues
9
- release-version: 0.49.1
9
+ release-version: 0.50.0
10
10
  ---
11
11
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
12
12
  # Rush issues (shortcut of resolve-issues)
@@ -6,7 +6,7 @@ metadata:
6
6
  workflow-role: coordinator
7
7
  dependencies:
8
8
  - resolve-release
9
- release-version: 0.49.1
9
+ release-version: 0.50.0
10
10
  ---
11
11
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
12
12
  # Rush release (shortcut of resolve-release)
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: test-quality
3
3
  description: "Seven principles for tests that catch real bugs, and an audit that reports weak, redundant, and not-coverage tests without fixing them. Use when writing, changing, or reviewing tests, or when asked whether tests are useful, redundant, over-mocked, flaky, only assert static text, or test config files, or asked to make a slow test suite faster without losing coverage, or when the user asks to write the testing conventions into a repo's CONTRIBUTING.md. NOT defining what a spec must prove (ensure-coverage) or authoring browser tests (e2e-test)."
4
- release-version: 0.49.1
4
+ release-version: 0.50.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Test Quality
@@ -2,7 +2,7 @@
2
2
  name: compliance-audit
3
3
  version: 2.9.0
4
4
  description: "Audit operations against financial-compliance checklists (AML/KYC, VARS, PCI-DSS, TPA oversight, onboarding, transaction monitoring). Produces tracker.md (with a tracker.xlsx export) and phase-trail.md with layered validation; cites evidence by original filename. Input: reg-parser or the built-in VARS checklist."
5
- release-version: 0.49.1
5
+ release-version: 0.50.0
6
6
  ---
7
7
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
8
8
  # Compliance Audit
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: contract-compare
3
3
  description: "Compare two or more contracts side-by-side for structured diff, gap analysis, and benchmarking. Use when: comparing contracts or agreements, contract diff, compare terms, vendor-vs-vendor, compare our template vs theirs, compliance gap between contracts. NOT for single-contract risk review — use contract-review for that."
4
- release-version: 0.49.1
4
+ release-version: 0.50.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Contract Compare
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: contract-review
3
3
  description: "Review contracts for legal, commercial, and compliance risks via convert → chunk review → merge → validate (+ optional mandate preprocessing). Use for reviewing, auditing, or checking any contract, agreement, NDA, MSA, or SLA; input a file path (PDF/DOCX/TXT) or pasted text."
4
- release-version: 0.49.1
4
+ release-version: 0.50.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Contract Review
@@ -3,7 +3,7 @@ name: doc-authenticity
3
3
  version: 0.4.1
4
4
  description: "Screen PDF documents and raster images for fabrication, tampering, Photoshop or other image-editor use, and content validity. Detects file type, runs format-specific authenticity forensics, classifies bank statements, conversation logs, invoices, agreements, and other documents, then applies type-specific validity checks. Produces separate authenticity (NO_EVIDENCE_OF_MODIFICATION / SUSPICIOUS / LIKELY_MODIFIED / INCONCLUSIVE) and validity (VALID / INVALID / INCONCLUSIVE) verdicts. Use whenever a user asks whether a document, screenshot, scan, statement, invoice, contract, chat log, receipt, or payment evidence is genuine, original, edited, Photoshopped, manipulated, fake, or valid."
5
5
  disable-model-invocation: true
6
- release-version: 0.49.1
6
+ release-version: 0.50.0
7
7
  ---
8
8
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
9
9
  # Document and Image Authenticity
@@ -12,7 +12,7 @@ description: >
12
12
  impact, and a business report. Use for fraud, scam, mule, account-takeover,
13
13
  chargeback, dispute, transfer, collection, or payout risk analysis even when the
14
14
  user does not name this skill.
15
- release-version: 0.49.1
15
+ release-version: 0.50.0
16
16
  ---
17
17
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
18
18
  # Fraud Detection
@@ -14,7 +14,7 @@ description: >
14
14
  transactions" — even if they do not say payment-analysis. Do NOT use for
15
15
  fraud rule discovery (fraud-detection) or TLD / MCC-miscoding
16
16
  clusters (tld-detection).
17
- release-version: 0.49.1
17
+ release-version: 0.50.0
18
18
  ---
19
19
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
20
20
  # Payment Analysis
@@ -5,7 +5,7 @@ description: >
5
5
  and investigate selected merchants' authorization, settlement and disputes,
6
6
  including descriptor mapping and overlapping exports. Portfolio-level payment
7
7
  overviews belong to payment-analysis; fraud rule discovery belongs to fraud-detection.
8
- release-version: 0.49.1
8
+ release-version: 0.50.0
9
9
  ---
10
10
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
11
11
  # Portfolio payment analysis
@@ -8,7 +8,7 @@ description: >
8
8
  links, an entity chain, and a watchlist for the operator's next domains.
9
9
  Not transaction data (tld-detection) or fraud rules (fraud-detection).
10
10
  disable-model-invocation: true
11
- release-version: 0.49.1
11
+ release-version: 0.50.0
12
12
  ---
13
13
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
14
14
  # Similar Domain
@@ -6,7 +6,7 @@ description: >
6
6
  patterns, with USD and BIN-country currency views, group profiles and priorities.
7
7
  Not payment-analysis, fraud-detection, or website research.
8
8
  disable-model-invocation: true
9
- release-version: 0.49.1
9
+ release-version: 0.50.0
10
10
  ---
11
11
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
12
12
  # TLD detection