@open-agent-toolkit/cli 0.1.66 → 0.1.72

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 (65) hide show
  1. package/assets/agents/oat-reviewer.md +0 -1
  2. package/assets/docs/cli-utilities/configuration.md +27 -0
  3. package/assets/docs/cli-utilities/index.md +1 -1
  4. package/assets/docs/cli-utilities/workflow-gates.md +147 -5
  5. package/assets/docs/reference/cli-reference.md +5 -1
  6. package/assets/docs/workflows/projects/dispatch-ceiling.md +25 -8
  7. package/assets/docs/workflows/projects/orchestration-model.md +21 -0
  8. package/assets/docs/workflows/projects/pr-flow.md +12 -1
  9. package/assets/docs/workflows/projects/review-flavors.md +9 -0
  10. package/assets/docs/workflows/projects/reviews.md +21 -1
  11. package/assets/public-package-versions.json +4 -4
  12. package/assets/skills/oat-dispatch-subagents/SKILL.md +21 -1
  13. package/assets/skills/oat-dispatch-subagents/references/provider-claude.md +21 -0
  14. package/assets/skills/oat-dispatch-subagents/references/provider-codex.md +16 -0
  15. package/assets/skills/oat-dispatch-subagents/references/provider-cursor.md +21 -0
  16. package/assets/skills/oat-project-autonomous/SKILL.md +1 -1
  17. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +5 -2
  18. package/assets/skills/oat-project-complete/SKILL.md +20 -2
  19. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +5 -2
  20. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  21. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +12 -1
  22. package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +24 -9
  23. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +5 -2
  24. package/assets/skills/oat-project-implement/references/phase-execution.md +5 -2
  25. package/assets/skills/oat-project-next/SKILL.md +21 -6
  26. package/assets/skills/oat-project-plan/SKILL.md +6 -1
  27. package/assets/skills/oat-project-plan-writing/SKILL.md +41 -34
  28. package/assets/skills/oat-project-pr-final/SKILL.md +20 -5
  29. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +5 -2
  30. package/assets/skills/oat-project-pr-progress/SKILL.md +18 -3
  31. package/assets/skills/oat-project-progress/SKILL.md +4 -1
  32. package/assets/skills/oat-project-quick-start/SKILL.md +6 -1
  33. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +5 -2
  34. package/assets/skills/oat-project-review-provide/SKILL.md +68 -2
  35. package/assets/skills/oat-project-review-receive/SKILL.md +46 -18
  36. package/assets/skills/oat-project-review-receive-remote/SKILL.md +41 -8
  37. package/dist/commands/cleanup/project/project.utils.d.ts.map +1 -1
  38. package/dist/commands/cleanup/project/project.utils.js +19 -1
  39. package/dist/commands/config/index.d.ts.map +1 -1
  40. package/dist/commands/config/index.js +65 -1
  41. package/dist/commands/gate/__fixtures__/fake-runtime.d.mts +3 -0
  42. package/dist/commands/gate/__fixtures__/fake-runtime.d.mts.map +1 -0
  43. package/dist/commands/gate/__fixtures__/fake-runtime.mjs +167 -0
  44. package/dist/commands/gate/activity-probes.d.ts +35 -0
  45. package/dist/commands/gate/activity-probes.d.ts.map +1 -0
  46. package/dist/commands/gate/activity-probes.js +135 -0
  47. package/dist/commands/gate/branch-local-cli.d.ts +31 -0
  48. package/dist/commands/gate/branch-local-cli.d.ts.map +1 -0
  49. package/dist/commands/gate/branch-local-cli.js +116 -0
  50. package/dist/commands/gate/index.d.ts +31 -0
  51. package/dist/commands/gate/index.d.ts.map +1 -1
  52. package/dist/commands/gate/index.js +465 -31
  53. package/dist/commands/gate/route.d.ts +22 -0
  54. package/dist/commands/gate/route.d.ts.map +1 -0
  55. package/dist/commands/gate/route.js +109 -0
  56. package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
  57. package/dist/commands/project/dispatch-ceiling/index.js +100 -16
  58. package/dist/commands/review/latest.d.ts +1 -0
  59. package/dist/commands/review/latest.d.ts.map +1 -1
  60. package/dist/commands/review/latest.js +96 -22
  61. package/dist/config/oat-config.d.ts +9 -0
  62. package/dist/config/oat-config.d.ts.map +1 -1
  63. package/dist/config/oat-config.js +23 -0
  64. package/dist/config/resolve.js +7 -0
  65. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-review-provide
3
- version: 1.3.16
3
+ version: 1.3.20
4
4
  description: Use when the user explicitly asks to review an OAT project — e.g. "review project", "review the project", "run project review", or confirms a previously offered review. Do NOT auto-invoke on completed work alone. Resolves a project review scope and offers before running.
5
5
  disable-model-invocation: false
6
6
  user-invocable: true
