@open-agent-toolkit/cli 0.1.41 → 0.1.42

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-phase-implementer
3
- version: 1.0.2
3
+ version: 1.0.3
4
4
  description: Implements a single plan phase end-to-end — reads artifacts once, executes tasks sequentially, commits per task, self-reviews, and returns a structured summary. Dispatched by oat-project-implement.
5
5
  tools: Read, Write, Edit, Bash, Grep, Glob
6
6
  color: cyan
@@ -46,7 +46,7 @@ For Codex, `provider-default` means the base/unpinned role follows Codex configu
46
46
 
47
47
  If `mode: fix`, the block also includes:
48
48
 
49
- - **review_artifact**: Path to the review artifact from the reviewer (e.g., `reviews/p02-review-YYYY-MM-DD.md`)
49
+ - **review_artifact**: Path to the review artifact from the reviewer (e.g., `reviews/p02-review-2026-01-28T140322Z.md`)
50
50
  - **findings**: Critical and Important findings list
51
51
  - **prior_summary**: Your own prior `implement` run summary (what was previously built)
52
52
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-reviewer
3
- version: 1.1.3
3
+ version: 1.1.4
4
4
  description: Unified reviewer for OAT projects - mode-aware verification of requirements/design alignment and code quality. Writes a review artifact to disk by default, or returns structured findings in-memory when dispatched in structured-output mode.
5
5
  tools: Read, Bash, Grep, Glob, Write
6
6
  color: yellow
@@ -287,19 +287,21 @@ Write the review artifact to the specified path.
287
287
 
288
288
  **File path format:**
289
289
 
290
- - Phase review: `{project}/reviews/pNN-review-YYYY-MM-DD.md`
291
- - Final review: `{project}/reviews/final-review-YYYY-MM-DD.md`
292
- - Task review: `{project}/reviews/pNN-tNN-review-YYYY-MM-DD.md`
293
- - Range review: `{project}/reviews/range-review-YYYY-MM-DD.md`
290
+ Use a seconds-precision **UTC** timestamp token (`YYYY-MM-DDTHHMMSSZ`, from `date -u +%Y-%m-%dT%H%M%SZ` — the `-u` and the trailing `Z` are mandatory) so same-scope, same-day re-reviews never collide and always sort by recency. Never emit a local-time or `Z`-less timestamp: a timezone-less datetime mis-orders artifacts written by agents in different timezones.
294
291
 
295
- **If file already exists for today:** add `-v2`, `-v3`, etc.
292
+ - Phase review: `{project}/reviews/pNN-review-YYYY-MM-DDTHHMMSSZ.md`
293
+ - Final review: `{project}/reviews/final-review-YYYY-MM-DDTHHMMSSZ.md`
294
+ - Task review: `{project}/reviews/pNN-tNN-review-YYYY-MM-DDTHHMMSSZ.md`
295
+ - Range review: `{project}/reviews/range-review-YYYY-MM-DDTHHMMSSZ.md`
296
+
297
+ The timestamp token must match the `oat_generated_at` frontmatter for the same run. In the unlikely event a file with that exact second already exists, append `-v2`, `-v3`, etc.
296
298
 
297
299
  **Review artifact template:**
298
300
 
299
301
  ````markdown
300
302
  ---
301
303
  oat_generated: true
302
- oat_generated_at: YYYY-MM-DD
304
+ oat_generated_at: YYYY-MM-DDTHH:MM:SSZ
303
305
  oat_review_scope: { scope }
304
306
  oat_review_type: { code|artifact|analysis }
305
307
  oat_review_invocation: { manual|auto|gate }
@@ -308,7 +310,7 @@ oat_project: { project-path }
308
310
 
309
311
  # {Code|Artifact|Analysis} Review: {scope}
310
312
 
311
- **Reviewed:** YYYY-MM-DD
313
+ **Reviewed:** YYYY-MM-DDTHH:MM:SSZ
312
314
  **Scope:** {scope description}
313
315
  **Files reviewed:** {N}
314
316
  **Commits:** {range or count}
@@ -71,6 +71,61 @@ status when the configured threshold is met. `cross-provider-exec` does not do
71
71
  that interpretation; for generic prompts it still returns only the child process
72
72
  status.
73
73
 
