@open-agent-toolkit/cli 0.1.50 → 0.1.52

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 (42) hide show
  1. package/assets/agents/oat-phase-implementer.md +16 -5
  2. package/assets/docs/cli-utilities/configuration.md +1 -1
  3. package/assets/docs/cli-utilities/workflow-gates.md +1 -1
  4. package/assets/docs/provider-sync/commands.md +29 -1
  5. package/assets/docs/provider-sync/providers.md +13 -4
  6. package/assets/docs/provider-sync/scope-and-surface.md +13 -0
  7. package/assets/docs/reference/cli-reference.md +2 -2
  8. package/assets/docs/workflows/projects/hill-checkpoints.md +1 -1
  9. package/assets/docs/workflows/projects/implementation-execution.md +34 -3
  10. package/assets/docs/workflows/projects/lifecycle.md +9 -0
  11. package/assets/docs/workflows/projects/reviews.md +18 -1
  12. package/assets/public-package-versions.json +4 -4
  13. package/assets/skills/oat-agent-instructions-analyze/references/docs/provider-reference.md +8 -2
  14. package/assets/skills/oat-agent-instructions-apply/references/docs/provider-reference.md +8 -2
  15. package/assets/skills/oat-project-implement/SKILL.md +196 -67
  16. package/assets/skills/oat-project-import-plan/SKILL.md +6 -6
  17. package/assets/skills/oat-project-next/SKILL.md +8 -1
  18. package/assets/skills/oat-project-plan/SKILL.md +6 -6
  19. package/assets/skills/oat-project-plan-writing/SKILL.md +13 -10
  20. package/assets/skills/oat-project-pr-final/SKILL.md +8 -1
  21. package/assets/skills/oat-project-quick-start/SKILL.md +5 -5
  22. package/assets/skills/oat-project-review-provide/SKILL.md +27 -9
  23. package/dist/commands/config/index.d.ts.map +1 -1
  24. package/dist/commands/config/index.js +34 -9
  25. package/dist/commands/doctor/index.d.ts.map +1 -1
  26. package/dist/commands/doctor/index.js +148 -85
  27. package/dist/commands/gate/index.js +1 -1
  28. package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
  29. package/dist/commands/project/dispatch-ceiling/index.js +80 -0
  30. package/dist/commands/providers/codex/materialize.d.ts.map +1 -1
  31. package/dist/commands/providers/codex/materialize.js +31 -15
  32. package/dist/config/oat-config.d.ts +9 -1
  33. package/dist/config/oat-config.d.ts.map +1 -1
  34. package/dist/config/oat-config.js +55 -4
  35. package/dist/config/resolve.d.ts.map +1 -1
  36. package/dist/config/resolve.js +7 -3
  37. package/dist/providers/codex/codec/config-merge.d.ts +8 -2
  38. package/dist/providers/codex/codec/config-merge.d.ts.map +1 -1
  39. package/dist/providers/codex/codec/config-merge.js +17 -2
  40. package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
  41. package/dist/providers/codex/codec/sync-extension.js +14 -4
  42. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-pr-final
3
- version: 1.4.1
3
+ version: 1.4.2
4
4
  description: Use when the user requests or confirms opening the final PR for an active OAT project — e.g. "open the final PR", "ship it", "run oat-project-pr-final", or confirms a previously offered final-PR step. Do NOT auto-invoke when phases are marked complete. Generates the final lifecycle PR description from artifacts and creates the PR.
5
5
  disable-model-invocation: false
6
6
  user-invocable: true
@@ -168,6 +168,13 @@ If `FINAL_ROW` is missing or does not contain `passed`:
168
168
 
169
169
  ### Step 3: Collect Project Summary
170
170
 
171
+ Before generating a summary, inspect `oat_post_implement_sequence`. Reuse a
172
+ completed `summary` step instead of regenerating it; when no completed sequence
173
+ summary exists, retain normal summary generation. Every state update in this
174
+ skill must merge with, never replace, `oat_post_implement_sequence`. If a PR
175
+ was partially created, reconcile the durable PR state before retrying its `pr`
176
+ sequence step.
177
+
171
178
  **Step 3.0: Check for summary.md**
172
179
 
173
180
  Check if `{PROJECT_PATH}/summary.md` exists:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-quick-start
3
- version: 2.1.13
3
+ version: 2.1.14
4
4
  description: Use when a task is small enough for quick mode or rapid iteration is preferred. Scaffolds a lightweight OAT project from discovery directly to a runnable plan, with optional brainstorming and lightweight design.
5
5
  argument-hint: '<project-name> ["project description"]'