@@ -584,6 +584,17 @@ candidate ladder, fail closed before review. Route interactive repair through
584
584
  the planning workflow's `Complete Dispatch Ladder Adoption Contract`; do not
585
585
  invent a reviewer target.
586
586
 
587
+ **Pre-plan policy inheritance:** When the resolver returns
588
+ `unresolvedReason: 'policy'`, and only then, an `artifact` review whose scope is
589
+ `discovery`, `design`, or `spec` does not block or prompt. Review by deliberate
590
+ inheritance in the current context and record
591
+ `selection_reason: inherit (pre-plan; no project policy)`. An explicitly set
592
+ project policy is always honored, including for pre-plan artifacts. Code
593
+ reviews and `artifact plan` reviews still hard-require a resolved policy.
594
+ Missing or incomplete ladder results (`unresolvedReason: 'ladder' | 'both'`)
595
+ still fail closed. Gate exec-target selection is unaffected by this
596
+ inheritance rule because gates resolve their target independently.
597
+
587
598
  After constructing the complete provider payload, record the launcher-owned
588
599
  `target`, `model_axis`, and `effort_axis` with
589
600
  `launcher-selected/config-declared` provenance. These fields are immutable:
@@ -609,6 +620,56 @@ managed base role is forbidden except for
609
620
  explicit inherit/default behavior or the documented managed-uncapped reviewer
610
621
  fallback.
611
622
 
623
+ **Step 6.1: Headless gate route (overrides normal tier selection)**
624
+
625
+ When either prompt frontmatter has `oat_gate_headless: true` or the environment
626
+ has `OAT_GATE_HEADLESS=1`, copy the expected runtime and model from
627
+ `oat_gate_runtime` and `oat_invocation_model`, determine whether this host has
628
+ an awaited-child capability, and use the gate-provided branch-local CLI path.
629
+ Fail closed with `OAT_GATE_REFUSAL` if `OAT_GATE_CLI_PATH` is absent or not
630
+ executable. Run the route command and validate its JSON before using it:
631
+
632
+ ```bash
633
+ ROUTE_JSON="$("$OAT_GATE_CLI_PATH" gate route --json \
634
+ --expect-runtime "$OAT_GATE_RUNTIME" \
635
+ --expect-model "$OAT_INVOCATION_MODEL" \
636
+ --can-await true)"
637
+
638
+ OAT_GATE_ROUTE_JSON="$ROUTE_JSON" node -e '
639
+ const value = JSON.parse(process.env.OAT_GATE_ROUTE_JSON);
640
+ if (!["inline", "delegate-sync", "refuse"].includes(value.route) ||
641
+ typeof value.reason !== "string") process.exit(1);
642
+ if (value.cliRoot !== process.env.OAT_GATE_CLI_ROOT) process.exit(1);
643
+ process.stdout.write(JSON.stringify(value));
644
+ '
645
+ ```
646
+
647
+ Pass `--can-await false` when this host cannot synchronously await delegated
648
+ review completion. An absent command, command failure, help/non-JSON output,
649
+ invalid envelope, or `cliRoot` different from `OAT_GATE_CLI_ROOT` is a
650
+ structured refusal; never retry with bare `oat` or another installed CLI. Do
651
+ not make a separate runtime/model identity judgment in skill prose; follow the
652
+ validated helper route:
653
+
654
+ In the terminal headless response, report
655
+ `Gate route: <route> (runtime=<expected-runtime>, cliRoot=<validated-cliRoot>)`
656
+ so the parent can retain branch-local route evidence.
657
+
658
+ - `inline`: execute the complete `oat-reviewer` role contract in the current
659
+ context, write the artifact, and finish bookkeeping before returning.
660
+ - `delegate-sync`: dispatch through an awaited handle, then verify the expected
661
+ artifact exists and carries the matching `oat_gate_run_id` before returning.
662
+ A terminal `BLOCKED`, interruption, timeout, or missing/mismatched artifact
663
+ remains terminal and must not trigger a second launch.
664
+ - `refuse`: print `OAT_GATE_REFUSAL: <reason from route output>` on its own
665
+ line. Exit nonzero where the host permits; the gate classifies this line
666
+ independently of exit code.
667
+
668
+ Headless gate mode overrides the Tier 1 background-dispatch preference:
669
+ NEVER use fire-and-forget background dispatch in this mode. The review and its
670
+ artifact/bookkeeping completion must be inline or synchronously awaited before
671
+ the gate child exits.
672
+
612
673
  **Step 6a: Probe Subagent Availability**
613
674
 
614
675
  Before selecting a tier, announce the probe and its result so the user can see what's happening:
@@ -842,7 +903,7 @@ Run the `oat-project-review-receive` skill to convert findings into plan tasks.
842
903
 
843
904
  After review artifact is written, update `plan.md` `## Reviews` table _if plan.md exists_.
844
905
 