74
+ `oat-project-implement` uses `oat gate review` per phase when a project opts in
75
+ via the `oat_phase_review_gate` plan frontmatter — a non-pausing gate that runs
76
+ after each selected phase's standard reviewer passes. See
77
+ [Reviews → Phase review gate](../workflows/projects/reviews.md#phase-review-gate)
78
+ for the frontmatter contract and how passing versus blocking gates are
79
+ dispositioned.
80
+
81
+ ### Gate completion signal
82
+
83
+ The canonical "how do I know the gate finished" signal is the structured result
84
+ `oat --json gate review` writes to stdout on exit, together with the process
85
+ exit code. Orchestrators should run the gate synchronously and read that
86
+ envelope — do **not** poll the `reviews/` directory for a file to appear or
87
+ watch the provider process's log for liveness. Filesystem and log-liveness
88
+ heuristics are unreliable: a re-gate can momentarily surface a prior round's
89
+ artifact, and a lingering provider side-process says nothing about whether the
90
+ review committed.
91
+
92
+ Every terminal envelope carries a `runId` (unique per gate invocation) and,
93
+ once an artifact exists, its `generatedAt` (the artifact's seconds-precision
94
+ `oat_generated_at`), so a caller can correlate the result to the exact artifact
95
+ and disambiguate re-gate rounds:
96
+
97
+ | `status` | Exit | Meaning |
98
+ | ---------------------------- | ---- | --------------------------------------------------------- |
99
+ | `ok` | 0 | Review completed; gate passed at the threshold. |
100
+ | `blocked` | 1 | Review completed; findings at/above the threshold. |
101
+ | `review_failed` | ≠0 | The provider target exited non-zero; no verdict. |
102
+ | `artifact_validation_failed` | 1 | Provider ran but the review artifact could not be parsed. |
103
+
104
+ `ok` and `blocked` also include `outcome`, `artifactPath`, `counts`, `scope`,
105
+ and `handoff`. Treat any status other than `ok`/`blocked` as an operational
106
+ failure, not a passing gate.
107
+
108
+ **Drive gates through `oat gate review`, not raw provider invocation.** An
109
+ orchestrator that hand-rolls the review (for example, calling
110
+ `codex exec … oat-project-review-provide <scope>` directly) and then watches
111
+ `reviews/` for a file is reimplementing — less reliably — what the CLI already
112
+ does: `oat gate review` snapshots the reviews directory, dispatches the
113
+ provider, and attributes the produced artifact by content hash, so it is immune
114
+ to a stale file lingering from a prior round. It works standalone, not only
115
+ inside the `oat-project-implement` auto-loop — a one-off final review is just:
116
+
117
+ ```bash
118
+ oat --json gate review \
119
+ --project "$PROJECT_PATH" \
120
+ --review-type code \
121
+ --review-scope final \
122
+ --exit-nonzero-on important \
123
+ 'Use oat-project-review-provide code final to review the current project'
124
+ ```
125
+
126
+ Read the resulting envelope and exit code; that is the whole completion
127
+ contract.
128
+
74
129
  ## Exec targets
75
130
 
76
131
  `oat gate cross-provider-exec` chooses from `workflow.gates.execTargets`.
@@ -68,7 +68,7 @@ Notable commands introduced in the current CLI surface:
68
68
  - `oat repo archive sync [project-name]` - hydrate archived project snapshots from the configured repo-scoped S3 archive into `.oat/projects/archived/`. The old `oat project archive sync` path remains as a deprecated shim.
69
69
  - `oat project validate-plan --project-path <path>` - validates `oat_plan_parallel_groups` metadata in `plan.md`; exits non-zero on invalid. See [Implementation Execution](../workflows/projects/implementation-execution.md#validating-plan-metadata).
70
70
  - `oat project set-mode` — deprecated no-op. Execution mode is no longer user-selectable; emits a deprecation warning and preserves the `--json` contract.
71
- - `oat gate review <prompt...>` - run a stateful OAT review through the target registry, parse the produced review artifact, and exit nonzero for configured blocking findings; produced gate reviews use `oat_review_invocation: gate` and still require `oat-project-review-receive` handoff before they are dispositioned.
71
+ - `oat gate review <prompt...>` - run a stateful OAT review through the target registry, parse the produced review artifact, and exit nonzero for configured blocking findings; produced gate reviews use `oat_review_invocation: gate` and still require `oat-project-review-receive` handoff before they are dispositioned. With `--json`, the result envelope on exit is the canonical completion signal — `status` (`ok` | `blocked` | `review_failed` | `artifact_validation_failed`), `runId`, `generatedAt`, `artifactPath` — so orchestrators should read it rather than poll the filesystem. Runs standalone (e.g. `--review-scope final`), not only inside `oat-project-implement`. See [Workflow Gates → Gate completion signal](../cli-utilities/workflow-gates.md#gate-completion-signal).
72
72
  - `oat gate cross-provider-exec <prompt...>` - choose an available exec target while avoiding the current runtime by default, then run the prompt with the chosen target's configured base command and exit with the child status.
73
73
 
74
74
  ## `oat config` surface flags
@@ -57,6 +57,7 @@ See [Project Splitting](splitting.md) for the full parent/child model.
57
57
 
58
58
  - `oat_plan_hill_phases` — list of phase IDs to pause at for HiLL checkpoints.
59
59
  - `oat_plan_parallel_groups` — declares which phases may execute concurrently in worktrees. See below.
60
+ - `oat_phase_review_gate` — opt-in non-pausing external review gate that runs after a phase's standard reviewer passes. See below.
60
61
 
61
62
  #### oat_plan_parallel_groups
62
63
 
@@ -85,6 +86,27 @@ Each inner array is a group of phases that execute concurrently in their own wor
85
86
 
86
87
  Before dispatching, `oat-project-implement` invokes `oat project validate-plan --project-path "${PROJECT_PATH}"`. Non-zero exit blocks the run. See [CLI Reference](../../reference/cli-reference.md) and [Implementation Execution](implementation-execution.md) for details.
87
88
 
89
+ #### oat_phase_review_gate
90
+
91
+ Enable an optional, non-pausing external review gate that runs after each selected phase's standard reviewer passes and its bookkeeping is committed:
92
+
93
+ ```yaml
94
+ oat_phase_review_gate:
95
+ enabled: true
96
+ phases: [] # empty or omitted = every implementation phase
97
+ review_type: code
98
+ exit_nonzero_on: important
99
+ ```
100
+
101
+ **Semantics:**
102
+
103
+ - Missing, `null`, or `enabled: false` → disabled (default). `enabled: true` activates the gate.
104
+ - `phases` → optional. Empty or missing runs the gate after every implementation phase; a populated list restricts it to those phase IDs, each of which must exist in the plan body.
105
+ - `review_type` → optional, defaults to `code`. Only `code` is supported for phase gates.
106
+ - `exit_nonzero_on` → optional, defaults to `important`. One of `critical`, `important`, `medium`, `minor`. This is the blocking threshold: findings at or above it stop the phase; sub-threshold findings are dispositioned by the judgment sweep rather than ignored.
107
+
108
+ A malformed gate stops the run before task execution rather than being silently disabled. The gate is independent of HiLL checkpoints and reuses the existing `oat gate review` target config — it does not hardcode a `--target`. See [Reviews → Phase review gate](reviews.md#phase-review-gate) for the runtime behavior and disposition rules.
109
+
88
110
  ## Reference artifacts
89
111
 
90
112
  - `.oat/templates/*.md`
@@ -10,6 +10,8 @@ OAT supports two checkpoint classes:
10
10
  - Workflow phase checkpoints
11
11
  - Plan phase checkpoints
12
12
 
13
+ The [phase review gate](reviews.md#phase-review-gate) (`oat_phase_review_gate`) is a separate, non-pausing mechanism — it does not pause on a passing gate and never modifies the HiLL keys below (`oat_hill_completed`, `oat_plan_hill_phases`).
14
+
13
15
  ## Workflow checkpoints (`state.md`)
14
16
 
15
17
  Frontmatter keys:
@@ -187,7 +187,8 @@ For each phase in the plan (whether sequential or inside a parallel group):
187
187
  5. **Parse the verdict:** zero Critical + zero Important findings → `pass`; otherwise `fail`.
188
188
  6. **On fail, run the bounded fix loop** (see below).
189
189
  7. **Update artifacts** (`implementation.md`, `plan.md` review row, `state.md`) and make the mandatory bookkeeping commit.
190
- 8. **HiLL checkpoint** if the phase id is listed in `oat_plan_hill_phases`.
190
+ 8. **Phase review gate** when `oat_phase_review_gate` selects the phase, run the optional non-pausing external gate (`oat gate review`) after the step 7 bookkeeping commit and before the HiLL check. A passing gate continues automatically; a blocking gate is received and fixed, reusing the bounded fix loop's `oat_orchestration_retry_limit`. See [Reviews → Phase review gate](reviews.md#phase-review-gate).
191
+ 9. **HiLL checkpoint** if the phase id is listed in `oat_plan_hill_phases`.
191
192
 
192
193
  ### Bounded fix loop
193
194
 
@@ -107,6 +107,23 @@ Auto-triggered reviews use `oat_review_invocation: auto` in the review artifact
107
107
 
108
108
  This feature is opt-in and disabled by default. When disabled, the manual `oat-project-review-provide` workflow applies.
109
109
 
110
+ ## Phase review gate
111
+
112
+ The phase review gate is an optional, **non-pausing** external review gate that runs after a phase's standard per-phase reviewer passes and the phase bookkeeping is committed. Where the Tier 1 reviewer is an in-session self-review, the gate calls `oat gate review` against the configured cross-provider target, adding an independent perspective before implementation moves to the next phase. It is enabled per-project through `plan.md` frontmatter (`oat_phase_review_gate`; see [Project Artifacts](artifacts.md#oat_phase_review_gate) for the field shape and validation).
113
+
114
+ It is independent of [HiLL checkpoints](hill-checkpoints.md): a passing gate does not pause, and the gate never touches `oat_hill_completed`, `oat_plan_hill_phases`, or `oat_auto_review_at_hill_checkpoints`.
115
+
116
+ Gate-produced review artifacts use `oat_review_invocation: gate` in frontmatter (the third invocation marker alongside `manual` and `auto`). The gate verdict — controlled by `exit_nonzero_on` (default `important`) — decides whether the **phase stops**; it does not decide whether sub-threshold findings are ignored. Either way the produced artifact is consumed by `oat-project-review-receive`, autonomously and without user prompts, so findings never evaporate:
117
+
118
+ - **Passing gate** (no findings at or above the threshold): receive runs a non-pausing **judgment sweep**. It makes a per-finding decision for each Medium/Minor — defer to final review (the default, recorded so [final review](#phase-and-final-review) resurfaces it), address now (only for small, contained, low-risk fixes, which do **not** re-trigger the standard reviewer or re-gate the phase), or reject with rationale — then archives the artifact. Address-now is an exception, not the norm; if such a fix reveals a Critical/Important concern it escalates to the blocking path.
119
+ - **Blocking gate** (one or more findings at or above the threshold): receive converts findings to fix tasks and implementation re-runs the standard reviewer and the gate for the phase. These block → fix → re-gate rounds are bounded by `oat_orchestration_retry_limit` (default `2`); exhausting the bound stops a sequential run or excludes the phase in a parallel group, matching the standard fix loop's terminal handling.
120
+
121
+ Gate-originated artifacts (`oat_review_invocation: gate`) are excluded from the same-scope review-cycle cap in `oat-project-review-receive`. The cap measures failed fix cycles of the standard review loop, so counting gate artifacts would trip it on artifact volume rather than real fix rounds.
122
+
123
+ When a phase is re-gated multiple times, each round produces a distinct review artifact — filenames and `oat_generated_at` are seconds-precision, so rounds never collide and `oat review latest` resolves the newest. An orchestrator should know a gate finished from the `oat --json gate review` result envelope on process exit (`status`, `runId`, `generatedAt`), not by watching the `reviews/` directory or the provider's log; see [Gate completion signal](../../cli-utilities/workflow-gates.md#gate-completion-signal).
124
+
125
+ This feature is opt-in and disabled by default (missing or `enabled: false`). For a parallel phase group, selected gates run after fan-in and bookkeeping, one per merged phase in plan order.
126
+
110
127
  ## Auto artifact-review loops
111
128
 
112
129
  Generated planning and analysis artifacts have a separate review loop from code/phase reviews.
@@ -1,6 +1,6 @@
1
1
  {
2
- "cli": "0.1.41",
3
- "docs-config": "0.1.41",
4
- "docs-theme": "0.1.41",
5
- "docs-transforms": "0.1.41"
2
+ "cli": "0.1.42",
3
+ "docs-config": "0.1.42",
4
+ "docs-theme": "0.1.42",
5
+ "docs-transforms": "0.1.42"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-implement
3
- version: 2.0.24
3
+ version: 2.0.25
4
4
  description: Use when plan.md is ready for execution. Dispatches phase-level subagents with bounded fix loops; supports plan-declared parallel phase groups with worktree-isolated execution and ordered fan-in.
5
5
  oat_gateable: true
6
6
  argument-hint: '[--retry-limit <N>] [--dry-run]'
@@ -679,6 +679,39 @@ This setting controls only the extra `oat-project-review-provide` lifecycle revi
679
679
 
680
680
  **On resume:** If `oat_auto_review_at_hill_checkpoints` is already present in plan.md frontmatter, skip Touchpoint A entirely — do not re-ask, do not re-read config, do not print the auto-review note. The stored value is authoritative. If only legacy `oat_auto_review_at_checkpoints` is present, treat it as authoritative for this run and write the new `oat_auto_review_at_hill_checkpoints` key on the next plan frontmatter update.
681
681
 
682
+ ### Step 2.6: Validate Optional Phase Review Gate
683
+
684
+ Read `oat_phase_review_gate` from `"$PROJECT_PATH/plan.md"` frontmatter when present.
685
+
686
+ This is the plan-level `phaseReviewGate` setting: an optional, non-pausing external lifecycle review gate that runs after a phase's standard per-phase self-review passes. It uses the existing `oat gate review` target configuration to run a cross-provider review, then maps the produced review artifact to a blocking/non-blocking gate result.
687
+
688
+ Valid shape:
689
+
690
+ ```yaml
691
+ oat_phase_review_gate:
692
+ enabled: true
693
+ phases: [] # empty or omitted = every implementation phase
694
+ review_type: code
695
+ exit_nonzero_on: important
696
+ ```
697
+
698
+ Validation rules:
699
+
700
+ - Missing, `null`, or `enabled: false` means disabled.
701
+ - `enabled: true` activates the gate.
702
+ - `phases` is optional. If missing or empty (`[]`), run after every implementation phase. If populated, every value must be a known plan phase ID.
703
+ - `review_type` is optional and defaults to `code`. This skill only supports `code` phase gates; any other value is invalid for implementation phase execution.
704
+ - `exit_nonzero_on` is optional and defaults to `important`. Allowed values: `critical`, `important`, `medium`, `minor`.
705
+
706
+ If the setting is invalid, stop before task execution and ask the user to repair `plan.md`. Do not silently disable a malformed gate.
707
+
708
+ This setting is independent from HiLL checkpoints:
709
+
710
+ - It does not pause when the gate passes.
711
+ - It does not append to `oat_hill_completed`.
712
+ - It does not alter `oat_plan_hill_phases` or `oat_auto_review_at_hill_checkpoints`.
713
+ - It uses the existing gate target config; do not hardcode `--target` in reusable plan execution unless the user explicitly asks for manual/debug routing.
714
+
682
715
  ### Step 3: Check Implementation State
683
716
 
684
717
  Check if implementation already started:
@@ -892,6 +925,43 @@ On reviewer verdict `fail`, run a bounded fix loop.
892
925
  - **Sequential mode:** STOP the run. Surface to user with phase ID, unresolved findings, review artifact path. Do not proceed to subsequent phases.
893
926
  - **Parallel group mode:** mark the phase `excluded`. Do not merge its worktree. Continue the remaining phases in the group. Report in Outstanding Items after the group completes.
894
927
 
928
+ ### Optional External Phase Review Gate
929
+
930
+ After the standard per-phase reviewer passes and after the required phase bookkeeping commit is cleanly recorded, check `oat_phase_review_gate`.
931
+
932
+ If the gate is enabled and the current phase is selected:
933
+
934
+ 1. Run the gate from the orchestration branch with the active project path:
935
+
936
+ ```bash
937
+ oat --json gate review \
938
+ --project "$PROJECT_PATH" \
939
+ --review-type code \
940
+ --review-scope "{pNN}" \
941
+ --exit-nonzero-on "{threshold}" \
942
+ '$oat-project-review-provide code {pNN}'
943
+ ```
944
+
945
+ - `{threshold}` comes from `oat_phase_review_gate.exit_nonzero_on` (default: `important`).
946
+ - `{pNN}` is the completed phase ID.
947
+ - Do not pass `--target` in normal execution; the existing gate config selects the cross-provider target.
948
+ - The gate CLI injects gate context into the review prompt. The produced review artifact must use `oat_review_invocation: gate`.
949
+
950
+ 2. Parse the JSON result. The gate verdict (`exit_nonzero_on: {threshold}`) decides whether the phase **stops**; it does not decide whether sub-threshold findings are ignored. Either way the produced artifact must be **consumed** — passing gate artifacts are not left unprocessed at the top level of `reviews/`.
951
+ - `status: "ok"` / exit code `0` means the phase gate passed at the configured threshold, so the phase does not stop. Still run `oat-project-review-receive` for the reported artifact path in non-pausing **judgment-sweep** mode (pass gate-passed context so receive selects sweep disposition). The sweep makes a per-finding judgment for each Medium/Minor — defer to final (default), address now (small/contained/low-risk fixes only), or reject with rationale — writes those durable dispositions into `implementation.md`, and archives the artifact. Then continue without pausing. Address-now fixes from a passing gate do **not** re-trigger the standard reviewer or re-gate the phase.
952
+ - `status: "blocked"` / non-zero exit due to review findings means blocking findings exist. Run `oat-project-review-receive` for the reported artifact path (blocking disposition) before treating the gate review as consumed.
953
+ - A non-zero exit caused by target execution failure, artifact validation failure, or missing review artifact is an operational failure. Stop and surface the gate output; do not continue as if the gate passed.
954
+
955
+ 3. If `oat-project-review-receive` adds fix tasks (blocking gate, or a sweep address-now fix that revealed a Critical/Important concern):
956
+ - Return to task execution for the newly added review-fix tasks.
957
+ - After fixes land, re-run the standard per-phase reviewer and this external phase gate for the same phase.
958
+ - Continue only after both the standard reviewer and the external phase gate pass.
959
+ - Bound these gate block → fix → re-gate rounds by `oat_orchestration_retry_limit` (from `state.md`, default `2`). If the limit is exhausted with the gate still blocking, apply the same terminal handling as the standard bounded fix loop: **sequential mode** stops the run and surfaces the phase ID, unresolved findings, and artifact path; **parallel group mode** marks the phase `excluded` and reports it in Outstanding Items.
960
+
961
+ 4. If the judgment sweep (passing gate) records only deferrals/rejections and no blocking fix tasks, record the receive result and continue.
962
+
963
+ For a parallel group, run selected phase gates after fan-in and bookkeeping, one gate per successfully merged phase in plan order. If a phase gate blocks, stop the schedule and process that gate's review before starting later schedule entries.
964
+
895
965
  ### Parallel Group Execution
896
966
 
897
967
  When the current schedule entry is a multi-phase group, execute as follows.
@@ -966,7 +1036,7 @@ When the current schedule entry is a multi-phase group, execute as follows.
966
1036
 
967
1037
  For phases that were excluded (fix-loop exhausted), preserve the worktree and log its path in `implementation.md` Outstanding Items.
968
1038
 
969
- 8. **Bookkeeping commit** after the group completes. Then HiLL checkpoint check.
1039
+ 8. **Bookkeeping commit** after the group completes. Then run any selected external phase review gates. After those gates pass, perform the HiLL checkpoint check.
970
1040
 
971
1041
  ### Step 7: Artifact Updates After Each Phase (or Group)
972
1042
 
@@ -1039,7 +1109,7 @@ git add {PROJECT_PATH}/implementation.md {PROJECT_PATH}/state.md {PROJECT_PATH}/
1039
1109
  git commit -m "chore(oat): bookkeeping after {pNN} {pass|fail}"
1040
1110
  ```
1041
1111
 
1042
- Then check HiLL checkpoint — if the phase ID is in `oat_plan_hill_phases`, pause for user approval before continuing.
1112
+ Then run the optional external phase review gate for the completed phase when `oat_phase_review_gate` selects it. After the gate passes or is skipped, check HiLL checkpoint — if the phase ID is in `oat_plan_hill_phases`, pause for user approval before continuing.
1043
1113
 
1044
1114
  ### Step 8: Check Plan Phase Completion
1045
1115
 
@@ -1130,6 +1200,7 @@ Do not use `git add -A` or glob patterns. Only commit the three project artifact
1130
1200
 
1131
1201
  - **Workflow HiLL** (`oat_hill_checkpoints` in state.md): Gates between workflow phases (discovery → spec → design → plan → implement). Checked by oat-project-progress router.
1132
1202
  - **Plan phase checkpoints** (`oat_plan_hill_phases` in plan.md): Gates at plan phase boundaries during implementation. `[]` means pause after every phase; a populated array pauses only after listed phases. The field may be absent only before the first implementation-run confirmation. Listed phases are where you stop AFTER completing them.
1203
+ - **Phase review gate** (`oat_phase_review_gate` in plan.md): Optional non-pausing external review gate after a completed phase passes the standard reviewer. Missing/disabled means skip; `phases: []` means gate every implementation phase. Passing gates continue automatically; blocking gates are received/fixed before execution proceeds.
1133
1204
 
1134
1205
  **Revision phase completion handling:**
1135
1206
 
@@ -1296,7 +1367,7 @@ echo "$FINAL_ROW"
1296
1367
  **If final review row exists and status is `passed`:**
1297
1368
 
1298
1369
  - Example row:
1299
- - `| final | code | passed | 2026-01-28 | reviews/final-review-2026-01-28.md |`
1370
+ - `| final | code | passed | 2026-01-28 | reviews/final-review-2026-01-28T140322Z.md |`
1300
1371
  - Check:
1301
1372
  ```bash
1302
1373
  echo "$FINAL_ROW" | grep -qE "^\\|\\s*final\\s*\\|.*\\|\\s*passed\\s*\\|" && echo "passed"
@@ -1446,7 +1517,7 @@ Final verification:
1446
1517
 
1447
1518
  Final review:
1448
1519
  - Status: ✓ passed
1449
- - Artifact: reviews/final-review-{date}.md
1520
+ - Artifact: reviews/final-review-{timestamp}.md
1450
1521
 
1451
1522
  Next: Create PR or run the oat-project-pr-final skill (when available)
1452
1523
  ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-review-provide
3
- version: 1.3.8
3
+ version: 1.3.10
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
@@ -25,11 +25,13 @@ Reviewers should distinguish implementation defects from artifact drift. If code
25
25
 
26
26
  ## Model Invocation Gate
27
27
 
28
- This skill is model-invokable only for explicit review asks such as "review project" or "review the project", or when the user confirms a previously offered project-review step. Do NOT auto-invoke merely because a task, phase, or implementation appears complete.
28
+ This skill is model-invokable only for explicit review asks such as "review project" or "review the project", or when the user confirms a previously offered project-review step. A gate-originated request from `oat gate review` is also an explicit review ask. Do NOT auto-invoke merely because a task, phase, or implementation appears complete.
29
29
 
30
30
  Before acting, verify that there is an active OAT project or a user-provided review target that can be resolved to project state. If neither exists, do not run this skill; offer `oat-project-open` / `oat-project-quick-start` for project workflow setup, or `oat-review-provide` for a non-project ad-hoc review.
31
31
 
32
- When the gate passes, summarize the inferred review type and scope, then ask before running the review.
32
+ When the invocation is manual, summarize the inferred review type and scope, then ask before running the review.
33
+
34
+ **Gate-originated mode:** If the request context says the review is gate-originated, set `REVIEW_INVOCATION=gate`, honor any provided review type/scope arguments, and run without interactive confirmation prompts. The gate session is already the explicit authorization boundary. If subagent dispatch is unavailable, run the review inline in the current gate session instead of handing off to a fresh session.
33
35
 
34
36
  ## Mode Assertion
35
37
 
@@ -123,7 +125,7 @@ Tell user:
123
125
  - `oat-project-quick-start` (new quick project)
124
126
  - `oat-project-import-plan` (external plan import)
125
127
 
126
- If validation passes, derive `{project-name}` as basename of `PROJECT_PATH`. Summarize the resolved project/review target and ask before continuing to Step 1.
128
+ If validation passes, derive `{project-name}` as basename of `PROJECT_PATH`. Summarize the resolved project/review target. For manual invocation, ask before continuing to Step 1. For `REVIEW_INVOCATION=gate`, continue without asking.
127
129
 
128
130
  ### Step 1: Parse Arguments or Ask
129
131
 
@@ -424,6 +426,8 @@ Files changed: {FILE_COUNT}
424
426
  Proceed with review?
425
427
  ```
426
428
 
429
+ If `REVIEW_INVOCATION=gate`, print the same scope summary but do not ask `Proceed with review?`; continue directly.
430
+
427
431
  ### Step 4.1: Dispatch Profile Override Advisory (Artifact Plan Only)
428
432
 
429
433
  When reviewing `artifact plan`, apply this Dispatch Profile override advisory:
@@ -536,6 +540,7 @@ Detection logic:
536
540
  - If the runtime can dispatch reviewer work (`subagent_type` in Claude Code, Cursor invocation via `/name` or natural mention, or Codex multi-agent spawn/auto-spawn) → **Tier 1**.
537
541
  - If the Task tool is not available or subagent dispatch is not supported → **Tier 2**.
538
542
  - If user explicitly requests inline or confirms they are already in a fresh session → **Tier 3**.
543
+ - If `REVIEW_INVOCATION=gate` and Tier 1 is unavailable, skip Tier 2 and use **Tier 3** inline. Do not return fresh-session instructions from a gate invocation.
539
544
 
540
545
  **Step 6b: Tier 1 — Subagent (if available)**
541
546
 
@@ -566,6 +571,7 @@ If subagent not available:
566
571
  - If user is already in a fresh session (confirmed), proceed to Tier 3.
567
572
  - If Codex reported `authorization required` and the user approved delegation, do **not** use Tier 2. Return to Tier 1 and delegate to `oat-reviewer`.
568
573
  - If user prefers fresh session: provide instructions and exit.
574
+ - If `REVIEW_INVOCATION=gate`: do not provide fresh-session instructions; run Tier 3 inline instead.
569
575
 
570
576
  Instructions for fresh session:
571
577
 
@@ -599,13 +605,15 @@ Review storage contract:
599
605
 
600
606
  **Naming convention:**
601
607
 
602
- - Phase review: `{PROJECT_PATH}/reviews/pNN-review-YYYY-MM-DD.md`
603
- - Task review: `{PROJECT_PATH}/reviews/pNN-tNN-review-YYYY-MM-DD.md`
604
- - Final review: `{PROJECT_PATH}/reviews/final-review-YYYY-MM-DD.md`
605
- - Range review: `{PROJECT_PATH}/reviews/range-review-YYYY-MM-DD.md`
606
- - Artifact review: `{PROJECT_PATH}/reviews/artifact-{artifact}-review-YYYY-MM-DD.md`
608
+ Use a seconds-precision **UTC** timestamp token (`YYYY-MM-DDTHHMMSSZ`, from `date -u +%Y-%m-%dT%H%M%SZ` — the `-u` and the trailing `Z` are mandatory), not a date-only or local-time stamp, so that same-scope re-reviews and re-gates within one day never collide and `oat review latest` orders them by recency:
609
+
610
+ - Phase review: `{PROJECT_PATH}/reviews/pNN-review-YYYY-MM-DDTHHMMSSZ.md`
611
+ - Task review: `{PROJECT_PATH}/reviews/pNN-tNN-review-YYYY-MM-DDTHHMMSSZ.md`
612
+ - Final review: `{PROJECT_PATH}/reviews/final-review-YYYY-MM-DDTHHMMSSZ.md`
613
+ - Range review: `{PROJECT_PATH}/reviews/range-review-YYYY-MM-DDTHHMMSSZ.md`
614
+ - Artifact review: `{PROJECT_PATH}/reviews/artifact-{artifact}-review-YYYY-MM-DDTHHMMSSZ.md`
607
615
 
608
- **If file exists for today:** append `-v2`, `-v3`, etc.
616
+ Set `oat_generated_at` in the artifact frontmatter to the matching full timestamp (`YYYY-MM-DDTHH:MM:SSZ`). Same-second collisions are effectively impossible for sequential runs; if one occurs, append `-v2`, `-v3`, etc.
609
617
 
610
618
  **Important:** `PROJECT_PATH` here must be the resolved path from Step 1.5. If a worktree was detected, this path is relative to the worktree root, ensuring the artifact is written on the correct branch.
611
619
 
@@ -636,7 +644,7 @@ Shared ad-hoc companion reference (non-project mode):
636
644
  ```markdown
637
645
  ---
638
646
  oat_generated: true
639
- oat_generated_at: { today }
647
+ oat_generated_at: { full UTC timestamp, e.g. 2026-07-06T11:16:01Z }
640
648
  oat_review_scope: { scope }
641
649
  oat_review_type: { code|artifact }
642
650
  oat_review_invocation: { manual|auto|gate }
@@ -655,9 +663,13 @@ oat_project: { PROJECT_PATH }
655
663
 
656
664
  - `manual` (default): Review was manually triggered by the user. `oat-project-review-receive` uses standard disposition behavior (user prompts for triage, minors auto-deferred for non-final scopes).
657
665
  - `auto`: Review was spawned by the auto-review checkpoint trigger in `oat-project-implement`. `oat-project-review-receive` uses relaxed disposition: minors are auto-converted to fix tasks (not deferred), no user prompts for disposition decisions.
658
- - `gate`: Review was spawned by `oat gate review`. Gate-originated reviews use normal stateful review-provide behavior: write the review artifact, update the `## Reviews` row, and commit review bookkeeping. `oat-project-review-receive` treats gate reviews with the same standard disposition behavior as manual reviews unless a future implementation explicitly designs autonomous receive.
666
+ - `gate`: Review was spawned by `oat gate review` for a workflow gate. Gate-originated reviews use normal stateful review-provide behavior: write the review artifact, update the `## Reviews` row, and commit review bookkeeping. The gate CLI maps the review artifact findings to exit status, and `oat-project-review-receive` dispositions gate reviews autonomously (no user prompts) — selecting convert-to-fix-tasks when the gate **blocked** at its threshold, or non-pausing judgment-sweep disposition (defer / small-fix-now / reject, with sub-threshold findings recorded durably) when the gate **passed**.
667
+
668
+ When `oat-project-implement` spawns this skill for auto-review at checkpoints, it passes context indicating auto invocation. Set `oat_review_invocation: auto` in the artifact frontmatter.
669
+
670
+ When `oat gate review` invokes this skill, it includes gate-originated context instructing the reviewer to write `oat_review_invocation: gate`. Honor that instruction in the artifact frontmatter.
659
671
 
660
- When `oat-project-implement` spawns this skill for auto-review at checkpoints, it passes context indicating auto invocation. Set `oat_review_invocation: auto` in the artifact frontmatter. When `oat gate review` spawns this skill, set `oat_review_invocation: gate`. For all other invocations (user-triggered, fresh session), use `manual`.
672
+ For all other invocations (user-triggered, fresh session), use `manual`.
661
673
 
662
674
  Gate parsing contract:
663
675
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-review-receive
3
- version: 1.5.4
3
+ version: 1.5.6
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 @@ fi
102
102
 
103
103
  Selection rules:
104
104
 
105
- - 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.
105
+ - 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.
106
106
  - Read the JSON result:
107
107
  - `path: null` means no review target was found.
108
108
  - `kind: "project"` and `actionable: true` means this skill can process the target when the path is an active top-level project review.
@@ -267,15 +267,23 @@ Read `oat_review_type` and `oat_review_invocation` from review artifact frontmat
267
267
  - Require explicit user confirmation before applying any artifact edits.
268
268
  - Resolve findings directly in artifact files; do not convert findings into plan tasks.
269
269
  - Do not defer findings by default. Only use `rejected_with_rationale` for invalid findings, or `needs_user_direction` when user input is required.
270
- - If `oat_review_type == code` AND `oat_review_invocation == auto`:
271
- - **Auto-disposition mode.** This review was spawned by the auto-review checkpoint trigger in `oat-project-implement`. Apply relaxed disposition defaults:
270
+ - If `oat_review_type == code` AND `oat_review_invocation == auto`, OR `oat_review_invocation == gate` from a **blocking** gate (the gate-originated context does not indicate a passing-gate sweep):
271
+ - **Auto-disposition mode.** This review was spawned by the auto-review checkpoint trigger in `oat-project-implement` or by a blocking `oat gate review`. Apply relaxed disposition defaults:
272
272
  - Critical/Important/Medium: convert to fix tasks (same as manual mode)
273
- - Minor: auto-convert to fix tasks unless clearly out of scope (e.g., cosmetic polish unrelated to changed code). Manual mode now also defaults minors to `convert` (see Step 9); auto mode keeps the same intent — fix everything while context is fresh — but without any user prompts.
274
- - **No user prompts for disposition decisions.** The auto-review path runs fully autonomously.
273
+ - Minor: auto-convert to fix tasks unless clearly out of scope (e.g., cosmetic polish unrelated to changed code). Manual mode now also defaults minors to `convert` (see Step 9); auto/gate mode keeps the same intent — fix everything while context is fresh — but without any user prompts.
274
+ - **No user prompts for disposition decisions.** The auto/gate review path runs fully autonomously.
275
275
  - Genuinely ambiguous findings (e.g., a medium the agent disagrees with) are deferred with a note explaining why, rather than pausing for interactive resolution.
276
276
  - Follow the task-conversion flow in Steps 3-10 with these adjusted defaults.
277
- - If `oat_review_type == code` AND `oat_review_invocation == gate`:
278
- - **Gate review mode.** This review was spawned by `oat gate review`, but receive remains standard disposition behavior. Treat `gate` the same as manual for prompts, finding conversion, artifact archival, and bookkeeping unless a future implementation explicitly designs an autonomous receive path.
277
+ - If `oat_review_type == code` AND `oat_review_invocation == gate` from a **passing** gate (the gate-originated context indicates a passing-gate judgment sweep):
278
+ - **Judgment-sweep mode.** The phase gate already passed at its `exit_nonzero_on` threshold, so the phase does not stop. Consume the artifact anyway, so its sub-threshold findings become durable, ordered dispositions in `implementation.md` instead of evaporating. Fully non-pausing; no user prompts.
279
+ - The gate verdict decided whether the phase stops; it did **not** decide whether Medium/Minor findings are ignored. There are, by definition, no unresolved Critical/Important findings in a passing gate (if there were, the gate would have blocked).
280
+ - Make a per-finding **judgment call** for each Medium/Minor — do not mechanically dump them all:
281
+ - **Defer to final** (default): record under "Deferred Findings" (Mediums under "Deferred Findings (Medium)" so Step 8.5 resurfacing picks them up) with concrete rationale.
282
+ - **Address now:** only for small, contained, low-risk fixes. Apply the fix, commit it with the phase bookkeeping, and record the disposition. Do **not** re-run the standard reviewer or re-gate the phase for address-now fixes.
283
+ - **Reject** as false-positive / out-of-scope, with concrete rationale.
284
+ - `address now` is an **exception, not the norm** — when in doubt, defer. Do not let a passing gate drift into behaving like a Medium-blocking gate by habit.
285
+ - **Escalation exception:** if an address-now fix reveals or creates a Critical/Important concern, stop treating it as a sweep item. Convert it to a fix task and return control to the blocking-gate path (`oat-project-implement` re-runs the standard reviewer and the gate for the phase).
286
+ - Do not add blocking fix tasks for deferred or rejected findings. After recording all dispositions, archive the artifact (Step 7.5) and commit review bookkeeping (Step 7.6) as usual.
279
287
  - If `oat_review_type == code` (manual or `oat_review_invocation` absent):
280
288
  - Follow the existing task-conversion flow in Steps 3-10 with standard disposition behavior.
281
289
 
@@ -483,13 +491,15 @@ If the project itself is still untracked because earlier lifecycle steps never c
483
491
 
484
492
  **Bounded loop protection:**
485
493
 
486
- Count how many review cycles have occurred for this scope:
494
+ Count how many review cycles have occurred for this scope. Exclude gate-originated artifacts (`oat_review_invocation: gate`): the cap measures failed fix cycles of the standard review loop, not artifact volume, and phase gate re-runs are governed by the phase review gate flow in `oat-project-implement`, not by this cap.
487
495
 
488
496
  ```bash
489
497
  {
490
498
  find "$PROJECT_PATH/reviews" -maxdepth 1 -type f -name "*$SCOPE_TOKEN*.md" 2>/dev/null
491
499
  find "$PROJECT_PATH/reviews/archived" -maxdepth 1 -type f -name "*$SCOPE_TOKEN*.md" 2>/dev/null
492
- } | wc -l
500
+ } | while IFS= read -r artifact; do
501
+ grep -q "oat_review_invocation: gate" "$artifact" || echo "$artifact"
502
+ done | wc -l
493
503
  ```
494
504
 
495
505
  **If 3 or more cycles:**
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-review-provide
3
- version: 1.2.2
3
+ version: 1.2.3
4
4
  description: Use when you need an ad-hoc review outside an active OAT project lifecycle. Reviews code or artifacts without project phase state, unlike oat-project-review-provide.
5
5
  argument-hint: '[unstaged|staged|base_branch=<branch>|base_sha=<sha>|<sha1>..<sha2>|--files <path1,path2,...>] [--output <path>] [--mode auto|local|tracked|inline]'
6
6
  disable-model-invocation: true
@@ -151,18 +151,20 @@ If user asks for tracked `.oat/repo/reviews` and it is gitignored, warn and ask
151
151
 
152
152
  Derive a **scope slug** from the resolved scope mode so that the filename indicates what was reviewed:
153
153
 
154
- | Scope mode | Slug derivation | Example filename |
155
- | ---------------------- | ------------------------------------ | --------------------------------------------- |
156
- | `base_branch=<branch>` | Current branch name | `ad-hoc-review-2026-02-16-oat-repo.md` |
157
- | `unstaged` | Literal `unstaged` | `ad-hoc-review-2026-02-16-unstaged.md` |
158
- | `staged` | Literal `staged` | `ad-hoc-review-2026-02-16-staged.md` |
159
- | `--files <paths>` | First 2–3 basenames, joined with `-` | `ad-hoc-review-2026-02-16-auth-login.md` |
160
- | `base_sha=<sha>` | Short SHA (7 chars) | `ad-hoc-review-2026-02-16-abc1234.md` |
161
- | `<sha1>..<sha2>` | Both short SHAs joined with `-` | `ad-hoc-review-2026-02-16-abc1234-def5678.md` |
154
+ | Scope mode | Slug derivation | Example filename |
155
+ | ---------------------- | ------------------------------------ | ----------------------------------------------------- |
156
+ | `base_branch=<branch>` | Current branch name | `ad-hoc-review-2026-02-16T140322Z-oat-repo.md` |
157
+ | `unstaged` | Literal `unstaged` | `ad-hoc-review-2026-02-16T140322Z-unstaged.md` |
158
+ | `staged` | Literal `staged` | `ad-hoc-review-2026-02-16T140322Z-staged.md` |
159
+ | `--files <paths>` | First 2–3 basenames, joined with `-` | `ad-hoc-review-2026-02-16T140322Z-auth-login.md` |
160
+ | `base_sha=<sha>` | Short SHA (7 chars) | `ad-hoc-review-2026-02-16T140322Z-abc1234.md` |
161
+ | `<sha1>..<sha2>` | Both short SHAs joined with `-` | `ad-hoc-review-2026-02-16T140322Z-abc1234-def5678.md` |
162
162
 
163
163
  ```bash
164
164
  mkdir -p "$OUTPUT_DIR"
165
- TODAY=$(date +%Y-%m-%d)
165
+ # Seconds-precision UTC token so same-scope same-day re-reviews never collide and
166
+ # order by recency in `oat review latest`. The trailing Z (UTC) is mandatory.
167
+ STAMP=$(date -u +%Y-%m-%dT%H%M%SZ)
166
168
 
167
169
  # Derive SCOPE_SLUG based on scope mode (examples):
168
170
  # base_branch → SCOPE_SLUG=$(git branch --show-current | tr '/' '-')
@@ -174,10 +176,10 @@ TODAY=$(date +%Y-%m-%d)
174
176
  # Sanitize: lowercase, replace non-alphanumeric with '-', collapse runs, trim to 40 chars
175
177
  SCOPE_SLUG=$(echo "$SCOPE_SLUG" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g; s/--*/-/g; s/^-//; s/-$//' | cut -c1-40)
176
178
 
177
- OUT_FILE="$OUTPUT_DIR/ad-hoc-review-${TODAY}-${SCOPE_SLUG}.md"
179
+ OUT_FILE="$OUTPUT_DIR/ad-hoc-review-${STAMP}-${SCOPE_SLUG}.md"
178
180
  ```
179
181
 
180
- If the file already exists (same scope reviewed twice in one day), suffix with `-v2`, `-v3`, etc.
182
+ Set `oat_generated_at` in the artifact frontmatter to the matching full UTC timestamp (`YYYY-MM-DDTHH:MM:SSZ`). Same-second collisions are effectively impossible for sequential runs; if one occurs, suffix with `-v2`, `-v3`, etc.
181
183
 
182
184
  ### Step 4: Run Review
183
185
 
@@ -5,7 +5,7 @@ Use this template for non-project commit-range reviews.
5
5
  ````markdown
6
6
  ---
7
7
  oat_generated: true
8
- oat_generated_at: YYYY-MM-DD
8
+ oat_generated_at: YYYY-MM-DDTHH:MM:SSZ
9
9
  oat_review_type: code
10
10
  oat_review_scope: { scope }
11
11
  oat_review_scope_mode: { files|unstaged|staged|range }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,EAQL,KAAK,UAAU,EAIf,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,UAAU,EAEhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,UAAU,uBAAuB;IAC/B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,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,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,UAAU,EAAE,CACV,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,iBAAiB,KACvB,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AA0CD,UAAU,iBAAiB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,OAAO,EAAE,gBAAgB,GAAG,cAAc,GAAG,SAAS,CAAC;IACvD,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;CAC7B;AAED,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,kBAAkB,GAAG,cAAc,GAAG,MAAM,CAAC;AAMlD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;CACpB;AAyZD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,EAC9C,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,kBAAkB,GACxB,kBAAkB,GAAG,IAAI,CAE3B;AAo6BD,wBAAgB,iBAAiB,CAC/B,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAwKT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAQ9B,OAAO,EAQL,KAAK,UAAU,EAIf,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,UAAU,EAEhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,UAAU,uBAAuB;IAC/B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,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,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,UAAU,EAAE,CACV,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,iBAAiB,KACvB,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AA0CD,UAAU,iBAAiB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,OAAO,EAAE,gBAAgB,GAAG,cAAc,GAAG,SAAS,CAAC;IACvD,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;CAC7B;AAED,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,kBAAkB,GAAG,cAAc,GAAG,MAAM,CAAC;AAMlD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;CACpB;AAyZD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,EAC9C,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,kBAAkB,GACxB,kBAAkB,GAAG,IAAI,CAE3B;AAu7BD,wBAAgB,iBAAiB,CAC/B,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAwKT"}
@@ -1,9 +1,9 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { createHash } from 'node:crypto';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
3
  import { readdir, readFile } from 'node:fs/promises';
4
4
  import { basename, isAbsolute, join, relative } from 'node:path';
5
5
  import { buildCommandContext, } from '../../app/command-context.js';
6
- import { getFrontmatterBlock, getFrontmatterField, } from '../shared/frontmatter.js';
6
+ import { getFrontmatterBlock, getFrontmatterField, parseGeneratedTime, } from '../shared/frontmatter.js';
7
7
  import { readGlobalOptions } from '../shared/shared.utils.js';
8
8
  import { BUILTIN_EXEC_TARGETS, readOatConfig, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
9
9
  import { resolveEffectiveConfig, resolveExecTargets, resolveGate, } from '../../config/resolve.js';
@@ -502,7 +502,7 @@ async function readReviewGateArtifactCandidate(repoRoot, relativePath) {
502
502
  if (!generatedAt) {
503
503
  return null;
504
504
  }
505
- const generatedTime = Date.parse(generatedAt);
505
+ const generatedTime = parseGeneratedTime(generatedAt);
506
506
  if (Number.isNaN(generatedTime)) {
507
507
  return null;
508
508
  }
@@ -616,6 +616,7 @@ function writeReviewGateResult(context, payload) {
616
616
  else {
617
617
  context.logger.info('Review completed and gate passed.');
618
618
  }
619
+ context.logger.info(`Run: ${payload.runId} (generated ${payload.generatedAt})`);
619
620
  context.logger.info(`Review artifact: ${payload.artifactPath}`);
620
621
  if (payload.normalization) {
621
622
  context.logger.info(`Artifact normalized: inserted ${payload.normalization.insertedSeverities.map((severity) => severityDisplayName(severity)).join(', ')} empty Findings section(s).`);
@@ -629,6 +630,7 @@ function writeReviewGateExecutionFailure(context, payload) {
629
630
  context.logger.json({
630
631
  status: 'review_failed',
631
632
  outcome: 'review_did_not_complete',
633
+ runId: payload.runId,
632
634
  target: payload.target,
633
635
  project: payload.project,
634
636
  exitCode: payload.exitCode,
@@ -643,9 +645,11 @@ function writeReviewGateArtifactValidationFailure(context, payload) {
643
645
  context.logger.json({
644
646
  status: 'artifact_validation_failed',
645
647
  outcome: 'review_completed_artifact_validation_failed',
648
+ runId: payload.runId,
646
649
  target: payload.target,
647
650
  project: payload.project,
648
651
  artifactPath: payload.artifactPath,
652
+ generatedAt: payload.generatedAt,
649
653
  message: payload.message,
650
654
  recovery: payload.recovery,
651
655
  });
@@ -755,6 +759,7 @@ async function runCrossProviderExec(prompt, options, context, dependencies) {
755
759
  }
756
760
  }
757
761
  async function runReviewGate(prompt, options, context, dependencies) {
762
+ const runId = randomUUID();
758
763
  try {
759
764
  const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
760
765
  const userConfigDir = join(context.home, '.oat');
@@ -785,6 +790,7 @@ async function runReviewGate(prompt, options, context, dependencies) {
785
790
  const childExitCode = await executeTarget(selected, [reviewPrompt], context, dependencies);
786
791
  if (childExitCode !== 0) {
787
792
  writeReviewGateExecutionFailure(context, {
793
+ runId,
788
794
  target: selected.id,
789
795
  project: projectPath,
790
796
  exitCode: childExitCode,
@@ -799,9 +805,11 @@ async function runReviewGate(prompt, options, context, dependencies) {
799
805
  const producedArtifact = findProducedReviewArtifact(before, after);
800
806
  if (!producedArtifact) {
801
807
  writeReviewGateArtifactValidationFailure(context, {
808
+ runId,
802
809
  target: selected.id,
803
810
  project: projectPath,
804
811
  artifactPath: null,
812
+ generatedAt: null,
805
813
  message: `No new review artifact was detected for project ${projectPath}.`,
806
814
  recovery: 'Ensure the review provider wrote a project review artifact. If it did, fix or attach that artifact and run oat-project-review-receive before treating the review as consumed.',
807
815
  });
@@ -817,9 +825,11 @@ async function runReviewGate(prompt, options, context, dependencies) {
817
825
  catch (error) {
818
826
  const detail = error instanceof Error ? error.message : String(error);
819
827
  writeReviewGateArtifactValidationFailure(context, {
828
+ runId,
820
829
  target: selected.id,
821
830
  project: projectPath,
822
831
  artifactPath: producedArtifact.path,
832
+ generatedAt: producedArtifact.generatedAt,
823
833
  message: detail,
824
834
  recovery: `The review artifact was created at ${producedArtifact.path} but could not be consumed. Fix the artifact format, then run oat-project-review-receive for ${producedArtifact.path}; if the only issue is a missing zero-count severity heading, rerun the gate to normalize the same artifact instead of creating a new review version.`,
825
835
  });
@@ -835,9 +845,11 @@ async function runReviewGate(prompt, options, context, dependencies) {
835
845
  });
836
846
  writeReviewGateResult(context, {
837
847
  status: blocking ? 'blocked' : 'ok',
848
+ runId,
838
849
  target: selected.id,
839
850
  project: projectPath,
840
851
  artifactPath: producedArtifact.path,
852
+ generatedAt: producedArtifact.generatedAt,
841
853
  threshold,
842
854
  blocking,
843
855
  counts: verdict.counts,
@@ -1 +1 @@
1
- {"version":3,"file":"latest.d.ts","sourceRoot":"","sources":["../../../src/commands/review/latest.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;CACrB;AAQD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACnE;AAmID,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAqF9B;AA+CD,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAkBT"}
1
+ {"version":3,"file":"latest.d.ts","sourceRoot":"","sources":["../../../src/commands/review/latest.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;CACrB;AAQD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACnE;AAsID,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAqF9B;AA+CD,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAkBT"}
@@ -1,7 +1,7 @@
1
1
  import { readdir, readFile } from 'node:fs/promises';
2
2
  import { isAbsolute, join, relative } from 'node:path';
3
3
  import { buildCommandContext, } from '../../app/command-context.js';
4
- import { getFrontmatterBlock, getFrontmatterField, } from '../shared/frontmatter.js';
4
+ import { getFrontmatterBlock, getFrontmatterField, parseGeneratedTime, } from '../shared/frontmatter.js';
5
5
  import { readGlobalOptions } from '../shared/shared.utils.js';
6
6
  import { readOatLocalConfig } from '../../config/oat-config.js';
7
7
  import { normalizeToPosixPath, resolveProjectRoot } from '../../fs/paths.js';
@@ -72,7 +72,7 @@ async function readReviewCandidate(repoRoot, relativePath, kind, priority, archi
72
72
  if (!generatedAt) {
73
73
  return null;
74
74
  }
75
- const generatedTime = Date.parse(generatedAt);
75
+ const generatedTime = parseGeneratedTime(generatedAt);
76
76
  if (Number.isNaN(generatedTime)) {
77
77
  return null;
78
78
  }
@@ -99,6 +99,9 @@ function sortReviewCandidates(a, b) {
99
99
  if (a.lifecycleRank !== b.lifecycleRank) {
100
100
  return b.lifecycleRank - a.lifecycleRank;
101
101
  }
102
+ // Same scope, same generated time: fall back to a stable path order. Review
103
+ // artifacts carry a seconds-precision `oat_generated_at`, so distinct re-gate
104
+ // rounds differ on generatedTime above and never reach this branch.
102
105
  return a.path.localeCompare(b.path);
103
106
  }
104
107
  export async function findLatestReview(options) {
@@ -9,6 +9,17 @@ export declare function isProjectStatePhase(value: string): value is ProjectStat
9
9
  export declare function isProjectStateFrontmatterField(value: string): value is ProjectStateFrontmatterField;
10
10
  export declare function getFrontmatterBlock(content: string): string | null;
11
11
  export declare function getFrontmatterField(frontmatter: string, field: string): string | null;
12
+ /**
13
+ * Parse an `oat_generated_at` value into a comparable epoch millisecond time.
14
+ *
15
+ * Review artifacts should carry a UTC, `Z`-suffixed timestamp
16
+ * (`YYYY-MM-DDTHH:MM:SSZ`). A bare date (`YYYY-MM-DD`) already parses as UTC,
17
+ * but a datetime with no timezone designator parses as *local* time, which
18
+ * mis-orders artifacts written by agents in different timezones. Treat such a
19
+ * value as UTC by appending `Z` so ordering is timezone-independent regardless
20
+ * of what the writer emitted. Returns `NaN` for unparseable input.
21
+ */
22
+ export declare function parseGeneratedTime(value: string): number;
12
23
  export declare function parseFrontmatterField(filePath: string, field: string): Promise<string>;
13
24
  export declare function getSkillVersion(skillDir: string): Promise<string | null>;
14
25
  export declare function getAgentVersion(agentPath: string): Promise<string | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/frontmatter.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,6CAA8C,CAAC;AAE/E,eAAO,MAAM,oBAAoB,gFAOvB,CAAC;AAEX,eAAO,MAAM,gCAAgC,sgBA2BnC,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACpE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AACtE,MAAM,MAAM,4BAA4B,GACtC,CAAC,OAAO,gCAAgC,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,gBAAgB,CAE3E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,iBAAiB,CAE7E;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,GACZ,KAAK,IAAI,4BAA4B,CAIvC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,IAAI,CAKf;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQxB;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB"}
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/frontmatter.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,6CAA8C,CAAC;AAE/E,eAAO,MAAM,oBAAoB,gFAOvB,CAAC;AAEX,eAAO,MAAM,gCAAgC,sgBA2BnC,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACpE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AACtE,MAAM,MAAM,4BAA4B,GACtC,CAAC,OAAO,gCAAgC,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,gBAAgB,CAE3E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,iBAAiB,CAE7E;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,GACZ,KAAK,IAAI,4BAA4B,CAIvC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,IAAI,CAKf;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQxB;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB"}
@@ -57,6 +57,21 @@ export function getFrontmatterField(frontmatter, field) {
57
57
  return null;
58
58
  return match[1].replace(/\s*#.*$/, '').trim();
59
59
  }
60
+ /**
61
+ * Parse an `oat_generated_at` value into a comparable epoch millisecond time.
62
+ *
63
+ * Review artifacts should carry a UTC, `Z`-suffixed timestamp
64
+ * (`YYYY-MM-DDTHH:MM:SSZ`). A bare date (`YYYY-MM-DD`) already parses as UTC,
65
+ * but a datetime with no timezone designator parses as *local* time, which
66
+ * mis-orders artifacts written by agents in different timezones. Treat such a
67
+ * value as UTC by appending `Z` so ordering is timezone-independent regardless
68
+ * of what the writer emitted. Returns `NaN` for unparseable input.
69
+ */
70
+ export function parseGeneratedTime(value) {
71
+ const hasTime = value.includes('T');
72
+ const hasZone = /(?:[zZ]|[+-]\d{2}:?\d{2})$/.test(value);
73
+ return Date.parse(hasTime && !hasZone ? `${value}Z` : value);
74
+ }
60
75
  export async function parseFrontmatterField(filePath, field) {
61
76
  try {
62
77
  const content = await readFile(filePath, 'utf8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "private": false,
5
5
  "description": "Open Agent Toolkit CLI",
6
6
  "homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
@@ -34,7 +34,7 @@
34
34
  "ora": "^9.0.0",
35
35
  "yaml": "2.8.2",
36
36
  "zod": "^3.25.76",
37
- "@open-agent-toolkit/control-plane": "0.1.41"
37
+ "@open-agent-toolkit/control-plane": "0.1.42"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.0",