6
6
  oat_gateable: true
@@ -563,20 +563,20 @@ copy compiled provider targets into this shape or user config. `Uncapped` and
563
563
  `Inherit Host Defaults` remain explicit modes; `Leave Unresolved` and
564
564
  non-interactive unresolved state are not implementation-ready.
565
565
 
566
- ### Step 3.55: Configure Optional Phase Review
566
+ ### Step 3.55: Configure Optional Phase Gate Review
567
567
 
568
568
  After the generated quick plan has stable phase IDs and before Step 3.6 starts
569
- the plan artifact review, invoke the `Shared Phase-Review Setup Contract` from
569
+ the plan artifact review, invoke the `Shared Phase Gate Review Setup Contract` from
570
570
  `oat-project-plan-writing`.
571
571
 
572
572
  If `plan.md` already contains an explicit `oat_phase_review_gate`, preserve it
573
573
  through the shared contract without probing, prompting, or mutation. Otherwise
574
574
  let the contract probe qualifying targets and offer all phases, selected
575
575
  phases, or disabled. If the probe fails, no target qualifies, or the user
576
- declines, leave phase review disabled and continue with the contract's concise
576
+ declines, leave Phase gate review disabled and continue with the contract's concise
577
577
  status output.
578
578
 
579
- This phase-review setup is independent from HiLL checkpoints. Do not read or
579
+ This Phase gate review setup is independent from HiLL checkpoints. Do not read or
580
580
  change HiLL fields here, and do not add a provider/model `--target` to any
581
581
  lifecycle command.
582
582
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-review-provide
3
- version: 1.3.13
3
+ version: 1.3.14
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
@@ -555,13 +555,16 @@ fallbacks.
555
555
  A concrete managed Codex target takes precedence over tier availability.
556
556
  Build the actual provider invocation before reporting the target as enforced:
557
557
 
558
- - Codex uses the exact registered reviewer role or variant from
559
- `providers.codex.dispatchArgs.variant` when selectable. If the current host
560
- cannot select it, launch a fresh Codex child with the resolver target's
561
- explicit model, reasoning effort, canonical role instructions from
562
- `.agents/agents/oat-reviewer.md`, and the same Review Scope payload.
563
- If that fresh child is unavailable, use only a verified-equivalent inline
564
- route or block the review.
558
+ - Codex must send the resolver-returned Codex variant first as the native
559
+ `agent_type`. Spawn acceptance plus the complete launcher payload is
560
+ configured invocation evidence; it does not require reviewer self-report or
561
+ separate runtime telemetry. A native role-selection rejection means an
562
+ explicit host rejection of that exact `agent_type` before any reviewer or
563
+ child starts. Only after that pre-start rejection may the workflow launch a
564
+ fresh Codex child with the resolver target's explicit model, reasoning
565
+ effort, canonical role instructions from `.agents/agents/oat-reviewer.md`,
566
+ and the same Review Scope payload. If neither exact route is available, use
567
+ only a verified-equivalent inline route or block the review.
565
568
  - Claude requires a non-empty `providers.claude.dispatchArgs.model`; the actual
566
569
  provider invocation must include that exact value as its `model` argument.
567
570
  - Cursor requires a non-empty `providers.cursor.dispatchArgs.model`; the actual
@@ -573,6 +576,18 @@ candidate ladder, fail closed before review. Route interactive repair through
573
576
  the planning workflow's `Complete Dispatch Ladder Adoption Contract`; do not
574
577
  invent a reviewer target.
575
578
 
579
+ After constructing the complete provider payload, record the launcher-owned
580
+ `target`, `model_axis`, and `effort_axis` with
581
+ `launcher-selected/config-declared` provenance. These fields are immutable:
582
+ missing telemetry, missing reviewer self-report, or contradictory self-report
583
+ must not populate, replace, or overwrite them and must not trigger fallback.
584
+
585
+ Once the native host accepts a reviewer, every terminal result is an
586
+ authoritative review outcome. An accepted reviewer returning `BLOCKED` is a
587
+ terminal blocking review outcome: it blocks the review and does not invoke or
588
+ trigger the fresh-child fallback. Absent findings from that terminal must not
589
+ be parsed, interpreted, or treated as a passing review.
590
+
576
591
  On timeout or retry, reuse the same exact role or complete invocation payload,
577
592
  including the Claude or Cursor model argument. If the host cannot apply a
578
593
  required role or model argument, fail closed or block unless inline execution
@@ -623,7 +638,7 @@ Then spawn the reviewer:
623
638
  - Use provider-appropriate dispatch:
624
639
  - Claude Code: Task tool with `subagent_type: "oat-reviewer"` (resolves from `.claude/agents/oat-reviewer.md`). For a concrete managed target, the payload must also contain `model: providers.claude.dispatchArgs.model` with the resolver-returned value.
625
640
  - Cursor: explicit invocation `/oat-reviewer` (or natural mention) with agent resolved from `.cursor/agents/oat-reviewer.md` or `.claude/agents/oat-reviewer.md` compatibility path. For a concrete managed target, the invocation must also contain `model: providers.cursor.dispatchArgs.model` with the exact opaque resolver-returned string.
626
- - Codex style: for a concrete managed target, spawn the exact resolver-returned `agent_type`; if it cannot be selected, use the explicitly pinned fresh-child route from Step 6.0. Generic auto-selection is permitted only for the documented base-role exceptions.
641
+ - Codex style: for a concrete managed target, first spawn the exact resolver-returned native `agent_type`; only an explicit pre-start native role-selection rejection permits the explicitly pinned fresh-child route from Step 6.0. Generic auto-selection is permitted only for the documented base-role exceptions.
627
642
  - Pass the Review Scope metadata block from Step 5 as the prompt
628
643
  - Include the pre-computed artifact path for the subagent to write to
629
644
  - **If a worktree was resolved in Step 1.5:** include the worktree path in the prompt so the subagent writes the artifact to the worktree directory, not the current session's working directory
@@ -633,6 +648,9 @@ The `oat-reviewer` agent definition contains the full review process, mode contr
633
648
 
634
649
  After the subagent completes:
635
650
 
651
+ - Treat its terminal status as authoritative. An accepted `BLOCKED` result
652
+ blocks the review; do not invoke fallback and do not infer a pass from a
653
+ missing review artifact or absent findings.
636
654
  - Verify the review artifact was written to the expected path
637
655
  - Continue with Step 9 (plan update) and Step 9.5 (commit)
638
656
 