845
- Update or add a row matching `{scope}`:
906
+ Record this artifact as one append-ordered review event:
846
907
 
847
908
  - `Scope`: `{scope}` (examples: `p02`, `final`, `spec`, `design`)
848
909
  - Phase-range examples such as `p02-p03` are valid code-review scopes and should be preserved exactly.
@@ -851,6 +912,11 @@ Update or add a row matching `{scope}`:
851
912
  - `Date`: `{today}`
852
913
  - `Artifact`: `reviews/{filename}.md`
853
914
 
915
+ For the first event with this Scope + Type, claim an unbound `pending`
916
+ placeholder only when its Artifact is `-`. Otherwise append a new row for the
917
+ new artifact. Never replace or regress a bound event merely because Scope +
918
+ Type matches; distinct artifact filenames are distinct review events.
919
+
854
920
  If plan.md is missing (e.g., spec/design review before planning), skip this update and rely on the review artifact + next-step routing.
855
921
 
856
922
  ### Step 9.5: Commit Review Bookkeeping Atomically (Required)
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-review-receive
3
- version: 1.5.7
3
+ version: 1.5.9
4
4
  description: Use when the user explicitly asks to receive review findings for an OAT project — e.g. "receive review", "process review", "process the project review", or confirms a previously offered review-receive step. Do NOT auto-invoke merely because a review file exists. Resolves the latest review and offers before acting.
5
5
  disable-model-invocation: false
6
6
  user-invocable: true
@@ -102,7 +102,7 @@ PROJECTS_ROOT="${PROJECTS_ROOT%/}"
102
102
 
103
103
  ```bash
104
104
  if [ -n "${PROJECT_PATH:-}" ] && [ -d "$PROJECT_PATH" ]; then
105
- oat review latest --project "$PROJECT_PATH" --json
105
+ oat review latest --project "$PROJECT_PATH" --actionable-project --json
106
106
  else
107
107
  oat review latest --json
108
108
  fi
@@ -110,11 +110,13 @@ fi
110
110
 
111
111
  Selection rules:
112
112
 
113
- - Use `oat review latest` as the first-choice resolver. It scans project reviews (`reviews/` and `reviews/archived/`) plus ad-hoc review locations and orders candidates by `oat_generated_at` frontmatter rather than filesystem mtime. Review artifacts carry a seconds-precision `oat_generated_at` and a matching timestamped filename, so same-scope same-day re-gates order deterministically and the newest round wins — never assume the plain `<scope>-review-<date>.md` name is current.
113
+ - With a valid project, use the `--actionable-project` path first. It resolves only an active/actionable project review from the top-level `reviews/` directory, so newer historical artifacts in `reviews/archived/` cannot strand an older active event.
114
+ - If the actionable-project result has `path: null`, run `oat review latest --json` without `--actionable-project` to discover the latest ad-hoc target or report project history. The default resolver intentionally preserves all-history ordering across project (`reviews/` and `reviews/archived/`) and ad-hoc locations.
115
+ - Both resolver paths order candidates by `oat_generated_at` frontmatter rather than filesystem mtime. Review artifacts carry a seconds-precision `oat_generated_at` and a matching timestamped filename, so same-scope same-day re-gates order deterministically and the newest round wins — never assume the plain `<scope>-review-<date>.md` name is current.
114
116
  - Read the JSON result:
115
117
  - `path: null` means no review target was found.
116
118
  - `kind: "project"` and `actionable: true` means this skill can process the target when the path is an active top-level project review.
117
- - `archived: true` or `actionable: false` means the target is historical/non-actionable for project review-receive.
119
+ - An `archived: true` or `actionable: false` result from the all-history fallback means no active project review was resolved and the selected result is historical/non-actionable for project review-receive.
118
120
  - `kind: "adhoc"` means route to `oat-review-receive` after offering that handoff to the user.
119
121
  - Only process active project review artifacts in the top level of `"$PROJECT_PATH/reviews/"`.
120
122
  - Treat archived project artifacts as history only; do not receive them automatically. If `oat review latest` returns an archived project review, tell the user no active project review is waiting and offer to run `oat-project-review-provide`.
@@ -140,11 +142,32 @@ Use this fallback only for active project reviews. It cannot discover ad-hoc rev
140
142
  Derive archive bookkeeping before making lifecycle edits:
141
143
 
142
144
  ```bash
143
- REVIEW_FILENAME=$(basename "$REVIEW_PATH")
145
+ SOURCE_REVIEW_FILENAME=$(basename "$REVIEW_PATH")
146
+ REVIEW_FILENAME="$SOURCE_REVIEW_FILENAME"
144
147
  ARCHIVED_REVIEW_DIR="$PROJECT_PATH/reviews/archived"
145
148
  ARCHIVED_REVIEW_PATH="$ARCHIVED_REVIEW_DIR/$REVIEW_FILENAME"
