docks-kit 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -5
- package/README.md +18 -3
- package/cli/docs/flags.md +17 -0
- package/cli/docs/models.md +19 -0
- package/cli/docs/modifiers.md +23 -0
- package/cli/src/commands/models.ts +7 -3
- package/cli/src/commands/status.ts +9 -2
- package/cli/src/engine-native/claudeSync.ts +2 -1
- package/cli/src/engine-native/codexSync.ts +7 -1
- package/cli/src/engine-native/index.ts +17 -1
- package/cli/src/engine-native/parseArgs.ts +64 -0
- package/cli/src/engine-native/sessionRelayReadiness.ts +94 -0
- package/cli/src/engine-native/workflowDeploy.ts +139 -0
- package/cli/src/generated/sotPayload.ts +7 -7
- package/cli/src/main.ts +58 -5
- package/cli/src/workflowModels.ts +395 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -15,9 +15,9 @@ Tool-specific instructions live alongside this file:
|
|
|
15
15
|
| Path | Purpose |
|
|
16
16
|
|------|---------|
|
|
17
17
|
| `docks-kit` | CLI launcher: runs the compiled binary in `cli/dist/` when present, otherwise Bun-from-source (auto-installs Bun + `node_modules`). No-Bun recovery is the platform release binary |
|
|
18
|
-
| `cli/src/engine-native/` | EngineNative implementation for `sync`, `model`, and `toolchain`; idempotent, flag-gated for destructive reconciliation |
|
|
18
|
+
| `cli/src/engine-native/` | EngineNative implementation for `sync`, `model`, `workflow`, and `toolchain`; idempotent, flag-gated for destructive reconciliation |
|
|
19
19
|
| `cli/` | Effect-TS CLI + bundled docs topics |
|
|
20
|
-
| `SoT/models.json` | Kit-verified model catalog |
|
|
20
|
+
| `SoT/models.json` | Kit-verified model catalog plus the strict Docks workflow-role registry |
|
|
21
21
|
| `SoT/toolchain.json` | Toolchain floors manifest (verified pins consumed by EngineNative) |
|
|
22
22
|
| `SoT/.claude/bin/` | Dependency-free Bun runtime programs for Claude's statusline, SessionStart, and Notification |
|
|
23
23
|
| `install.sh` | Global installer |
|
|
@@ -32,9 +32,11 @@ Tool-specific instructions live alongside this file:
|
|
|
32
32
|
|
|
33
33
|
Codex SoT notes:
|
|
34
34
|
- `SoT/.codex/AGENTS.md` deploys to `~/.codex/AGENTS.md` as global Codex instructions.
|
|
35
|
-
- `SoT/.codex/config.toml` pins Codex to `model = "gpt-5.6-sol"`, sets reasoning effort/summaries (`xhigh` + `
|
|
35
|
+
- `SoT/.codex/config.toml` pins Codex to `model = "gpt-5.6-sol"`, sets reasoning effort/summaries (`xhigh` + `concise`), `model_verbosity = "low"`, `personality`, live top-level `web_search`, workspace-write sandboxing with sandboxed command network access, cross-session `memories` (+ dedicated note tools), `[agents]` subagent limits (`max_threads = 12`, `max_depth = 2` — intentionally above Codex defaults for broad parallel kit work; deeper recursion increases cost and predictability risk), a 128 KiB `project_doc_max_bytes` budget for the repo-side AGENTS.md chain (the global `~/.codex/AGENTS.md` is uncapped and not counted), and enables the Docks plugins as `docks@docks`, `session-relay@docks`, and `effect-kit@docks`.
|
|
36
36
|
- `SoT/.codex/rules/*.rules` deploys to `~/.codex/rules/` as kit-managed Codex command policy. This is Codex's equivalent of permission allow/prompt/block rules; user-learned approvals in `~/.codex/rules/default.rules` are preserved.
|
|
37
37
|
- `SoT/.codex/plugins/marketplace.json` deploys to Codex's personal marketplace path at `~/.agents/plugins/marketplace.json`; when the `codex` CLI is available, sync reruns `codex plugin add <plugin@marketplace>` for enabled SoT plugins so stale cached installs are refreshed.
|
|
38
|
+
- `docks-kit status` verifies Session Relay only through the supported `codex plugin list --json` inventory. `ready` means installed and enabled for a newly started Codex session; it is not evidence about an old process, lifecycle state, receive-path health, or worker quiescence. The global prompt SoTs carry the owner's standing authorization for Docks cross-company plan review, which never overrides host or platform denial.
|
|
39
|
+
- Both global prompt SoTs carry one byte-identical compact `Docks-workflow-models:` record. Root `--model-orchestrator` / `--model-reviewer` / `--model-implementer` / review-bound flags update only that deployed record; `docks-kit models workflow` lists the closed selectors, and a flag-less sync restores defaults.
|
|
38
40
|
- The `codex` CLI binary is upstream-owned, not kit-owned. The official standalone installer keeps package metadata under `$CODEX_HOME/packages/standalone` and places the `codex` symlink in `~/.local/bin` by default; sync only warns with a download-then-run installer command when the CLI is missing. Existing installs can self-update with `codex update`; npm and Homebrew remain upstream alternatives.
|
|
39
41
|
- `SoT/.codex/AGENTS.md` deliberately does not import `@RTK.md`: RTK's published Codex integration is prompt-file based rather than hook based, so importing it leaks implementation detail into agent-visible context. Use Codex hooks for RTK only after the kit installs a hook-backed Codex integration.
|
|
40
42
|
- Claude runtime settings are an authoring template with sentinels. `claudeRuntime.ts` materializes absolute Bun/script paths only after the shared `bun.ts` bootstrap is ready; `claudeSync.ts` writes all runtime assets before atomically committing settings, then prunes the legacy shell scripts and Stop hook. Native `rate_limits` is the sole quota source, so jq/curl/OAuth caches are not runtime dependencies. A missing Bun defers only this cutover and preserves legacy pointers/files.
|
|
@@ -70,6 +72,8 @@ For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`), see the matching SoT d
|
|
|
70
72
|
|
|
71
73
|
Automated coverage includes `bun run test:unit`, `bun run golden:dryrun`, and `bun run golden:mutation`; prove-red modes must exit non-zero after detecting planted mismatches. Also verify user-facing changes via `./docks-kit sync --dry-run`, per-tool sanity (`/doctor`, `/plugin`, etc.), and `diff <(jq -S . <SoT>) <(jq -S . <deployed>)` recipes from the per-tool file.
|
|
72
74
|
|
|
75
|
+
Use direct acceptance and focused regressions while iterating, then run the full unit/golden gate once at the pre-commit or release boundary. Reuse still-matching evidence; a later relevant edit invalidates only the affected rung and final gate, not every prior check.
|
|
76
|
+
|
|
73
77
|
## Skills
|
|
74
78
|
|
|
75
79
|
This project ships **kit-mechanic skills** under `.claude/skills/` — narrowly-scoped references for how EngineNative works (settings merge, plugin bootstrap, universal-skill install, Codex TOML merge, sync orchestration). They cost prompt tokens only inside this repo's sessions and document regression-prone TypeScript sync logic in `cli/src/engine-native/`. **Pipeline content** (multi-agent slash commands, refactor/security/docs workflows, parallel-scanner agents) belongs in the separate [DocksDocks/docks](https://github.com/DocksDocks/docks) plugin — not here. Project-level agents under `.claude/agents/` follow the same rule: kit-mechanic agents that wrap kit-mechanic skills are permitted; pipeline agents live in the docks plugin.
|
|
@@ -83,10 +87,10 @@ When a kit-mechanic skill, its `references/`, or a wrapper agent (`.claude/agent
|
|
|
83
87
|
## Plans
|
|
84
88
|
|
|
85
89
|
<constraint>
|
|
86
|
-
Multi-commit work plans live in `docs/plans/active/` (status is a frontmatter field) and `docs/plans/finished/` (archive). Every plan file is a complete handoff document —
|
|
90
|
+
Multi-commit work plans live in `docs/plans/active/` (status is a frontmatter field) and `docs/plans/finished/` (archive). Every plan file is a complete cold-handoff document — goal, context & rationale, environment & how-to-run, steps with exact paths, executable acceptance criteria, and a binary cold-handoff checklist — so any agent (or a weaker model) can pick one up cold without guessing. Skills handle every operation: `plan-init` (bootstrap/migrate), `plan-manager` (list/show/start/block/ship/new, auto-commit on transition, self-review on draft), `plan-review` (verification). Trigger by natural language or the matching `plan-*` skill. `active/` is multi-occupancy.
|
|
87
91
|
</constraint>
|
|
88
92
|
|
|
89
|
-
The full convention (frontmatter schema, body sections, self-review
|
|
93
|
+
The full convention (frontmatter schema, body sections, one-pass local self-review, bounded strong-default X/S review receipts, workflow roles, open-questions, age tokens) lives in `docs/plans/AGENTS.md`. `docs/plans/CLAUDE.md` is a one-line `@AGENTS.md` import for Claude Code's nested discovery. If `.codex/agents/plan-manager.toml` and `.codex/agents/plan-review.toml` exist, Codex may use them for explicit subagent delegation; otherwise run the matching `plan-*` skill inline.
|
|
90
94
|
|
|
91
95
|
Distinct from per-tool **Open Concerns** sections (wait-on-upstream
|
|
92
96
|
blockers tied to a vendor shipping a fix — these live inside the per-tool
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ RTK/Bun download boundaries, not as a global sync prerequisite.
|
|
|
39
39
|
docks-kit sync [claude] [codex] [agents] deploy the SoT (default: all three)
|
|
40
40
|
docks-kit update [--no-sync] self-update the kit (autodetects checkout vs global install), then sync
|
|
41
41
|
docks-kit model <claude|codex> [value] get/set the DEPLOYED model (TTY picker)
|
|
42
|
-
docks-kit models [
|
|
42
|
+
docks-kit models [claude|codex|workflow] model and workflow-role catalogs (`--json`)
|
|
43
43
|
docks-kit toolchain [check|ensure <tool>] verified-version floors for external tools
|
|
44
44
|
docks-kit status [--json] deployed-vs-SoT drift + toolchain + counts
|
|
45
45
|
docks-kit plugins list [--json] enabledPlugins tri-state vs installed
|
|
@@ -71,6 +71,21 @@ golden-regression coverage for dry-run output, mutation snapshots, and argv logs
|
|
|
71
71
|
and a later flag-less sync reverts them. Full reference: `docks-kit docs flags`
|
|
72
72
|
(includes the old→new rename table for the pre-CLI `sync.sh` flags).
|
|
73
73
|
|
|
74
|
+
### Docks workflow roles (root flags)
|
|
75
|
+
|
|
76
|
+
| Flag | Effect |
|
|
77
|
+
|------|--------|
|
|
78
|
+
| `--model-orchestrator=<selector>` | Override the Docks orchestrator candidate/profile |
|
|
79
|
+
| `--model-reviewer=<selector>` | Override the Docks reviewer candidate/profile |
|
|
80
|
+
| `--model-implementer=<selector>` | Override the Docks implementer candidate/profile |
|
|
81
|
+
| `--review-min-score=<0..100>` | Override the completion-review target |
|
|
82
|
+
| `--review-max-rounds=<1..10>` | Bound completion-review batches |
|
|
83
|
+
|
|
84
|
+
Selectors are strict `profile:<name>` or `<tool>:<model>@<effort>` values from
|
|
85
|
+
`docks-kit models workflow`. These root flags update only the identical compact
|
|
86
|
+
record in `~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`; omitted fields retain
|
|
87
|
+
their current valid values, while a later flag-less sync restores all defaults.
|
|
88
|
+
|
|
74
89
|
## How syncing works
|
|
75
90
|
|
|
76
91
|
- **Additive by default** — user-only settings keys, plugins, and skills
|
|
@@ -83,7 +98,7 @@ and a later flag-less sync reverts them. Full reference: `docks-kit docs flags`
|
|
|
83
98
|
(`--yes` accepts; non-TTY falls back to the pinned verified version
|
|
84
99
|
when possible). `docks-kit toolchain check` shows the full table.
|
|
85
100
|
- **Model catalog** — `SoT/models.json` is the research-verified source for
|
|
86
|
-
model validation, listings, and
|
|
101
|
+
model validation, listings, pickers, and the Docks workflow-role registry.
|
|
87
102
|
- **Claude runtime** — sync materializes three dependency-free Bun `.mjs`
|
|
88
103
|
programs for statusline, SessionStart, and Notification. Quota display uses
|
|
89
104
|
Claude's native `rate_limits`; there is no OAuth fetch, shared usage cache,
|
|
@@ -97,7 +112,7 @@ and a later flag-less sync reverts them. Full reference: `docks-kit docs flags`
|
|
|
97
112
|
| `SoT/.claude/` | Claude Code SoT (settings template, Bun runtime programs, CLAUDE.md) |
|
|
98
113
|
| `SoT/.codex/` | Codex SoT (config.toml, rules, AGENTS.md, marketplace) |
|
|
99
114
|
| `SoT/.agents/` | Universal-skill manifest |
|
|
100
|
-
| `SoT/models.json` | Kit-verified model catalog |
|
|
115
|
+
| `SoT/models.json` | Kit-verified model and Docks workflow-role catalog |
|
|
101
116
|
| `SoT/toolchain.json` | Verified-version floors |
|
|
102
117
|
| `cli/src/engine-native/` | EngineNative sync/model/toolchain implementation |
|
|
103
118
|
| `cli/src/generated/sotPayload.ts` | Generated in-memory payload used by standalone and npm installs |
|
package/cli/docs/flags.md
CHANGED
|
@@ -39,6 +39,23 @@ Bare model, effort, or advisor modifiers print the relevant valid-value catalog
|
|
|
39
39
|
and exit 2. A modifier for a target not selected by the positional arguments is
|
|
40
40
|
ignored with a warning; Claude modifiers never touch Codex config and vice versa.
|
|
41
41
|
|
|
42
|
+
## Docks workflow root flags
|
|
43
|
+
|
|
44
|
+
These flags belong at the root (`docks-kit --model-reviewer=...`), not after
|
|
45
|
+
`sync`. They update only the shared deployed workflow record.
|
|
46
|
+
|
|
47
|
+
| Flag | Accepted value |
|
|
48
|
+
|------|----------------|
|
|
49
|
+
| `--model-orchestrator=<selector>` | `profile:<name>` or `<tool>:<model>@<effort>` |
|
|
50
|
+
| `--model-reviewer=<selector>` | same strict selector grammar |
|
|
51
|
+
| `--model-implementer=<selector>` | same strict selector grammar |
|
|
52
|
+
| `--review-min-score=<n>` | base-10 integer `0..100` |
|
|
53
|
+
| `--review-max-rounds=<n>` | base-10 integer `1..10` |
|
|
54
|
+
|
|
55
|
+
Use `docks-kit models workflow [--json]` for the closed registry. Bare, empty,
|
|
56
|
+
unknown, or malformed values print that helper and exit 2 before either prompt
|
|
57
|
+
file changes.
|
|
58
|
+
|
|
42
59
|
## Renamed legacy flags (pre-CLI sync.sh)
|
|
43
60
|
|
|
44
61
|
Old flags exit with a rename hint — there is no compat behavior.
|
package/cli/docs/models.md
CHANGED
|
@@ -31,8 +31,27 @@ docks-kit models claude --json # machine-readable
|
|
|
31
31
|
docks-kit model claude # current deployed + SoT + picker (TTY)
|
|
32
32
|
docks-kit model claude opus # per-machine override from the Fable SoT
|
|
33
33
|
docks-kit sync claude --claude-model=opus # same, as part of a sync
|
|
34
|
+
docks-kit models workflow # Docks workflow profiles and defaults
|
|
35
|
+
docks-kit models workflow --json # closed machine-readable registry
|
|
34
36
|
```
|
|
35
37
|
|
|
38
|
+
## Docks workflow-role registry
|
|
39
|
+
|
|
40
|
+
The `workflow` section is strict even though ordinary tool-model modifiers are
|
|
41
|
+
forward-compatible. It accepts only known `profile:<name>` values or exact
|
|
42
|
+
`<tool>:<model>@<effort>` targets whose model and effort both appear in the
|
|
43
|
+
catalog. The defaults are:
|
|
44
|
+
|
|
45
|
+
- orchestrator: `profile:claude-best`, ordered as `claude:fable@high` then
|
|
46
|
+
`claude:opus@xhigh`;
|
|
47
|
+
- reviewer and implementer: `codex:gpt-5.6-sol@xhigh`;
|
|
48
|
+
- review minimum score 90 and maximum 3 rounds.
|
|
49
|
+
|
|
50
|
+
`claude:best@high` is Claude's native one-model alias. It is deliberately not
|
|
51
|
+
the same as the Docks-managed `profile:claude-best` candidate chain.
|
|
52
|
+
Availability is `checked_when_used`: Docks classifies each launch attempt;
|
|
53
|
+
docks-kit neither probes providers nor promises provider-wide fallback.
|
|
54
|
+
|
|
36
55
|
## Advisor pairing note (Claude)
|
|
37
56
|
|
|
38
57
|
The SoT ships `model: fable` with advisor off (`advisorModel` unset).
|
package/cli/docs/modifiers.md
CHANGED
|
@@ -44,6 +44,29 @@ docks-kit model codex gpt-5.5 # codex flavor
|
|
|
44
44
|
The set path calls the same engine functions as the sync flags —
|
|
45
45
|
one implementation, two entry points.
|
|
46
46
|
|
|
47
|
+
## Workflow-role overrides (root, no full sync)
|
|
48
|
+
|
|
49
|
+
The root workflow flags are deployed modifiers for Docks rather than one tool:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
docks-kit --model-orchestrator=profile:claude-best \
|
|
53
|
+
--model-reviewer=codex:gpt-5.6-terra@high \
|
|
54
|
+
--review-min-score=80 --review-max-rounds=5
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
They atomically write one byte-identical complete record to
|
|
58
|
+
`~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`. Omitted fields retain the
|
|
59
|
+
current valid record; a missing side is repaired. Invalid or conflicting state
|
|
60
|
+
stops before mutation, a repeated request is a no-op, and failure on the second
|
|
61
|
+
atomic write restores both snapshots. A later flag-less sync restores the
|
|
62
|
+
embedded defaults. Start fresh sessions after changing the record.
|
|
63
|
+
|
|
64
|
+
Selectors are strict `profile:<name>` or `<tool>:<model>@<effort>` entries from
|
|
65
|
+
`docks-kit models workflow`. `profile:claude-best` is the ordered Docks
|
|
66
|
+
Fable-high then Opus-xhigh chain; `claude:best@high` is instead Claude's native
|
|
67
|
+
single-model alias. Docks checks each candidate when used—there is no docks-kit
|
|
68
|
+
provider preflight.
|
|
69
|
+
|
|
47
70
|
## Sticky opt-ins are NOT modifiers
|
|
48
71
|
|
|
49
72
|
`--claude-plugin=<name>` installs+enables an optional plugin and it STAYS
|
|
@@ -2,9 +2,10 @@ import { Args, Command, Options } from "@effect/cli"
|
|
|
2
2
|
import { Console, Effect, Option } from "effect"
|
|
3
3
|
import { bail } from "../engine"
|
|
4
4
|
import { modelCatalog, type Tool } from "../manifests"
|
|
5
|
+
import { workflowCatalog, workflowRegistryJson } from "../workflowModels"
|
|
5
6
|
|
|
6
7
|
const tool = Args.text({ name: "tool" }).pipe(
|
|
7
|
-
Args.withDescription("claude | codex (omit for both)"),
|
|
8
|
+
Args.withDescription("claude | codex | workflow (omit for both tool catalogs)"),
|
|
8
9
|
Args.optional
|
|
9
10
|
)
|
|
10
11
|
const json = Options.boolean("json").pipe(
|
|
@@ -24,8 +25,11 @@ const renderTool = (t: Tool) =>
|
|
|
24
25
|
export const modelsCommand = Command.make("models", { tool, json }, (config) =>
|
|
25
26
|
Effect.gen(function* () {
|
|
26
27
|
const requested = Option.getOrUndefined(config.tool)
|
|
28
|
+
if (requested === "workflow") {
|
|
29
|
+
return yield* Console.log(config.json ? workflowRegistryJson() : workflowCatalog())
|
|
30
|
+
}
|
|
27
31
|
if (requested !== undefined && requested !== "claude" && requested !== "codex") {
|
|
28
|
-
return yield* bail(`Unknown tool '${requested}' (valid: claude, codex)`)
|
|
32
|
+
return yield* bail(`Unknown tool '${requested}' (valid: claude, codex, workflow)`)
|
|
29
33
|
}
|
|
30
34
|
const tools: Array<Tool> = requested !== undefined ? [requested as Tool] : ["claude", "codex"]
|
|
31
35
|
|
|
@@ -42,5 +46,5 @@ export const modelsCommand = Command.make("models", { tool, json }, (config) =>
|
|
|
42
46
|
)
|
|
43
47
|
})
|
|
44
48
|
).pipe(
|
|
45
|
-
Command.withDescription("List
|
|
49
|
+
Command.withDescription("List kit-verified tool models or the Docks workflow role registry (SoT/models.json).")
|
|
46
50
|
)
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
sotCodexModel
|
|
11
11
|
} from "../manifests"
|
|
12
12
|
import { kitHome } from "../kitHome"
|
|
13
|
+
import { sessionRelayReadiness } from "../engine-native/sessionRelayReadiness"
|
|
13
14
|
|
|
14
15
|
const json = Options.boolean("json").pipe(
|
|
15
16
|
Options.withDescription("Machine-readable output")
|
|
@@ -47,11 +48,12 @@ export const statusCommand = Command.make("status", { json }, (config) =>
|
|
|
47
48
|
const drift = gatherDrift()
|
|
48
49
|
const plugins = pluginsView()
|
|
49
50
|
const skills = skillsView()
|
|
51
|
+
const sessionRelay = sessionRelayReadiness()
|
|
50
52
|
const toolchainTable = yield* engineCapture(["toolchain", "check"])
|
|
51
53
|
|
|
52
54
|
if (config.json) {
|
|
53
55
|
return yield* Console.log(
|
|
54
|
-
JSON.stringify({ kitHome: kitHome(), drift, plugins, skills, toolchainTable }, null, 2)
|
|
56
|
+
JSON.stringify({ kitHome: kitHome(), drift, plugins, skills, sessionRelayReadiness: sessionRelay, toolchainTable }, null, 2)
|
|
55
57
|
)
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -67,6 +69,11 @@ export const statusCommand = Command.make("status", { json }, (config) =>
|
|
|
67
69
|
yield* Console.log(
|
|
68
70
|
`\nPlugins: ${plugins.length} known (${enabled} SoT-enabled) — details: docks-kit plugins list`
|
|
69
71
|
)
|
|
72
|
+
yield* Console.log(
|
|
73
|
+
sessionRelay.state === "ready"
|
|
74
|
+
? `Session Relay: ready for new Codex sessions (v${sessionRelay.version})`
|
|
75
|
+
: `Session Relay: unavailable for new Codex sessions (${sessionRelay.reason})`
|
|
76
|
+
)
|
|
70
77
|
const installed = skills.filter((s) => s.installed).length
|
|
71
78
|
yield* Console.log(
|
|
72
79
|
`Skills: ${skills.length} known (${installed} installed) — details: docks-kit skills list`
|
|
@@ -74,6 +81,6 @@ export const statusCommand = Command.make("status", { json }, (config) =>
|
|
|
74
81
|
})
|
|
75
82
|
).pipe(
|
|
76
83
|
Command.withDescription(
|
|
77
|
-
"Doctor view: deployed-vs-SoT drift
|
|
84
|
+
"Doctor view: deployed-vs-SoT drift, toolchain, plugin/skill counts, and Session Relay readiness for new Codex sessions."
|
|
78
85
|
)
|
|
79
86
|
)
|
|
@@ -31,6 +31,7 @@ import { ExitError } from "./parseArgs"
|
|
|
31
31
|
import { mergeSettings, reconcileSettings } from "./settings"
|
|
32
32
|
import { ensure, field } from "./toolchain"
|
|
33
33
|
import { payloadBytes, payloadDisplayPath, payloadText } from "../payload"
|
|
34
|
+
import { renderDefaultWorkflowInstructions } from "./workflowDeploy"
|
|
34
35
|
|
|
35
36
|
export type ClaudeRuntimeState =
|
|
36
37
|
| { readonly kind: "ready"; readonly paths: ClaudeRuntimePaths }
|
|
@@ -222,7 +223,7 @@ function syncClaudeMd(ctx: Ctx, claudeDir: string): void {
|
|
|
222
223
|
return
|
|
223
224
|
}
|
|
224
225
|
|
|
225
|
-
const source = payloadText("SoT/.claude/CLAUDE.md")
|
|
226
|
+
const source = renderDefaultWorkflowInstructions(payloadText("SoT/.claude/CLAUDE.md"))
|
|
226
227
|
const stripReason = ctx.skipRtk ? "--skip-rtk" : rtkMdAbsent ? "~/.claude/RTK.md absent (rtk not initialized)" : ""
|
|
227
228
|
if (stripReason !== "") {
|
|
228
229
|
const stripped = source
|
|
@@ -10,7 +10,9 @@ import { syncCodexEffort, syncCodexModel, replaceTopLevelSettingInFile } from ".
|
|
|
10
10
|
import { p } from "./exec"
|
|
11
11
|
import type { Ctx } from "./index"
|
|
12
12
|
import { compareCodepoints, isObject, jqStringify, parseJson, type Json } from "./jq"
|
|
13
|
+
import { sessionRelayReadiness } from "./sessionRelayReadiness"
|
|
13
14
|
import { payloadBytes, payloadDisplayPath, payloadPaths, payloadText, type PayloadPath } from "../payload"
|
|
15
|
+
import { renderDefaultWorkflowInstructions } from "./workflowDeploy"
|
|
14
16
|
|
|
15
17
|
export function codexSync(ctx: Ctx): void {
|
|
16
18
|
const codexDir = p(ctx.home, ".codex")
|
|
@@ -23,7 +25,7 @@ export function codexSync(ctx: Ctx): void {
|
|
|
23
25
|
syncCodexModel(ctx, ctx.codexModel)
|
|
24
26
|
syncCodexEffort(ctx, ctx.codexEffort)
|
|
25
27
|
syncRules(ctx, payloadPaths("SoT/.codex/rules/"), p(codexDir, "rules"))
|
|
26
|
-
syncAgentsMd(ctx, payloadText("SoT/.codex/AGENTS.md"), p(codexDir, "AGENTS.md"))
|
|
28
|
+
syncAgentsMd(ctx, renderDefaultWorkflowInstructions(payloadText("SoT/.codex/AGENTS.md")), p(codexDir, "AGENTS.md"))
|
|
27
29
|
syncMarketplace(ctx, payloadText("SoT/.codex/plugins/marketplace.json"), p(ctx.agentsDir, "plugins", "marketplace.json"))
|
|
28
30
|
removeLegacyDocksMarketplace(ctx, userConfig)
|
|
29
31
|
syncPlugins(ctx, sotConfig)
|
|
@@ -476,6 +478,10 @@ function syncPlugins(ctx: Ctx, sotConfigText: string): void {
|
|
|
476
478
|
if (refreshed > 0) {
|
|
477
479
|
change(`Codex plugins synced (plugins: ~${refreshed})`)
|
|
478
480
|
ctx.nextStepTriggers.codexRestart = true
|
|
481
|
+
const readiness = sessionRelayReadiness()
|
|
482
|
+
if (readiness.state !== "ready") {
|
|
483
|
+
warn(`Session Relay readiness unavailable after refresh: ${readiness.reason}`)
|
|
484
|
+
}
|
|
479
485
|
}
|
|
480
486
|
if (failed > 0) warn(`${failed} Codex plugin operation(s) failed — re-run sync or install manually`)
|
|
481
487
|
}
|
|
@@ -15,7 +15,8 @@ import { claudeNextSteps, claudeSummary, claudeSync } from "./claudeSync"
|
|
|
15
15
|
import { codexNextSteps, codexSummary, codexSync } from "./codexSync"
|
|
16
16
|
import { skillsNextSteps, skillsSummary, skillsSync } from "./skillsSync"
|
|
17
17
|
import { modeModel, modeToolchain } from "./modes"
|
|
18
|
-
import { ExitError, parseArgs, validateModifierFlags } from "./parseArgs"
|
|
18
|
+
import { ExitError, parseArgs, parseWorkflowArgs, printWorkflowUsage, validateModifierFlags } from "./parseArgs"
|
|
19
|
+
import { deployWorkflowOverrides } from "./workflowDeploy"
|
|
19
20
|
|
|
20
21
|
export type ModifierFlag =
|
|
21
22
|
| "--claude-model"
|
|
@@ -124,6 +125,19 @@ function engineSync(ctx: Ctx, args: ReadonlyArray<string>): number {
|
|
|
124
125
|
return 0
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
function engineWorkflow(ctx: Ctx, args: ReadonlyArray<string>): number {
|
|
129
|
+
try {
|
|
130
|
+
const overrides = parseWorkflowArgs(ctx, args)
|
|
131
|
+
deployWorkflowOverrides(ctx, overrides)
|
|
132
|
+
return 0
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error instanceof ExitError) throw error
|
|
135
|
+
printWorkflowUsage(ctx)
|
|
136
|
+
ctx.services.logger.err(error instanceof Error ? error.message : String(error))
|
|
137
|
+
throw new ExitError(2)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
127
141
|
export function runEngineNative(argv: ReadonlyArray<string>, services?: EngineServices): number {
|
|
128
142
|
let ctx!: Ctx
|
|
129
143
|
const baseServices = services ?? makeEngineServices()
|
|
@@ -149,6 +163,8 @@ export function runEngineNative(argv: ReadonlyArray<string>, services?: EngineSe
|
|
|
149
163
|
return modeModel(ctx, argv.slice(1))
|
|
150
164
|
case "toolchain":
|
|
151
165
|
return modeToolchain(ctx, argv.slice(1))
|
|
166
|
+
case "workflow":
|
|
167
|
+
return engineWorkflow(ctx, argv.slice(1))
|
|
152
168
|
case "sync":
|
|
153
169
|
return engineSync(ctx, argv.slice(1))
|
|
154
170
|
default:
|
|
@@ -16,6 +16,11 @@ import {
|
|
|
16
16
|
isEffortModifierValue
|
|
17
17
|
} from "../efforts"
|
|
18
18
|
import { printModels, validateClaudeModel, validateCodexModel } from "./models"
|
|
19
|
+
import {
|
|
20
|
+
buildWorkflowRecord,
|
|
21
|
+
workflowCatalog,
|
|
22
|
+
type WorkflowOverrides
|
|
23
|
+
} from "../workflowModels"
|
|
19
24
|
|
|
20
25
|
export class ExitError extends Error {
|
|
21
26
|
constructor(readonly code: number) {
|
|
@@ -259,6 +264,65 @@ function printCatalog(ctx: Ctx, catalog: string): void {
|
|
|
259
264
|
for (const line of catalog.split("\n")) ctx.services.logger.echo(line)
|
|
260
265
|
}
|
|
261
266
|
|
|
267
|
+
const WORKFLOW_FLAGS = {
|
|
268
|
+
"--model-orchestrator": "orchestrator",
|
|
269
|
+
"--model-reviewer": "reviewer",
|
|
270
|
+
"--model-implementer": "implementer",
|
|
271
|
+
"--review-min-score": "minimumScore",
|
|
272
|
+
"--review-max-rounds": "maxRounds"
|
|
273
|
+
} as const satisfies Readonly<Record<string, keyof WorkflowOverrides>>
|
|
274
|
+
|
|
275
|
+
function workflowUsage(ctx: Ctx): void {
|
|
276
|
+
printCatalog(ctx, workflowCatalog())
|
|
277
|
+
ctx.services.logger.echo("")
|
|
278
|
+
ctx.services.logger.echo("Workflow override flags:")
|
|
279
|
+
ctx.services.logger.echo(" --model-orchestrator=<profile:name|tool:model@effort>")
|
|
280
|
+
ctx.services.logger.echo(" --model-reviewer=<profile:name|tool:model@effort>")
|
|
281
|
+
ctx.services.logger.echo(" --model-implementer=<profile:name|tool:model@effort>")
|
|
282
|
+
ctx.services.logger.echo(" --review-min-score=<0..100>")
|
|
283
|
+
ctx.services.logger.echo(" --review-max-rounds=<1..10>")
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function parseWorkflowArgs(ctx: Ctx, args: ReadonlyArray<string>): WorkflowOverrides {
|
|
287
|
+
const overrides: Partial<Record<keyof WorkflowOverrides, string>> = {}
|
|
288
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
289
|
+
const arg = args[index] ?? ""
|
|
290
|
+
if (arg === "-h" || arg === "--help") {
|
|
291
|
+
workflowUsage(ctx)
|
|
292
|
+
throw new ExitError(0)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const equals = arg.indexOf("=")
|
|
296
|
+
const flag = equals === -1 ? arg : arg.slice(0, equals)
|
|
297
|
+
const key = WORKFLOW_FLAGS[flag as keyof typeof WORKFLOW_FLAGS]
|
|
298
|
+
if (key === undefined) throw new Error(`Unknown workflow arg: ${arg}`)
|
|
299
|
+
|
|
300
|
+
let value: string
|
|
301
|
+
if (equals !== -1) {
|
|
302
|
+
value = arg.slice(equals + 1)
|
|
303
|
+
} else {
|
|
304
|
+
const next = args[index + 1]
|
|
305
|
+
if (next === undefined || next.startsWith("--")) {
|
|
306
|
+
throw new Error(`${flag} requires a value: ${flag}=<value>`)
|
|
307
|
+
}
|
|
308
|
+
value = next
|
|
309
|
+
index += 1
|
|
310
|
+
}
|
|
311
|
+
if (value === "") throw new Error(`${flag} requires a value: ${flag}=<value>`)
|
|
312
|
+
overrides[key] = value
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (Object.keys(overrides).length === 0) {
|
|
316
|
+
throw new Error("At least one workflow override flag is required")
|
|
317
|
+
}
|
|
318
|
+
buildWorkflowRecord(overrides)
|
|
319
|
+
return overrides
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function printWorkflowUsage(ctx: Ctx): void {
|
|
323
|
+
workflowUsage(ctx)
|
|
324
|
+
}
|
|
325
|
+
|
|
262
326
|
export function validateModifierFlags(ctx: Ctx): void {
|
|
263
327
|
const { err, warn } = ctx.services.logger
|
|
264
328
|
const supplied = (flag: ModifierFlag, value: string): boolean =>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process"
|
|
2
|
+
|
|
3
|
+
import { isObject, parseJson } from "./jq"
|
|
4
|
+
|
|
5
|
+
const SESSION_RELAY_PLUGIN_ID = "session-relay@docks"
|
|
6
|
+
|
|
7
|
+
export type SessionRelayReadinessReason =
|
|
8
|
+
| "codex_cli_unavailable"
|
|
9
|
+
| "plugin_list_failed"
|
|
10
|
+
| "invalid_plugin_list"
|
|
11
|
+
| "plugin_missing"
|
|
12
|
+
| "plugin_ambiguous"
|
|
13
|
+
| "plugin_not_installed"
|
|
14
|
+
| "plugin_disabled"
|
|
15
|
+
|
|
16
|
+
export interface SessionRelayReadiness {
|
|
17
|
+
readonly schema: 1
|
|
18
|
+
readonly state: "ready" | "unavailable"
|
|
19
|
+
readonly reason: SessionRelayReadinessReason | null
|
|
20
|
+
readonly version: string | null
|
|
21
|
+
readonly installed: boolean
|
|
22
|
+
readonly enabled: boolean
|
|
23
|
+
readonly scope: "new_sessions"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CodexPluginListProbe {
|
|
27
|
+
readonly status: number | null
|
|
28
|
+
readonly stdout: string
|
|
29
|
+
readonly errorCode: string | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function unavailable(
|
|
33
|
+
reason: SessionRelayReadinessReason,
|
|
34
|
+
fields: { version?: string | null; installed?: boolean; enabled?: boolean } = {}
|
|
35
|
+
): SessionRelayReadiness {
|
|
36
|
+
return {
|
|
37
|
+
schema: 1,
|
|
38
|
+
state: "unavailable",
|
|
39
|
+
reason,
|
|
40
|
+
version: fields.version ?? null,
|
|
41
|
+
installed: fields.installed ?? false,
|
|
42
|
+
enabled: fields.enabled ?? false,
|
|
43
|
+
scope: "new_sessions"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function classifySessionRelayReadiness(probe: CodexPluginListProbe): SessionRelayReadiness {
|
|
48
|
+
if (probe.errorCode === "ENOENT") return unavailable("codex_cli_unavailable")
|
|
49
|
+
if (probe.errorCode !== null || probe.status !== 0) return unavailable("plugin_list_failed")
|
|
50
|
+
|
|
51
|
+
const value = parseJson(probe.stdout)
|
|
52
|
+
if (value === undefined || !isObject(value) || !Array.isArray(value["installed"])) {
|
|
53
|
+
return unavailable("invalid_plugin_list")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const matches = value["installed"].filter(
|
|
57
|
+
(entry) => isObject(entry) && entry["pluginId"] === SESSION_RELAY_PLUGIN_ID
|
|
58
|
+
)
|
|
59
|
+
if (matches.length === 0) return unavailable("plugin_missing")
|
|
60
|
+
if (matches.length !== 1) return unavailable("plugin_ambiguous")
|
|
61
|
+
|
|
62
|
+
const row = matches[0]
|
|
63
|
+
if (row === undefined || !isObject(row)) return unavailable("invalid_plugin_list")
|
|
64
|
+
const version = row["version"]
|
|
65
|
+
const installed = row["installed"]
|
|
66
|
+
const enabled = row["enabled"]
|
|
67
|
+
if (typeof version !== "string" || version.length === 0 || typeof installed !== "boolean" || typeof enabled !== "boolean") {
|
|
68
|
+
return unavailable("invalid_plugin_list")
|
|
69
|
+
}
|
|
70
|
+
if (!installed) return unavailable("plugin_not_installed", { version, enabled })
|
|
71
|
+
if (!enabled) return unavailable("plugin_disabled", { version, installed })
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
schema: 1,
|
|
75
|
+
state: "ready",
|
|
76
|
+
reason: null,
|
|
77
|
+
version,
|
|
78
|
+
installed,
|
|
79
|
+
enabled,
|
|
80
|
+
scope: "new_sessions"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function sessionRelayReadiness(): SessionRelayReadiness {
|
|
85
|
+
const result = spawnSync("codex", ["plugin", "list", "--json"], {
|
|
86
|
+
encoding: "utf8",
|
|
87
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
88
|
+
})
|
|
89
|
+
return classifySessionRelayReadiness({
|
|
90
|
+
status: result.status,
|
|
91
|
+
stdout: result.stdout ?? "",
|
|
92
|
+
errorCode: (result.error as NodeJS.ErrnoException | undefined)?.code ?? null
|
|
93
|
+
})
|
|
94
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"
|
|
2
|
+
import { dirname } from "node:path"
|
|
3
|
+
|
|
4
|
+
import type { Ctx } from "./index"
|
|
5
|
+
import {
|
|
6
|
+
WORKFLOW_RECORD_PREFIX,
|
|
7
|
+
buildWorkflowRecord,
|
|
8
|
+
defaultWorkflowRecord,
|
|
9
|
+
parseWorkflowRecord,
|
|
10
|
+
renderWorkflowRecordLine,
|
|
11
|
+
type WorkflowOverrides,
|
|
12
|
+
type WorkflowRecordV1
|
|
13
|
+
} from "../workflowModels"
|
|
14
|
+
|
|
15
|
+
export interface WorkflowFileSystem {
|
|
16
|
+
readonly read: (path: string) => string | undefined
|
|
17
|
+
readonly writeAtomic: (path: string, bytes: string) => void
|
|
18
|
+
readonly remove: (path: string) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let atomicWriteSequence = 0
|
|
22
|
+
|
|
23
|
+
const nodeFileSystem: WorkflowFileSystem = {
|
|
24
|
+
read: (path) => existsSync(path) ? readFileSync(path, "utf8") : undefined,
|
|
25
|
+
writeAtomic: (path, bytes) => {
|
|
26
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
27
|
+
const temporary = `${path}.tmp-${process.pid}-${atomicWriteSequence++}`
|
|
28
|
+
try {
|
|
29
|
+
writeFileSync(temporary, bytes)
|
|
30
|
+
renameSync(temporary, path)
|
|
31
|
+
} finally {
|
|
32
|
+
rmSync(temporary, { force: true })
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
remove: (path) => rmSync(path, { force: true })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface DocumentRecord {
|
|
39
|
+
readonly record: WorkflowRecordV1 | undefined
|
|
40
|
+
readonly line: string | undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readDocumentRecord(document: string | undefined, label: string): DocumentRecord {
|
|
44
|
+
const lines = (document ?? "").split("\n").filter((line) => line.startsWith(WORKFLOW_RECORD_PREFIX))
|
|
45
|
+
if (lines.length === 0) return { record: undefined, line: undefined }
|
|
46
|
+
|
|
47
|
+
let canonicalLine: string | undefined
|
|
48
|
+
let canonicalRecord: WorkflowRecordV1 | undefined
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
let record: WorkflowRecordV1
|
|
51
|
+
try {
|
|
52
|
+
record = parseWorkflowRecord(JSON.parse(line.slice(WORKFLOW_RECORD_PREFIX.length)) as unknown)
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const detail = error instanceof Error ? error.message : String(error)
|
|
55
|
+
throw new Error(`${label} contains a malformed Docks workflow record: ${detail}`)
|
|
56
|
+
}
|
|
57
|
+
const rendered = renderWorkflowRecordLine(record)
|
|
58
|
+
if (canonicalLine !== undefined && rendered !== canonicalLine) {
|
|
59
|
+
throw new Error(`${label} contains conflicting Docks workflow records`)
|
|
60
|
+
}
|
|
61
|
+
canonicalLine = rendered
|
|
62
|
+
canonicalRecord = record
|
|
63
|
+
}
|
|
64
|
+
return { record: canonicalRecord, line: canonicalLine }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function upsertWorkflowRecord(document: string, record: WorkflowRecordV1): string {
|
|
68
|
+
const content = document
|
|
69
|
+
.split("\n")
|
|
70
|
+
.filter((line) => !line.startsWith(WORKFLOW_RECORD_PREFIX))
|
|
71
|
+
.join("\n")
|
|
72
|
+
.replace(/\n+$/, "")
|
|
73
|
+
const line = renderWorkflowRecordLine(record)
|
|
74
|
+
return content === "" ? `${line}\n` : `${content}\n${line}\n`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function renderDefaultWorkflowInstructions(document: string): string {
|
|
78
|
+
readDocumentRecord(document, "SoT instruction document")
|
|
79
|
+
return upsertWorkflowRecord(document, defaultWorkflowRecord())
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function restoreSnapshot(fileSystem: WorkflowFileSystem, path: string, snapshot: string | undefined): void {
|
|
83
|
+
if (snapshot === undefined) fileSystem.remove(path)
|
|
84
|
+
else fileSystem.writeAtomic(path, snapshot)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function deployWorkflowOverrides(
|
|
88
|
+
ctx: Ctx,
|
|
89
|
+
overrides: WorkflowOverrides,
|
|
90
|
+
fileSystem: WorkflowFileSystem = nodeFileSystem
|
|
91
|
+
): void {
|
|
92
|
+
const claudePath = `${ctx.home}/.claude/CLAUDE.md`
|
|
93
|
+
const codexPath = `${ctx.home}/.codex/AGENTS.md`
|
|
94
|
+
const claudeBefore = fileSystem.read(claudePath)
|
|
95
|
+
const codexBefore = fileSystem.read(codexPath)
|
|
96
|
+
const claudeState = readDocumentRecord(claudeBefore, claudePath)
|
|
97
|
+
const codexState = readDocumentRecord(codexBefore, codexPath)
|
|
98
|
+
|
|
99
|
+
if (claudeState.line !== undefined && codexState.line !== undefined && claudeState.line !== codexState.line) {
|
|
100
|
+
throw new Error("Claude and Codex instruction files contain conflicting Docks workflow records")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const base = claudeState.record ?? codexState.record ?? defaultWorkflowRecord()
|
|
104
|
+
const record = buildWorkflowRecord(overrides, base)
|
|
105
|
+
const claudeAfter = upsertWorkflowRecord(claudeBefore ?? "", record)
|
|
106
|
+
const codexAfter = upsertWorkflowRecord(codexBefore ?? "", record)
|
|
107
|
+
const claudeChanged = claudeBefore !== claudeAfter
|
|
108
|
+
const codexChanged = codexBefore !== codexAfter
|
|
109
|
+
|
|
110
|
+
if (ctx.dryRun) {
|
|
111
|
+
if (claudeChanged) ctx.services.logger.echo(`[dry-run] update Docks workflow record in ${claudePath}`)
|
|
112
|
+
if (codexChanged) ctx.services.logger.echo(`[dry-run] update Docks workflow record in ${codexPath}`)
|
|
113
|
+
if (!claudeChanged && !codexChanged) ctx.services.logger.verbose("Workflow models already match the requested state")
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!claudeChanged && !codexChanged) {
|
|
118
|
+
ctx.services.logger.verbose("Workflow models already match the requested state")
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
if (claudeChanged) fileSystem.writeAtomic(claudePath, claudeAfter)
|
|
124
|
+
if (codexChanged) fileSystem.writeAtomic(codexPath, codexAfter)
|
|
125
|
+
} catch (error) {
|
|
126
|
+
try {
|
|
127
|
+
restoreSnapshot(fileSystem, claudePath, claudeBefore)
|
|
128
|
+
restoreSnapshot(fileSystem, codexPath, codexBefore)
|
|
129
|
+
} finally {
|
|
130
|
+
ctx.services.logger.err("Workflow update failed; restored both instruction files to their pre-run state")
|
|
131
|
+
}
|
|
132
|
+
throw error
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
ctx.services.logger.change("Workflow models updated in Claude and Codex instructions")
|
|
136
|
+
ctx.services.logger.warn("Start fresh Claude Code and Codex sessions before relying on the new workflow roles")
|
|
137
|
+
ctx.nextStepTriggers.claudeRestart = true
|
|
138
|
+
ctx.nextStepTriggers.codexRestart = true
|
|
139
|
+
}
|