@@ -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,EAahB,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;AAkFpC,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;AAo0DD,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,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;AAkFpC,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;AAq2DD,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, readOatConfig, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
10
+ import { VALID_DISPATCH_POLICY_MODES, VALID_MANAGED_DISPATCH_POLICIES, 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';
@@ -389,11 +389,11 @@ const CONFIG_CATALOG = [
389
389
  group: 'Workflow Preferences (3-layer: local > shared > user)',
390
390
  file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
391
391
  scope: 'workflow',
392
- type: 'wait | summary | pr | docs-pr',
392
+ type: 'legacy string (wait | summary | pr | docs-pr) | structured JSON object',
393
393
  defaultValue: 'unset',
394
394
  mutability: 'read/write',
395
- owningCommand: 'oat config set workflow.postImplementSequence <value>',
396
- description: 'Default post-implementation chaining: "wait" stops without auto-chaining, "summary" generates summary only, "pr" runs pr-final (which auto-generates summary), "docs-pr" runs docs sync then pr-final. When unset, the skill prompts. Resolution: env > local > shared > user > default.',
395
+ owningCommand: "oat config set workflow.postImplementSequence '<legacy-or-json>'",
396
+ description: 'Default post-implementation chaining. Legacy strings remain supported unchanged. Structured JSON uses {"preApproval":[...],"postApproval":[...]} with the canonical sequence steps. Plain get/list/dump output serializes structured values as compact JSON; get --json preserves the object value. When unset, the skill prompts. Resolution: env > local > shared > user > default.',
397
397
  },
398
398
  {
399
399
  key: 'workflow.reviewExecutionModel',
@@ -717,6 +717,23 @@ function parseWorkflowValue(key, rawValue) {
717
717
  }
718
718
  return normalized === 'true';
719
719
  }
720
+ if (key === 'workflow.postImplementSequence') {
721
+ const normalized = rawValue.trim();
722
+ if (normalized.startsWith('{') || normalized.startsWith('[')) {
723
+ let parsed;
724
+ try {
725
+ parsed = JSON.parse(normalized);
726
+ }
727
+ catch {
728
+ throw new Error(`Invalid value for ${key}: expected valid structured JSON`);
729
+ }
730
+ const sequence = normalizeWorkflowPostImplementSequence(parsed);
731
+ if (!sequence || Array.isArray(parsed)) {
732
+ throw new Error(`Invalid value for ${key}: structured JSON must match {"preApproval":[...],"postApproval":[...]}`);
733
+ }
734
+ return sequence;
735
+ }
736
+ }
720
737
  const allowed = WORKFLOW_ENUM_VALUES[key];
721
738
  if (allowed) {
722
739
  const normalized = rawValue.trim();
@@ -828,6 +845,12 @@ function toAvailabilityRef(ref) {
828
845
  }
829
846
  function applyWorkflowValue(workflow, key, value) {
830
847
  const subKey = key.slice('workflow.'.length);
848
+ if (subKey === 'postImplementSequence') {
849
+ return {
850
+ ...workflow,
851
+ postImplementSequence: value,
852
+ };
853
+ }
831
854
  if (subKey === 'dispatchPolicy.mode') {
832
855
  const mode = value;
833
856
  if (mode === 'inherit') {
@@ -940,7 +963,7 @@ function formatResolvedValue(value) {
940
963
  }
941
964
  return String(value);
942
965
  }
943
- async function getConfigValue(repoRoot, userConfigDir, key, dependencies) {
966
+ async function getConfigValue(repoRoot, userConfigDir, key, dependencies, preserveRawObject = false) {
944
967
  const resolved = await dependencies.resolveEffectiveConfig(repoRoot, userConfigDir, dependencies.processEnv);
945
968
  const entry = resolved.resolved[key];
946
969
  if (!entry) {
@@ -948,7 +971,9 @@ async function getConfigValue(repoRoot, userConfigDir, key, dependencies) {
948
971
  }
949
972
  return {
950
973
  key,
951
- value: formatResolvedValue(entry.value),
974
+ value: preserveRawObject && typeof entry.value === 'object'
975
+ ? entry.value
976
+ : formatResolvedValue(entry.value),
952
977
  source: entry.source,
953
978
  };
954
979
  }
@@ -966,7 +991,7 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
966
991
  const effectiveSurface = surface === 'auto' ? defaultSurfaceForKey(key) : surface;
967
992
  if (isWorkflowKey(key)) {
968
993
  const parsedValue = parseWorkflowValue(key, rawValue);
969
- const displayValue = typeof parsedValue === 'boolean' ? String(parsedValue) : parsedValue;
994
+ const displayValue = formatResolvedValue(parsedValue);
970
995
  if (typeof parsedValue === 'string' && isDispatchCeilingProviderKey(key)) {
971
996
  const availability = await dependencies.validateMatrixCell(providerNameFromConfigKey(key), parsedValue, {
972
997
  cwd: repoRoot,
@@ -1322,7 +1347,7 @@ async function runGet(keyArg, context, dependencies) {
1322
1347
  }
1323
1348
  const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
1324
1349
  const userConfigDir = join(context.home, '.oat');
1325
- const value = await getConfigValue(repoRoot, userConfigDir, keyArg, dependencies);
1350
+ const value = await getConfigValue(repoRoot, userConfigDir, keyArg, dependencies, context.json);
1326
1351
  if (context.json) {
1327
1352
  context.logger.json({
1328
1353
  status: 'ok',
@@ -1330,7 +1355,7 @@ async function runGet(keyArg, context, dependencies) {
1330
1355
  });
1331
1356
  }
1332
1357
  else {
1333
- context.logger.info(value.value ?? '');
1358
+ context.logger.info(formatResolvedValue(value.value) ?? '');
1334
1359
  }
1335
1360
  process.exitCode = 0;
1336
1361
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/index.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { access, mkdtemp, readdir, readFile, rm, symlink, } from 'node:fs/promises';
2
2
  import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
3
+ import { dirname, join } from 'node:path';
4
4
  import { buildCommandContext, } from '../../app/command-context.js';
5
5
  import { compareVersions } from '../init/tools/shared/version.js';
6
6
  import { createPjmDisabledCheck, runPjmDoctorChecks, } from '../pjm/doctor.js';
@@ -279,6 +279,152 @@ async function isManagedCodexRoleConfig(scopeRoot, roleName, configFile, depende
279
279
  return false;
280
280
  }
281
281
  }
282
+ function getCodexAgents(config) {
283
+ const agents = config.agents;
284
+ return agents && typeof agents === 'object' && !Array.isArray(agents)
285
+ ? agents
286
+ : {};
287
+ }
288
+ function getCodexMaxDepthEntry(config) {
289
+ const agents = getCodexAgents(config);
290
+ return {
291
+ present: Object.prototype.hasOwnProperty.call(agents, 'max_depth'),
292
+ value: agents.max_depth,
293
+ };
294
+ }
295
+ /**
296
+ * Applies Codex config precedence without letting an invalid project value fall
297
+ * through to the lower-precedence user value.
298
+ */
299
+ async function resolveDoctorCodexMaxDepth(scope, parsedConfig, userHome, dependencies) {
300
+ const scopedEntry = getCodexMaxDepthEntry(parsedConfig);
301
+ if (scope !== 'project' || scopedEntry.present) {
302
+ return scopedEntry;
303
+ }
304
+ const userConfigPath = join(userHome, '.codex', 'config.toml');
305
+ if (!(await dependencies.pathExists(userConfigPath))) {
306
+ return scopedEntry;
307
+ }
308
+ try {
309
+ return getCodexMaxDepthEntry(TOML.parse(await dependencies.readFile(userConfigPath)));
310
+ }
311
+ catch {
312
+ return { present: true, value: null };
313
+ }
314
+ }
315
+ function createCodexMaxDepthCheck(scope, entry) {
316
+ const topology = 'root (0) → phase coordinator (1) → task worker (2)';
317
+ const numericDepth = typeof entry.value === 'number' && Number.isFinite(entry.value)
318
+ ? entry.value
319
+ : null;
320
+ const sufficient = numericDepth !== null && numericDepth >= 2;
321
+ let state;
322
+ if (!entry.present) {
323
+ state = 'agents.max_depth is missing';
324
+ }
325
+ else if (numericDepth === null) {
326
+ state = 'agents.max_depth is not a valid number';
327
+ }
328
+ else {
329
+ state = `agents.max_depth is ${numericDepth}`;
330
+ }
331
+ return {
332
+ name: `${scope}:codex_max_depth`,
333
+ description: 'Codex managed-role nested dispatch depth',
334
+ status: sufficient ? 'pass' : 'warn',
335
+ message: sufficient
336
+ ? `${state}; sufficient for ${topology}.`
337
+ : `${state}; OAT managed implementation requires ${topology}.`,
338
+ fix: sufficient
339
+ ? undefined
340
+ : `Run \`oat sync --scope ${scope}\`, or materialize a single role with \`oat providers codex materialize <agent-name> --model <model> --effort <effort> --scope ${scope}\`.`,
341
+ };
342
+ }
343
+ async function runCodexChecksForScope(scope, scopeRoot, userHome, dependencies) {
344
+ const checks = [];
345
+ const codexConfigPath = join(scopeRoot, '.codex', 'config.toml');
346
+ if (!(await dependencies.pathExists(codexConfigPath))) {
347
+ return checks;
348
+ }
349
+ let parsedConfig;
350
+ try {
351
+ parsedConfig = TOML.parse(await dependencies.readFile(codexConfigPath));
352
+ checks.push({
353
+ name: `${scope}:codex_config_toml`,
354
+ description: 'Codex config TOML parseability',
355
+ status: 'pass',
356
+ message: '.codex/config.toml parsed successfully.',
357
+ });
358
+ }
359
+ catch (error) {
360
+ checks.push({
361
+ name: `${scope}:codex_config_toml`,
362
+ description: 'Codex config TOML parseability',
363
+ status: 'fail',
364
+ message: error instanceof Error
365
+ ? `Failed to parse .codex/config.toml: ${error.message}`
366
+ : 'Failed to parse .codex/config.toml.',
367
+ fix: 'Repair .codex/config.toml syntax and rerun doctor.',
368
+ });
369
+ return checks;
370
+ }
371
+ const features = parsedConfig.features &&
372
+ typeof parsedConfig.features === 'object' &&
373
+ !Array.isArray(parsedConfig.features)
374
+ ? parsedConfig.features
375
+ : null;
376
+ const agents = getCodexAgents(parsedConfig);
377
+ const managedRoles = [];
378
+ const roleConfigFiles = new Map();
379
+ for (const [roleName, roleConfig] of Object.entries(agents)) {
380
+ const configFile = getCodexAgentConfigFile(roleConfig);
381
+ if (!configFile) {
382
+ continue;
383
+ }
384
+ roleConfigFiles.set(roleName, configFile);
385
+ if (await isManagedCodexRoleConfig(scopeRoot, roleName, configFile, dependencies)) {
386
+ managedRoles.push(roleName);
387
+ }
388
+ }
389
+ if (managedRoles.length === 0) {
390
+ return checks;
391
+ }
392
+ const multiAgentEnabled = features?.multi_agent === true || features?.multi_agent === 'true';
393
+ checks.push({
394
+ name: `${scope}:codex_multi_agent`,
395
+ description: 'Codex multi-agent feature flag',
396
+ status: multiAgentEnabled ? 'pass' : 'warn',
397
+ message: multiAgentEnabled
398
+ ? 'features.multi_agent is enabled for codex managed roles.'
399
+ : 'Codex managed roles detected but features.multi_agent is not true.',
400
+ fix: multiAgentEnabled
401
+ ? undefined
402
+ : 'Set [features] multi_agent = true in .codex/config.toml.',
403
+ });
404
+ checks.push(createCodexMaxDepthCheck(scope, await resolveDoctorCodexMaxDepth(scope, parsedConfig, userHome, dependencies)));
405
+ const missingRoleFiles = [];
406
+ for (const roleName of managedRoles) {
407
+ const configFile = roleConfigFiles.get(roleName);
408
+ if (!configFile)
409
+ continue;
410
+ const absoluteRolePath = join(scopeRoot, '.codex', configFile);
411
+ if (!(await dependencies.pathExists(absoluteRolePath))) {
412
+ missingRoleFiles.push(configFile);
413
+ }
414
+ }
415
+ checks.push({
416
+ name: `${scope}:codex_role_file_refs`,
417
+ description: 'Codex role config_file references',
418
+ status: missingRoleFiles.length === 0 ? 'pass' : 'warn',
419
+ message: missingRoleFiles.length === 0
420
+ ? 'All codex role config_file references exist.'
421
+ : `Missing codex role files: ${missingRoleFiles.join(', ')}`,
422
+ fix: missingRoleFiles.length === 0
423
+ ? undefined
424
+ : `Regenerate codex roles with \`oat sync --scope ${scope}\`, or materialize a single role with \`oat providers codex materialize <agent-name> --model <model> --effort <effort> --scope ${scope}\`.`,
425
+ });
426
+ return checks;
427
+ }
282
428
  async function runChecksForScope(scope, scopeRoot, userConfigDir, dependencies) {
283
429
  const checks = [];
284
430
  const skillsPath = join(scopeRoot, '.agents', 'skills');
@@ -354,6 +500,7 @@ async function runChecksForScope(scope, scopeRoot, userConfigDir, dependencies)
354
500
  ? undefined
355
501
  : 'Install or enable a provider directory (e.g. .claude, .cursor, .codex).',
356
502
  });
503
+ checks.push(...(await runCodexChecksForScope(scope, scopeRoot, dirname(userConfigDir), dependencies)));
357
504
  if (scope === 'project') {
358
505
  try {
359
506
  const assetsRoot = await dependencies.resolveAssetsRoot();
@@ -403,90 +550,6 @@ async function runChecksForScope(scope, scopeRoot, userConfigDir, dependencies)
403
550
  fix: 'Run `pnpm build` and rerun `oat doctor`.',
404
551
  });
405
552
  }
406
- const codexConfigPath = join(scopeRoot, '.codex', 'config.toml');
407
- const codexConfigExists = await dependencies.pathExists(codexConfigPath);
408
- if (codexConfigExists) {
409
- let parsedConfig = null;
410
- try {
411
- parsedConfig = TOML.parse(await dependencies.readFile(codexConfigPath));
412
- checks.push({
413
- name: `${scope}:codex_config_toml`,
414
- description: 'Codex config TOML parseability',
415
- status: 'pass',
416
- message: '.codex/config.toml parsed successfully.',
417
- });
418
- }
419
- catch (error) {
420
- checks.push({
421
- name: `${scope}:codex_config_toml`,
422
- description: 'Codex config TOML parseability',
423
- status: 'fail',
424
- message: error instanceof Error
425
- ? `Failed to parse .codex/config.toml: ${error.message}`
426
- : 'Failed to parse .codex/config.toml.',
427
- fix: 'Repair .codex/config.toml syntax and rerun doctor.',
428
- });
429
- }
430
- if (parsedConfig) {
431
- const features = parsedConfig.features &&
432
- typeof parsedConfig.features === 'object' &&
433
- !Array.isArray(parsedConfig.features)
434
- ? parsedConfig.features
435
- : null;
436
- const agents = parsedConfig.agents &&
437
- typeof parsedConfig.agents === 'object' &&
438
- !Array.isArray(parsedConfig.agents)
439
- ? parsedConfig.agents
440
- : null;
441
- const managedRoles = [];
442
- const roleConfigFiles = new Map();
443
- for (const [roleName, roleConfig] of Object.entries(agents ?? {})) {
444
- const configFile = getCodexAgentConfigFile(roleConfig);
445
- if (!configFile) {
446
- continue;
447
- }
448
- roleConfigFiles.set(roleName, configFile);
449
- if (await isManagedCodexRoleConfig(scopeRoot, roleName, configFile, dependencies)) {
450
- managedRoles.push(roleName);
451
- }
452
- }
453
- if (managedRoles.length > 0) {
454
- const multiAgentEnabled = features?.multi_agent === true || features?.multi_agent === 'true';
455
- checks.push({
456
- name: `${scope}:codex_multi_agent`,
457
- description: 'Codex multi-agent feature flag',
458
- status: multiAgentEnabled ? 'pass' : 'warn',
459
- message: multiAgentEnabled
460
- ? 'features.multi_agent is enabled for codex managed roles.'
461
- : 'Codex managed roles detected but features.multi_agent is not true.',
462
- fix: multiAgentEnabled
463
- ? undefined
464
- : 'Set [features] multi_agent = true in .codex/config.toml.',
465
- });
466
- const missingRoleFiles = [];
467
- for (const roleName of managedRoles) {
468
- const configFile = roleConfigFiles.get(roleName);
469
- if (!configFile)
470
- continue;
471
- const absoluteRolePath = join(scopeRoot, '.codex', configFile);
472
- if (!(await dependencies.pathExists(absoluteRolePath))) {
473
- missingRoleFiles.push(configFile);
474
- }
475
- }
476
- checks.push({
477
- name: `${scope}:codex_role_file_refs`,
478
- description: 'Codex role config_file references',
479
- status: missingRoleFiles.length === 0 ? 'pass' : 'warn',
480
- message: missingRoleFiles.length === 0
481
- ? 'All codex role config_file references exist.'
482
- : `Missing codex role files: ${missingRoleFiles.join(', ')}`,
483
- fix: missingRoleFiles.length === 0
484
- ? undefined
485
- : 'Regenerate codex roles with `oat sync --scope project`, or materialize a single role with `oat providers codex materialize ...`.',
486
- });
487
- }
488
- }
489
- }
490
553
  const repoReferenceRoot = join(scopeRoot, '.oat', 'repo');
491
554
  const [userConfig, config, localConfig] = await Promise.all([
492
555
  dependencies.readUserConfig(userConfigDir),
@@ -55,7 +55,7 @@ const VALID_IDENTITY_PROVENANCES = [
55
55
  ];
56
56
  const REVIEW_GATE_CONTEXT_NOTE = 'This review is gate-originated. If you run `oat-project-review-provide`, set `oat_review_invocation: gate` in the review artifact. Write a canonical review artifact with `### Critical`, `### Important`, `### Medium`, and `### Minor` headings in that order, using `None` for empty sections.';
57
57
  const GATE_CHECK_TIMEOUT_MS = 5_000;
58
- const GATE_EXEC_TIMEOUT_MS = 10 * 60 * 1_000;
58
+ const GATE_EXEC_TIMEOUT_MS = 15 * 60 * 1_000;
59
59
  function reviewGateProjectContext(project) {
60
60
  return [
61
61
  `Resolved OAT project path: ${project.path}. Run the review for this project path.`,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/dispatch-ceiling/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAa9B,OAAO,EAUL,KAAK,uBAAuB,EAY7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAsBzB,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAiC5C,UAAU,2BAA2B;IACnC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AA63ED,wBAAgB,mCAAmC,CACjD,SAAS,GAAE,OAAO,CAAC,2BAA2B,CAAM,GACnD,OAAO,CAkGT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/dispatch-ceiling/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAa9B,OAAO,EAUL,KAAK,uBAAuB,EAY7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAsBzB,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAiC5C,UAAU,2BAA2B;IACnC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAi/ED,wBAAgB,mCAAmC,CACjD,SAAS,GAAE,OAAO,CAAC,2BAA2B,CAAM,GACnD,OAAO,CAkGT"}
@@ -1175,6 +1175,58 @@ function readCodexDefaultFromToml(content) {
1175
1175
  const value = parsed['model_reasoning_effort'];
1176
1176
  return typeof value === 'string' && value.trim() ? value.trim() : null;
1177
1177
  }
1178
+ function readCodexMaxDepthEntry(content) {
1179
+ let parsed;
1180
+ try {
1181
+ parsed = TOML.parse(content);
1182
+ }
1183
+ catch {
1184
+ return { present: true, value: null };
1185
+ }
1186
+ const agents = parsed.agents;
1187
+ if (!agents || typeof agents !== 'object' || Array.isArray(agents)) {
1188
+ return { present: false, value: undefined };
1189
+ }
1190
+ const record = agents;
1191
+ return {
1192
+ present: Object.prototype.hasOwnProperty.call(record, 'max_depth'),
1193
+ value: record.max_depth,
1194
+ };
1195
+ }
1196
+ async function readCodexMaxDepthAtPath(path, dependencies) {
1197
+ if (!(await dependencies.pathExists(path))) {
1198
+ return { present: false, value: undefined };
1199
+ }
1200
+ try {
1201
+ return readCodexMaxDepthEntry(await dependencies.readFile(path));
1202
+ }
1203
+ catch {
1204
+ return { present: true, value: null };
1205
+ }
1206
+ }
1207
+ /**
1208
+ * Resolves the depth seen by Codex: project config wins whenever it declares a
1209
+ * value, while an absent project value inherits the user config.
1210
+ */
1211
+ async function resolveEffectiveCodexMaxDepth(scope, repoRoot, context, dependencies) {
1212
+ const userPath = join(context.home, '.codex', 'config.toml');
1213
+ if (scope === 'user') {
1214
+ return readCodexMaxDepthAtPath(userPath, dependencies);
1215
+ }
1216
+ const projectEntry = await readCodexMaxDepthAtPath(join(repoRoot, '.codex', 'config.toml'), dependencies);
1217
+ return projectEntry.present
1218
+ ? projectEntry
1219
+ : readCodexMaxDepthAtPath(userPath, dependencies);
1220
+ }
1221
+ function managedCodexDepthBlockMessage(scope, entry) {
1222
+ const validDepth = typeof entry.value === 'number' && Number.isFinite(entry.value);
1223
+ const state = !entry.present
1224
+ ? 'agents.max_depth is missing'
1225
+ : validDepth
1226
+ ? `agents.max_depth is ${entry.value}`
1227
+ : 'agents.max_depth is not a valid number';
1228
+ return `BLOCKED: Codex ${state}; managed implementation requires root (0) → phase coordinator (1) → task worker (2).\nRun \`oat sync --scope ${scope}\`, or materialize a single role with \`oat providers codex materialize <agent-name> --model <model> --effort <effort> --scope ${scope}\`.`;
1229
+ }
1178
1230
  async function resolveCodexProviderDefaultEffort(repoRoot, context, dependencies) {
1179
1231
  const candidates = [
1180
1232
  join(repoRoot, '.codex', 'config.toml'),
@@ -1224,6 +1276,34 @@ async function resolveDispatchCeiling(context, dependencies, options) {
1224
1276
  [provider]: providerResolution,
1225
1277
  };
1226
1278
  if (resolvedValue) {
1279
+ const requiresManagedCodexDepth = options.preflight === true &&
1280
+ provider === 'codex' &&
1281
+ role === 'implementer' &&
1282
+ resolvedValue.mode === 'managed';
1283
+ if (requiresManagedCodexDepth) {
1284
+ const scope = context.scope === 'user' ? 'user' : 'project';
1285
+ const depth = await resolveEffectiveCodexMaxDepth(scope, repoRoot, context, dependencies);
1286
+ const numericDepth = typeof depth.value === 'number' && Number.isFinite(depth.value)
1287
+ ? depth.value
1288
+ : null;
1289
+ if (numericDepth === null || numericDepth < 2) {
1290
+ return {
1291
+ status: 'blocked',
1292
+ provider,
1293
+ value: resolvedValue.value,
1294
+ policyMode: resolvedValue.mode,
1295
+ policy: resolvedValue.policy,
1296
+ source: resolvedValue.source,
1297
+ preset: resolvedValue.preset,
1298
+ unresolved: true,
1299
+ projectPath,
1300
+ providerDefaultEffort,
1301
+ matrix: resolvedValue.matrix,
1302
+ providers,
1303
+ message: managedCodexDepthBlockMessage(scope, depth),
1304
+ };
1305
+ }
1306
+ }
1227
1307
  const incompleteManagedPreflight = options.preflight === true &&
1228
1308
  resolvedValue.mode === 'managed' &&
1229
1309
  providerResolution.mode === 'advisory' &&
@@ -1 +1 @@
1
- {"version":3,"file":"materialize.d.ts","sourceRoot":"","sources":["../../../../src/commands/providers/codex/materialize.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,sBAAsB,EACtB,qCAAqC,EACtC,MAAM,qCAAqC,CAAC;AAU7C,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAsD5C,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,sBAAsB,GAC7B,MAAM,CAaR;AA+GD,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,OAAO,CAAC,qCAAqC,CAAM,GAC7D,OAAO,CAoCT"}
1
+ {"version":3,"file":"materialize.d.ts","sourceRoot":"","sources":["../../../../src/commands/providers/codex/materialize.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,sBAAsB,EACtB,qCAAqC,EACtC,MAAM,qCAAqC,CAAC;AAa7C,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAyD5C,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,sBAAsB,GAC7B,MAAM,CAaR;AAsID,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,OAAO,CAAC,qCAAqC,CAAM,GAC7D,OAAO,CAoCT"}