149
+
150
+ if [[ -e "$ARCHIVED_REVIEW_PATH" ]]; then
151
+ REVIEW_STEM="${SOURCE_REVIEW_FILENAME%.md}"
152
+ ARCHIVE_TIMESTAMP=$(date -u +%Y-%m-%dT%H%M%SZ)
153
+ REVIEW_FILENAME="${REVIEW_STEM}-${ARCHIVE_TIMESTAMP}.md"
154
+ ARCHIVED_REVIEW_PATH="$ARCHIVED_REVIEW_DIR/$REVIEW_FILENAME"
155
+ ARCHIVE_COLLISION_INDEX=2
156
+
157
+ while [[ -e "$ARCHIVED_REVIEW_PATH" ]]; do
158
+ REVIEW_FILENAME="${REVIEW_STEM}-${ARCHIVE_TIMESTAMP}-${ARCHIVE_COLLISION_INDEX}.md"
159
+ ARCHIVED_REVIEW_PATH="$ARCHIVED_REVIEW_DIR/$REVIEW_FILENAME"
160
+ ARCHIVE_COLLISION_INDEX=$((ARCHIVE_COLLISION_INDEX + 1))
161
+ done
162
+ fi
146
163
  ```
147
164
 
165
+ `SOURCE_REVIEW_FILENAME` identifies the selected active event before archival.
166
+ `REVIEW_FILENAME` and `ARCHIVED_REVIEW_PATH` are the collision-free final
167
+ identity and destination. Resolve them here, before writing plan,
168
+ implementation, or artifact-review references. Never choose a different
169
+ basename later in either receive path.
170
+
148
171
  ### Step 2: Parse Findings into Buckets
149
172
 
150
173
  Extract findings from the review artifact and categorize:
@@ -382,7 +405,9 @@ Add new tasks to plan.md in the target phase. When adding or editing tasks, pres
382
405
 
383
406
  **Review-fix bookkeeping (required):**
384
407
  - When you add review-generated fix tasks:
385
- - Update the relevant Reviews table row status to `fixes_added` (work queued) and set the Date + Artifact.
408
+ - Locate the Reviews event matching the selected review's Scope, Type, and `SOURCE_REVIEW_FILENAME`, then update it to `fixes_added` (work queued), set the Date, and replace its Artifact with `reviews/archived/$REVIEW_FILENAME`.
409
+ - The written `REVIEW_FILENAME` becomes the event's artifact filename and identity for every later mutation; use the already-resolved final basename in every plan and implementation reference.
410
+ - Never select a row by scope alone or move an event status backward. If the exact bound event is missing, stop and reconcile the ledger instead of mutating another event.
386
411
  - Update `## Implementation Complete` totals (phase counts + total task count) so downstream PR/review summaries don’t go stale.
387
412
  - If the plan includes any phase rollups that reference task counts, update those too.
388
413
 
@@ -399,10 +424,11 @@ Add new tasks to plan.md in the target phase. When adding or editing tasks, pres
399
424
  **Update Reviews section:**
400
425
  ```markdown
401
426
  ## Reviews
402
- - Update or add a row for `{scope}` in the Reviews table:
427
+ - Find the existing event by `{scope}`, review Type, and
428
+ `$SOURCE_REVIEW_FILENAME`, then update only that row:
403
429
  - Status: `fixes_added` (if tasks were added) or `passed` (if no Critical/Important/Medium and no unresolved final-scope gates)
404
430
  - Date: `{today}`
405
- - Artifact: `reviews/archived/{filename}.md`
431
+ - Artifact: `reviews/archived/$REVIEW_FILENAME`
406
432
  ````
407
433
 
408
434
  **Status semantics (v1):**
@@ -410,6 +436,7 @@ Add new tasks to plan.md in the target phase. When adding or editing tasks, pres
410
436
  - `fixes_added`: fix tasks were created and added to the plan
411
437
  - `fixes_completed`: fix tasks implemented, awaiting re-review
412
438
  - `passed`: re-review completed and recorded as passing (no unresolved Critical/Important/Medium, and all final-scope gates satisfied: deferred-medium + minor disposition)
439
+ - Status changes are monotonic. Never move an event status backward, replace an earlier event, or update a different event that happens to share the same scope/type.
413
440
 
414
441
  ### Step 7: Update Implementation.md
415
442
 
@@ -419,7 +446,7 @@ Add a note to implementation.md:
419
446
  ### Review Received: {scope}
420
447
 
421
448
  **Date:** {today}
422
- **Review artifact:** reviews/archived/{filename}.md
449
+ **Review artifact:** reviews/archived/$REVIEW_FILENAME
423
450
 
424
451
  **Findings:**
425
452
 
@@ -438,7 +465,7 @@ Add a note to implementation.md:
438
465
 
439
466
  After the fix tasks are complete:
440
467
 
441
- - Update the review row status to `fixes_completed`
468
+ - Update this same artifact-identified review event to `fixes_completed`
442
469
  - Re-run `oat-project-review-provide {type} {scope}` then `oat-project-review-receive` to reach `passed`
443
470
  ```
444
471
 
@@ -468,8 +495,8 @@ mv "$REVIEW_PATH" "$ARCHIVED_REVIEW_PATH"
468
495
 
469
496
  Rules:
470
497
 
471
- - Perform the move only after all written references have been updated to `reviews/archived/{filename}.md`.
472
- - If `"$ARCHIVED_REVIEW_PATH"` already exists, append a timestamp suffix before moving so history is preserved.
498
+ - Perform the move only after all written references and event identity have been updated to `reviews/archived/$REVIEW_FILENAME`.
499
+ - Use the `REVIEW_FILENAME` and `ARCHIVED_REVIEW_PATH` resolved in Step 1. Do not rename or re-resolve the destination here.
473
500
  - Report the archived location in the final summary.
474
501
 
475
502
  ### Step 7.6: Commit Review Bookkeeping (Required)
@@ -647,8 +674,9 @@ mkdir -p "$ARCHIVED_REVIEW_DIR"
647
674
  mv "$REVIEW_PATH" "$ARCHIVED_REVIEW_PATH"
648
675
  ```
649
676
 
650
- - Perform the move only after all artifact edits are applied and any written references point to `reviews/archived/{filename}.md`.
651
- - If `"$ARCHIVED_REVIEW_PATH"` already exists, append a timestamp suffix before moving so history is preserved.
677
+ - Before the move, use the Step 1 `REVIEW_FILENAME` for every artifact-review reference or event identity written by the approved edits.
678
+ - Perform the move only after all artifact edits are applied and every written reference points to `reviews/archived/$REVIEW_FILENAME`.
679
+ - Use the Step 1 `ARCHIVED_REVIEW_PATH` unchanged. Do not rename or re-resolve the destination here.
652
680
 
653
681
  **Then route:**
654
682
 
@@ -662,14 +690,14 @@ mv "$REVIEW_PATH" "$ARCHIVED_REVIEW_PATH"
662
690
  ```
663
691
  Review received for {project-name}.
664
692
 
665
- Review: {review_filename}
693
+ Review: $REVIEW_FILENAME
666
694
  Scope: {scope}
667
695
  Findings: {N} critical, {N} important, {N} medium, {N} minor
668
696
 
669
697
  Actions taken:
670
698
  - Added {N} fix tasks to plan.md ({task_ids})
671
699
  - Updated implementation.md with review notes
672
- - Archived review artifact to `reviews/archived/{filename}.md`
700
+ - Archived review artifact to `reviews/archived/$REVIEW_FILENAME`
673
701
  - Deferred/accepted Medium findings: {N}
674
702
  - Minor findings dispositioned: {N} converted (default), {N} deferred-with-rationale (explicit user decision required for final scope)
675
703
  - Finding disposition map: {ID -> converted|deferred|accepted + rationale summary}
@@ -684,13 +712,13 @@ For `artifact` reviews, summarize instead:
684
712
  ```
685
713
  Review received for {project-name}.
686
714
 
687
- Review: {review_filename}
715
+ Review: $REVIEW_FILENAME
688
716
  Scope: {scope}
689
717
  Findings: {N} critical, {N} important, {N} medium, {N} minor
690
718
 
691
719
  Actions taken:
692
720
  - Applied {N} artifact edits
693
- - Archived review artifact to `reviews/archived/{filename}.md`
721
+ - Archived review artifact to `reviews/archived/$REVIEW_FILENAME`
694
722
  - No plan tasks created
695
723
  - Finding disposition map: {ID -> resolve_in_artifact|rejected_with_rationale|needs_user_direction}
696
724
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-review-receive-remote
3
- version: 1.4.0
3
+ version: 1.4.2
4
4
  description: Use when processing GitHub PR review comments within project context. Fetches PR comments, creates plan tasks, and updates project artifacts.
5
5
  disable-model-invocation: true
6
6
  user-invocable: true
@@ -117,9 +117,22 @@ npx agent-reviews --json --unresolved --pr <N>
117
117
 
118
118
  If no unresolved comments:
119
119
 
120
- - Report clean status.
121
- - Update `plan.md` review row for scoped entry to `passed` when applicable.
122
- - Stop.
120
+ 1. Create a UTC timestamp and event-distinct filename:
121
+ `reviews/archived/remote-pr-<N>-review-YYYY-MM-DDTHHMMSSZ.md`.
122
+ 2. Ensure `reviews/archived/` exists, then write that artifact with the PR
123
+ number, fetch timestamp, remote scope/type, and a zero-unresolved-findings
124
+ result. A clean remote review is consumed as it is recorded, so it must not
125
+ remain in top-level `reviews/`.
126
+ 3. Record the clean result as a `passed` Reviews event whose event identity
127
+ combines `Scope`, `Type`, and artifact filename:
128
+ - Claim only an unbound `pending` placeholder with matching Scope + Type and
129
+ Artifact `-`; otherwise append a distinct row.
130
+ - Set Date and Artifact to this clean event. Advance only this event and
131
+ never mutate another row by scope alone.
132
+ 4. Commit `plan.md` and the clean review artifact atomically with
133
+ `chore(oat): record clean remote review (pr-#<N>)`. Do not stop with
134
+ uncommitted bookkeeping.
135
+ 5. Report clean status and stop.
123
136
 
124
137
  ### Step 3: Classify and Normalize Findings
125
138
 
@@ -162,14 +175,34 @@ For each converted finding:
162
175
 
163
176
  ### Step 6: Update Project Artifacts
164
177
 
178
+ Before changing the ledger, write an event-distinct review artifact containing
179
+ the PR number, fetch timestamp, normalized findings, and dispositions:
180
+ `reviews/archived/remote-pr-<N>-review-YYYY-MM-DDTHHMMSSZ.md`. Remote receive
181
+ fully dispositions the event as `passed` or `fixes_added`, so the artifact is
182
+ consumed immediately and belongs in `reviews/archived/`, not top-level
183
+ `reviews/`. Each fetch/triage cycle gets a new timestamped artifact filename,
184
+ even when the PR and lifecycle scope are unchanged.
185
+
186
+ ```bash
187
+ REMOTE_REVIEW_TIMESTAMP=$(date -u +%Y-%m-%dT%H%M%SZ)
188
+ REMOTE_REVIEW_FILENAME="remote-pr-${PR_NUMBER}-review-${REMOTE_REVIEW_TIMESTAMP}.md"
189
+ mkdir -p "$PROJECT_PATH/reviews/archived"
190
+ REMOTE_REVIEW_PATH="$PROJECT_PATH/reviews/archived/$REMOTE_REVIEW_FILENAME"
191
+ ```
192
+
165
193
  Update `plan.md`:
166
194
 
167
195
  - Append inserted review-fix task sections in correct phase order.
168
- - Update `## Reviews` row for remote scope:
196
+ - Record an append-ordered `## Reviews` event for the remote scope:
169
197
  - status `fixes_added` when tasks were added
170
198
  - status `passed` when no actionable findings remain
171
199
  - date set to today
172
- - artifact `github-pr #<N>`
200
+ - artifact `reviews/archived/remote-pr-<N>-review-YYYY-MM-DDTHHMMSSZ.md`
201
+ - Claim an unbound `pending` placeholder only when its Scope + Type matches and
202
+ its Artifact is `-`; otherwise append the event. Later mutations select it by
203
+ Scope + Type + artifact filename, never by scope or `github-pr #<N>` alone.
204
+ - Never move an event status backward or overwrite an earlier event from the
205
+ same PR.
173
206
  - Update `## Implementation Complete` totals.
174
207
 
175
208
  Update `implementation.md`:
@@ -192,12 +225,12 @@ Update `state.md`:
192
225
 
193
226
  ### Step 6.5: Commit Review Bookkeeping (Required)
194
227
 
195
- **CRITICAL — DO NOT SKIP.** This skill modifies `plan.md`, `implementation.md`, and `state.md` when processing GitHub PR comments. When it runs in a separate agent session (subagent, fresh session, or different conversation), uncommitted bookkeeping updates cause state drift for the original agent. The commit below is the safety net.
228
+ **CRITICAL — DO NOT SKIP.** This skill modifies the event-distinct review artifact, `plan.md`, `implementation.md`, and `state.md` when processing GitHub PR comments. When it runs in a separate agent session (subagent, fresh session, or different conversation), uncommitted bookkeeping updates cause state drift for the original agent. The commit below is the safety net.
196
229
 
197
230
  Commit all modified OAT tracking files atomically:
198
231
 
199
232
  ```bash
200
- git add "$PROJECT_PATH/plan.md" "$PROJECT_PATH/implementation.md" "$PROJECT_PATH/state.md"
233
+ git add "$PROJECT_PATH/plan.md" "$PROJECT_PATH/implementation.md" "$PROJECT_PATH/state.md" "$REMOTE_REVIEW_PATH"
201
234
  git diff --cached --quiet || git commit -m "chore(oat): record remote review findings and add fix tasks (pr-#$PR_NUMBER)"
202
235
  ```
203
236
 
@@ -1 +1 @@
1
- {"version":3,"file":"project.utils.d.ts","sourceRoot":"","sources":["../../../../src/commands/cleanup/project/project.utils.ts"],"names":[],"mappings":"AAQA,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAG1E;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAG1E;AAED,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAErE;AAED,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,OAAO,CAKT;AAED,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,MAAM,CAMR;AAED,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkB1E"}
1
+ {"version":3,"file":"project.utils.d.ts","sourceRoot":"","sources":["../../../../src/commands/cleanup/project/project.utils.ts"],"names":[],"mappings":"AAQA,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAG1E;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAG1E;AAED,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CA8BrE;AAED,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,OAAO,CAKT;AAED,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,MAAM,CAMR;AAED,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkB1E"}
@@ -14,7 +14,25 @@ export function projectNeedsStateFile(fileNames) {
14
14
  return !files.has('state.md') && hasLifecycleArtifacts(files);
15
15
  }
16
16
  export function projectPlanMarksComplete(planContent) {
17
- return /\|\s*final\s*\|\s*code\s*\|\s*passed\s*\|/i.test(planContent);
17
+ const reviewsHeading = /^## Reviews[ \t]*\r?$/m.exec(planContent);
18
+ if (!reviewsHeading) {
19
+ return false;
20
+ }
21
+ const afterHeading = planContent.slice(reviewsHeading.index + reviewsHeading[0].length);
22
+ const nextSectionIndex = afterHeading.search(/^##(?!#)[ \t]+\S.*\r?$/m);
23
+ const reviewsSection = nextSectionIndex === -1
24
+ ? afterHeading
25
+ : afterHeading.slice(0, nextSectionIndex);
26
+ const finalCodeEvents = reviewsSection
27
+ .split('\n')
28
+ .filter((line) => line.trim().startsWith('|'))
29
+ .map((line) => line
30
+ .split('|')
31
+ .slice(1, -1)
32
+ .map((cell) => cell.trim()))
33
+ .filter(([scope, type]) => scope?.toLowerCase() === 'final' && type?.toLowerCase() === 'code');
34
+ const latestFinalCodeEvent = finalCodeEvents[finalCodeEvents.length - 1];
35
+ return latestFinalCodeEvent?.[2]?.toLowerCase() === 'passed';
18
36
  }
19
37
  export function stateLifecycleIsComplete(stateContent) {
20
38
  return /^oat_lifecycle:\s*complete$/m.test(stateContent);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEhF,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAC;AAezC,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,cAAc,EAGnB,KAAK,UAAU,EAehB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAGL,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,mCAAmC,EACnC,0BAA0B,EAC3B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmFpC,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,cAAc,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,eAAe,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzE,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC7C,mCAAmC,EAAE,OAAO,mCAAmC,CAAC;IAChF,0BAA0B,EAAE,OAAO,0BAA0B,CAAC;IAC9D,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAg5DD,wBAAgB,mBAAmB,CACjC,SAAS,GAAE,OAAO,CAAC,yBAAyB,CAAM,GACjD,OAAO,CAsKT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEhF,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAC;AAezC,OAAO,EAML,KAAK,SAAS,EACd,KAAK,cAAc,EAGnB,KAAK,UAAU,EAehB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAGL,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,mCAAmC,EACnC,0BAA0B,EAC3B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuFpC,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,cAAc,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,eAAe,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzE,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC7C,mCAAmC,EAAE,OAAO,mCAAmC,CAAC;IAChF,0BAA0B,EAAE,OAAO,0BAA0B,CAAC;IAC9D,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAg+DD,wBAAgB,mBAAmB,CACjC,SAAS,GAAE,OAAO,CAAC,yBAAyB,CAAM,GACjD,OAAO,CAsKT"}
@@ -7,7 +7,7 @@ import { readGlobalOptions } from '../shared/shared.utils.js';
7
7
  import { compileDispatchCeilingPreset } from '../../config/dispatch-ceiling-preset.js';
8
8
  import { normalizeDispatchMatrix, validateDispatchRouteTarget, walkDispatchMatrix, } from '../../config/dispatch-matrix.js';
9
9
  import { dispatchPolicyModeDescription, dispatchPolicyPolicyDescription, managedDispatchPolicyValueList, } from '../../config/dispatch-policy-options.js';
10
- import { VALID_DISPATCH_POLICY_MODES, VALID_MANAGED_DISPATCH_POLICIES, normalizeWorkflowPostImplementSequence, readOatConfig, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
10
+ import { VALID_DISPATCH_POLICY_MODES, VALID_MANAGED_DISPATCH_POLICIES, MAX_GATE_TIMEOUT_MS, MIN_GATE_TIMEOUT_MS, isValidGateTimeoutMs, normalizeWorkflowPostImplementSequence, readOatConfig, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
11
11
  import { resolveEffectiveConfig, } from '../../config/resolve.js';
12
12
  import { resolveAssetsRoot } from '../../fs/assets.js';
13
13
  import { resolveProjectRoot } from '../../fs/paths.js';
@@ -63,6 +63,8 @@ const KEY_ORDER = [
63
63
  'workflow.dispatchCeiling.preset',
64
64
  'workflow.dispatchCeiling.providers.codex',
65
65
  'workflow.dispatchCeiling.providers.claude',
66
+ 'workflow.gateTimeouts.code',
67
+ 'workflow.gateTimeouts.artifact',
66
68
  'worktrees.root',
67
69
  ];
68
70
  const CONFIG_CATALOG = [
@@ -583,6 +585,8 @@ const DEFAULT_DEPENDENCIES = {
583
585
  };
584
586
  function isConfigKey(value) {
585
587
  return (KEY_ORDER.includes(value) ||
588
+ value === 'workflow.dispatchCeiling' ||
589
+ value === 'workflow.dispatchCeiling.providers' ||
586
590
  isDispatchCeilingProviderKey(value));
587
591
  }
588
592
  function isDispatchCeilingProviderKey(value) {
@@ -758,6 +762,14 @@ function parseWorkflowValue(key, rawValue) {
758
762
  return sequence;
759
763
  }
760
764
  }
765
+ if (key === 'workflow.gateTimeouts.code' ||
766
+ key === 'workflow.gateTimeouts.artifact') {
767
+ const value = Number(rawValue);
768
+ if (!isValidGateTimeoutMs(value)) {
769
+ throw new Error(`Invalid value for ${key}: expected an integer between ${MIN_GATE_TIMEOUT_MS} and ${MAX_GATE_TIMEOUT_MS}`);
770
+ }
771
+ return value;
772
+ }
761
773
  const allowed = WORKFLOW_ENUM_VALUES[key];
762
774
  if (allowed) {
763
775
  const normalized = rawValue.trim();
@@ -875,6 +887,16 @@ function applyWorkflowValue(workflow, key, value) {
875
887
  postImplementSequence: value,
876
888
  };
877
889
  }
890
+ if (subKey.startsWith('gateTimeouts.')) {
891
+ const timeoutKey = subKey.slice('gateTimeouts.'.length);
892
+ return {
893
+ ...workflow,
894
+ gateTimeouts: {
895
+ ...workflow.gateTimeouts,
896
+ [timeoutKey]: value,
897
+ },
898
+ };
899
+ }
878
900
  if (subKey === 'dispatchPolicy.mode') {
879
901
  const mode = value;
880
902
  if (mode === 'inherit') {
@@ -991,6 +1013,10 @@ async function getConfigValue(repoRoot, userConfigDir, key, dependencies, preser
991
1013
  const resolved = await dependencies.resolveEffectiveConfig(repoRoot, userConfigDir, dependencies.processEnv);
992
1014
  const entry = resolved.resolved[key];
993
1015
  if (!entry) {
1016
+ const aggregate = buildResolvedConfigAggregate(resolved, key);
1017
+ if (aggregate) {
1018
+ return { key, ...aggregate };
1019
+ }
994
1020
  return { key, value: null, source: 'default' };
995
1021
  }
996
1022
  return {
@@ -1001,6 +1027,44 @@ async function getConfigValue(repoRoot, userConfigDir, key, dependencies, preser
1001
1027
  source: entry.source,
1002
1028
  };
1003
1029
  }
1030
+ function buildResolvedConfigAggregate(resolved, key) {
1031
+ const prefix = `${key}.`;
1032
+ const entries = Object.entries(resolved.resolved).filter(([entryKey]) => entryKey.startsWith(prefix));
1033
+ if (entries.length === 0) {
1034
+ return null;
1035
+ }
1036
+ const value = {};
1037
+ let source = 'default';
1038
+ const sourceRank = {
1039
+ default: 0,
1040
+ user: 1,
1041
+ shared: 2,
1042
+ local: 3,
1043
+ env: 4,
1044
+ };
1045
+ for (const [entryKey, entry] of entries) {
1046
+ if (sourceRank[entry.source] > sourceRank[source]) {
1047
+ source = entry.source;
1048
+ }
1049
+ const parts = entryKey.slice(prefix.length).split('.');
1050
+ let cursor = value;
1051
+ for (const [index, part] of parts.entries()) {
1052
+ if (index === parts.length - 1) {
1053
+ cursor[part] = entry.value;
1054
+ }
1055
+ else {
1056
+ const nested = cursor[part];
1057
+ if (typeof nested !== 'object' ||
1058
+ nested === null ||
1059
+ Array.isArray(nested)) {
1060
+ cursor[part] = {};
1061
+ }
1062
+ cursor = cursor[part];
1063
+ }
1064
+ }
1065
+ }
1066
+ return { value, source };
1067
+ }
1004
1068
  async function listConfigKeys(repoRoot, userConfigDir, dependencies) {
1005
1069
  const resolved = await dependencies.resolveEffectiveConfig(repoRoot, userConfigDir, dependencies.processEnv);
1006
1070
  const staticKeys = new Set(KEY_ORDER);
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=fake-runtime.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-runtime.d.mts","sourceRoot":"","sources":["../../../../src/commands/gate/__fixtures__/fake-runtime.mjs"],"names":[],"mappings":""}