wei-wuwei 0.1.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ One section per published version, newest first. `wuwei update` prints the
4
4
  sections between the installed version and the new one before it asks;
5
5
  `wuwei check` fails when package.json's version has no section here.
6
6
 
7
+ ## 0.2.0
8
+
9
+ Execute runs at a fixed tier; the model policy now covers review only.
10
+
11
+ - `wuwei-execute-phase` runs at `sonnet` / `xhigh` under every policy: its
12
+ own frontmatter sets the tier for the turn it runs in, and step 2 always
13
+ dispatches the new `wuwei-implementer` agent (pinned `sonnet` / `xhigh`)
14
+ with no model or effort on the call. A stop names `/wuwei-execute-phase`,
15
+ since a reply in chat continues on the session's model.
16
+ - **Changed:** `model policy` under `## Sign-off` is `inherit` | `roles` for
17
+ the review tier (`roles` sends `review:design` and `review:defects` on
18
+ opus). `implementation=…` is refused by `plan-check` and `claim`.
19
+ - `wuwei claim` prints how steps 2 and 5 dispatch, and for Codex how to
20
+ start the session; `wuwei check` ties the skill's and the agent's tier to
21
+ `modelRoles` in `wuwei.config.json`.
22
+ - Codex: `## Sign-off` takes `codex: <model> / <effort>`; `wuwei init`
23
+ offers a `[profiles.wuwei-execute]` in `$CODEX_HOME/config.toml` (removed
24
+ by `wuwei uninstall`); `claim` prints `codex --profile wuwei-execute`.
25
+
7
26
  ## 0.1.0
8
27
 
9
28
  First release.
package/README.md CHANGED
@@ -59,7 +59,8 @@ cd wuwei
59
59
  npm run check # lint frontmatter
60
60
  npm run build # emit repo-local artifacts (.claude/, .agents/, AGENTS.md, CLAUDE.md)
61
61
  node bin/wuwei.js init <project> # repo scope: templates + .wuwei/ skeleton, then
62
- # offers the tools below and names the next step
62
+ # offers the tools below (and, with Codex, an execute
63
+ # profile in $CODEX_HOME/config.toml) and names the next step
63
64
  node bin/wuwei.js install # user scope: skills/agents → ~/.claude, rules → ~/.claude/CLAUDE.md;
64
65
  # Codex: skills → ~/.agents/skills, rules → $CODEX_HOME/AGENTS.md
65
66
  node bin/wuwei.js status <project> # read-only: streams/claims from .wuwei, drift vs STATE.md flagged
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: wuwei-implementer
3
+ description: >-
4
+ The implementation subagent of wuwei-execute-phase step 2, under every model
5
+ policy: one task of a phase, dispatched fresh with SPEC, the phase context,
6
+ the ladder and step 4's floor. Exists so implementation runs at sonnet /
7
+ xhigh — a dispatch alone can set a model but not an effort.
8
+ model: sonnet
9
+ effort: xhigh
10
+ version: 0.1.0
11
+ targets: [claude-code]
12
+ ---
13
+
14
+ # wuwei-implementer
15
+
16
+ ## Mandate
17
+
18
+ Implement the one task you were dispatched with, inside the phase you were
19
+ given, and nothing beyond it. What arrives with the dispatch governs: SPEC,
20
+ the phase context, the ladder (climb it before writing), and step 4's floor.
21
+ `wuwei-tdd` governs the task; commit as `wuwei-commit-hygiene` says — one
22
+ concern per commit, the why in the body.
23
+
24
+ ## Out of scope
25
+
26
+ - Another task, or the phase's next step — hand back instead.
27
+ - A planning defect (a missing `depends-on`, an interface not there yet):
28
+ report it; do not work around it.
29
+ - Review of your own work — a fresh subagent does that.
30
+
31
+ ## Output contract
32
+
33
+ What landed (commit shas, one line each), the tests that ran and their
34
+ result, and anything you stopped short of — with why.
package/bin/wuwei.js CHANGED
@@ -187,10 +187,8 @@ function validate(items) {
187
187
  errs.push(`${it.rel}: description too long (${it.data.description.length} > 400)`)
188
188
  if (it.data.mode && !MODES.includes(it.data.mode))
189
189
  errs.push(`${it.rel}: mode must be one of ${MODES.join('|')}`)
190
- // a skill is not a subagent: these fields would be emitted into SKILL.md
191
- // and ignored, so say it at check time instead of shipping dead frontmatter
192
- if ((it.data.model || it.data.effort) && it.kind !== 'agents')
193
- errs.push(`${it.rel}: model/effort apply to agents only — a skill is not a subagent`)
190
+ // on a skill, Claude Code applies model/effort to the turn the skill runs
191
+ // in (and only that turn — the session's resume on the next prompt)
194
192
  if (it.data.model && !MODEL_ALIASES.includes(it.data.model)
195
193
  && !/^claude-[a-z0-9.-]+$/.test(it.data.model))
196
194
  errs.push(`${it.rel}: model must be ${MODEL_ALIASES.join('|')} or a full id (claude-…)`)
@@ -536,6 +534,21 @@ function lint(items) {
536
534
  for (const rel of Object.keys(budgets))
537
535
  if (!items.some(it => it.rel === rel || (it.refs ?? []).some(r => r.rel === rel)))
538
536
  errs.push(`wuwei.config.json: wordBudgets names ${rel}, which is not a definition or reference`)
537
+ // modelRoles names the tier; the agent file is what dispatch actually reads
538
+ const impl = CONFIG.modelRoles?.implementation
539
+ if (impl) {
540
+ const ag = items.find(it => it.kind === 'agents' && it.data.name === impl.agent)
541
+ if (!ag) errs.push(`wuwei.config.json: modelRoles.implementation.agent ${impl.agent} is not an agent`)
542
+ else for (const f of ['model', 'effort']) if (ag.data[f] !== impl[f])
543
+ errs.push(`${ag.rel}: ${f} ${ag.data[f] ?? '(unset)'} ≠ modelRoles.implementation.${f} ${impl[f]} — the roles tier is what this file pins`)
544
+ }
545
+ // the skill that runs implementation runs its own turn at the same tier
546
+ const sk = impl?.skill && items.find(it => it.kind === 'skills' && it.data.name === impl.skill)
547
+ if (impl?.skill && !sk) errs.push(`wuwei.config.json: modelRoles.implementation.skill ${impl.skill} is not a skill`)
548
+ else if (sk) for (const f of ['model', 'effort']) if (sk.data[f] !== impl[f])
549
+ errs.push(`${sk.rel}: ${f} ${sk.data[f] ?? '(unset)'} ≠ modelRoles.implementation.${f} ${impl[f]} — execute runs at the implementer's tier`)
550
+ for (const l of CONFIG.modelRoles?.review?.lenses ?? [])
551
+ if (!items.some(it => it.kind === 'agents' && it.data.name === l)) errs.push(`wuwei.config.json: modelRoles.review.lenses names ${l}, which is not an agent`)
539
552
  // update prints this file's sections — a version without one updates in silence
540
553
  const changelog = existsSync(join(ROOT, 'CHANGELOG.md')) ? readFileSync(join(ROOT, 'CHANGELOG.md'), 'utf8') : ''
541
554
  if (!new RegExp(`^## v?${escapeRe(PKG.version)}\\b`, 'm').test(changelog))
@@ -692,6 +705,7 @@ async function cmdUninstall(target) {
692
705
  }
693
706
  if (stripManagedBlock(join(codexHome(), 'AGENTS.md')))
694
707
  removed.push(join(codexHome(), 'AGENTS.md') + ' (managed block)')
708
+ if (removeCodexProfile()) removed.push(`${codexConfigPath()} ([profiles.${CODEX_PROFILE}])`)
695
709
  if (existsSync(manifestPath())) { rmSync(manifestPath()); removed.push(manifestPath()) }
696
710
  console.log(`user scope ← ${base}`)
697
711
  for (const r of removed) console.log(` removed ${r}`)
@@ -1152,6 +1166,51 @@ function claimPhases(dest, stream) {
1152
1166
  return { streamBranch, integration, remote, behind, phases }
1153
1167
  }
1154
1168
 
1169
+ // SPEC ## Sign-off's model policy: `inherit` (default, also when the line is
1170
+ // absent) or `roles`, for the review tier. Implementation is not on the menu:
1171
+ // it is always wuwei-implementer at modelRoles.implementation's tier
1172
+ function modelPolicy(spec) {
1173
+ const raw = (signOffField(spec ?? '', 'model policy') ?? 'inherit').trim()
1174
+ const v = (raw.match(/\breview\s*[=:]\s*(\w+)/i)?.[1] ?? raw).toLowerCase()
1175
+ const error = /\bimplementation\b/i.test(raw)
1176
+ ? `model policy '${raw}': implementation is fixed (wuwei-implementer, ${CONFIG.modelRoles?.implementation?.model} / ${CONFIG.modelRoles?.implementation?.effort}) — the policy is inherit or roles, for review`
1177
+ : !['inherit', 'roles'].includes(v) ? `model policy '${raw}': inherit or roles` : null
1178
+ // Codex, when it runs execute: `codex: <model> / <effort>` — the session it is started as
1179
+ const cx = (spec ?? '').split(/^## Sign-off\s*$/m)[1]?.split(/^## /m)[0]?.split('\n').find(l => /^[-*|\s]*codex\s*[:|]/i.test(l))
1180
+ const cv = cx ? (cx.match(/`([^`]+)`/)?.[1] ?? cx.split(/[:|]/).map(x => x.trim()).filter(Boolean).at(-1)) : null
1181
+ const cm = cv?.match(/^(\S+)\s*\/\s*(\S+)$/)
1182
+ const codex = cm ? { model: cm[1], effort: cm[2] } : null
1183
+ const codexError = cx && !cm ? `codex '${cv}': write it as <model> / <effort>`
1184
+ : codex && !CODEX_EFFORTS.includes(codex.effort) ? `codex effort '${codex.effort}': one of ${CODEX_EFFORTS.join('|')}` : null
1185
+ return { raw, tiers: { review: v }, error: error ?? codexError, codex }
1186
+ }
1187
+
1188
+ function codexLine(want) {
1189
+ const { profile } = codexProfile()
1190
+ if (!want) return profile
1191
+ ? `codex start it with \`codex --profile ${CODEX_PROFILE}\` (${profile.model} / ${profile.effort}) — none recorded under ## Sign-off`
1192
+ : 'codex the model Codex was started with — no `codex:` under ## Sign-off'
1193
+ return profile?.model === want.model && profile?.effort === want.effort
1194
+ ? `codex start it with \`codex --profile ${CODEX_PROFILE}\` (${want.model} / ${want.effort})`
1195
+ : `codex start it with \`${codexLaunch(want.model, want.effort)}\`${profile ? ` — profile ${CODEX_PROFILE} is ${profile.model} / ${profile.effort}, not what ## Sign-off says` : ''}`
1196
+ }
1197
+
1198
+ // what step 2 and step 5 dispatch, from the policy and wuwei.config.json's
1199
+ // modelRoles — printed, so the tier is read off the screen, not remembered
1200
+ function dispatchLines(policy) {
1201
+ const roles = CONFIG.modelRoles ?? {}
1202
+ const impl = roles.implementation ?? {}, rev = roles.review ?? {}
1203
+ return [
1204
+ `implement (step 2) subagent_type ${impl.agent}, no model or effort on the call — ${impl.model} / ${impl.effort}, pinned in its agent file`,
1205
+ policy.tiers.review === 'roles'
1206
+ ? `${(rev.lenses ?? []).map(l => l.replace(/^wuwei-review-/, 'review:')).join(', ')} (step 5) model: ${rev.model} — effort from the agent file`
1207
+ : `${(rev.lenses ?? []).map(l => l.replace(/^wuwei-review-/, 'review:')).join(', ')} (step 5) no model — the session's model; effort from the agent file`,
1208
+ 'other lenses no model — the session\'s; wuwei-security-auditor stays fable / high (pinned)',
1209
+ 'inline (no subagents, Codex) the session\'s, whatever the policy — say so in the round record',
1210
+ codexLine(policy.codex),
1211
+ ]
1212
+ }
1213
+
1155
1214
  function cmdClaim(argv) {
1156
1215
  const flag = name => { const i = argv.indexOf(name); return i === -1 ? null : argv[i + 1] }
1157
1216
  const positional = argv.filter((a, i) => !a.startsWith('--') && argv[i - 1] !== '--by')
@@ -1175,6 +1234,10 @@ function cmdClaim(argv) {
1175
1234
  for (const p of r.phases)
1176
1235
  console.log(` ${p.name.padEnd(w)} ${p.state}${p.review === 'human' ? ' ← review: human' : ''}`)
1177
1236
 
1237
+ const policy = modelPolicy(existsSync(join(streamsDir, stream, 'SPEC.md')) ? readFileSync(join(streamsDir, stream, 'SPEC.md'), 'utf8') : '')
1238
+ if (policy.error) { console.error(`\n✗ ${policy.error} — fix ## Sign-off (wuwei plan-check)`); process.exit(1) }
1239
+ console.log(`\ndispatch · model policy: ${policy.raw}\n${dispatchLines(policy).map(l => ` ${l}`).join('\n')}`)
1240
+
1178
1241
  if (!phaseArg) {
1179
1242
  const ready = r.phases.filter(p => p.state === 'ready')
1180
1243
  console.log(ready.length ? `\nclaim one: wuwei claim ${stream} ${ready[0].no}` : '\nnothing ready to claim')
@@ -1283,6 +1346,8 @@ function cmdPlanCheck(argv) {
1283
1346
  const open = signLines.filter(l => /:\s*(\?|tbd|todo|pending|—|-|<[^>]+>)?\s*$/i.test(l) && /\w/.test(l.split(':')[0]) && /^\s*[-*|]/.test(l))
1284
1347
  const auto = signLines.some(l => /\bmode\b[^|\n]*\bauto\b/i.test(l))
1285
1348
  if (auto && open.length) errs.push(`drive mode is auto while ## Sign-off still has open fields: ${open.map(l => l.trim()).join(' / ')} — a partly answered sign-off stays manual`)
1349
+ const policy = spec ? modelPolicy(spec) : null
1350
+ if (policy?.error) errs.push(`## Sign-off: ${policy.error}`)
1286
1351
  const tracker = signLines.find(l => /tracker/i.test(l)) ?? ''
1287
1352
  const linked = tracker && !/\bnone\b/i.test(tracker)
1288
1353
 
@@ -2620,6 +2685,59 @@ async function promptYesNo(question, def = false) {
2620
2685
  }
2621
2686
  }
2622
2687
 
2688
+ async function promptText(question, def = '') {
2689
+ if (!isInteractive()) return def
2690
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
2691
+ try { return (await rl.question(`${question}${def ? ` [${def}]` : ''} `)).trim() || def } catch { return def } finally { rl.close() }
2692
+ }
2693
+
2694
+ // ── Codex: a skill cannot set its model — the session is started with one.
2695
+ // So execute's tier on Codex is a profile in $CODEX_HOME/config.toml that the
2696
+ // session is launched under (codex --profile wuwei-execute); init offers to
2697
+ // write it, claim prints how to start. Effort values: codex-rs ReasoningEffort
2698
+ const CODEX_PROFILE = 'wuwei-execute'
2699
+ const CODEX_EFFORTS = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra', 'persistent']
2700
+ const CODEX_PROFILE_MARK = '# added by wuwei init — wuwei-execute-phase runs under this profile; `wuwei uninstall` removes it'
2701
+ const codexConfigPath = () => join(codexHome(), 'config.toml')
2702
+ // [profiles.wuwei-execute]'s model and effort, or null; plus the top-level model
2703
+ function codexProfile() {
2704
+ const text = existsSync(codexConfigPath()) ? readFileSync(codexConfigPath(), 'utf8') : ''
2705
+ const sec = text.split(/^(?=\[)/m).find(s => new RegExp(`^\\[profiles\\.(?:"${CODEX_PROFILE}"|${CODEX_PROFILE})\\]`).test(s))
2706
+ const val = (s, k) => s?.match(new RegExp(`^${k}\\s*=\\s*"([^"]*)"`, 'm'))?.[1] ?? null
2707
+ const top = text.split(/^\[/m)[0]
2708
+ return { profile: sec ? { model: val(sec, 'model'), effort: val(sec, 'model_reasoning_effort') } : null, topModel: val(top, 'model') }
2709
+ }
2710
+ const codexLaunch = (model, effort) => `codex -m ${model} -c model_reasoning_effort=${effort}`
2711
+
2712
+ async function offerCodexProfile(dry) {
2713
+ if (!existsSync(codexHome())) return
2714
+ const { profile, topModel } = codexProfile()
2715
+ if (profile) { console.log(`
2716
+ codex: profile ${CODEX_PROFILE} — ${profile.model ?? '(model unset)'} / ${profile.effort ?? '(effort unset)'} · start execute with \`codex --profile ${CODEX_PROFILE}\``); return }
2717
+ const snippet = m => [CODEX_PROFILE_MARK, `[profiles.${CODEX_PROFILE}]`, `model = "${m.model}"`, `model_reasoning_effort = "${m.effort}"`].join('\n')
2718
+ console.log(`
2719
+ codex: a skill can't choose Codex's model — the session is started with one. wuwei-execute-phase runs under a profile you launch it with.`)
2720
+ if (!isInteractive()) { console.log(` add to ${codexConfigPath()}:\n${snippet({ model: '<model>', effort: 'xhigh' }).replace(/^/gm, ' ')}`); return }
2721
+ if (!await promptYesNo(` Add [profiles.${CODEX_PROFILE}] to ${codexConfigPath()}?`, true)) { console.log(' skipped — re-run `wuwei init` anytime'); return }
2722
+ const model = await promptText(' model for execute:', topModel ?? '')
2723
+ let effort = await promptText(` reasoning effort (${CODEX_EFFORTS.join('|')}):`, 'xhigh')
2724
+ if (!model || !CODEX_EFFORTS.includes(effort)) { console.log(` ✗ ${!model ? 'no model given' : `effort must be one of ${CODEX_EFFORTS.join('|')}`} — nothing written`); return }
2725
+ if (dry) { console.log(` [dry] would append:\n${snippet({ model, effort }).replace(/^/gm, ' ')}`); return }
2726
+ const prev = existsSync(codexConfigPath()) ? readFileSync(codexConfigPath(), 'utf8') : ''
2727
+ writeFileSync(codexConfigPath(), (prev ? prev.replace(/\n*$/, '\n\n') : '') + snippet({ model, effort }) + '\n')
2728
+ console.log(` ✓ written · start execute with \`codex --profile ${CODEX_PROFILE}\`, and write \`codex: ${model} / ${effort}\` under the stream's ## Sign-off`)
2729
+ }
2730
+
2731
+ // only the section init wrote (its marker line above it), never one you made
2732
+ function removeCodexProfile() {
2733
+ if (!existsSync(codexConfigPath())) return false
2734
+ const text = readFileSync(codexConfigPath(), 'utf8')
2735
+ const re = new RegExp(`\\n*${escapeRe(CODEX_PROFILE_MARK)}\\n\\[profiles\\.${CODEX_PROFILE}\\][^\\[]*`)
2736
+ if (!re.test(text)) return false
2737
+ writeFileSync(codexConfigPath(), text.replace(re, '\n').replace(/^\n+/, '').replace(/\n*$/, '\n'))
2738
+ return true
2739
+ }
2740
+
2623
2741
  async function cmdInit(target, dry) {
2624
2742
  // bootstrap a project: repo-scope install into the target (default: cwd) —
2625
2743
  // the entrypoint `npx <pkg> init` runs from anywhere, package files as ROOT
@@ -2664,6 +2782,7 @@ async function cmdInit(target, dry) {
2664
2782
 
2665
2783
  await offerCodegraphSetup(dest, dry, existing)
2666
2784
  await offerScannerSetup(dry)
2785
+ await offerCodexProfile(dry)
2667
2786
 
2668
2787
  const installHint = isSkillsInstalled()
2669
2788
  ? ''
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wei-wuwei",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "為無為,則無不治 — a source-of-truth skills & agents framework that compiles to Claude Code and Codex (Cursor and Gemini CLI planned). Write once, run in every agent, govern without forcing.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,8 @@ description: >-
9
9
  simplify/review/fix → PR — one phase, or a range on request.
10
10
  version: 0.2.0
11
11
  mode: triggered
12
+ model: sonnet
13
+ effort: xhigh
12
14
  targets: [claude-code, codex]
13
15
  tags: [execution, workflow]
14
16
  ---
@@ -98,9 +100,12 @@ Stopping early is normal — say where, why, and what resumes:
98
100
  ```
99
101
  stopped at 03-appcheck-wiring · bound e2e red (auth/refresh.spec.ts)
100
102
  02 merged. 03 claimed, branch pushed, not merged.
101
- next invocation resumes at 03.
103
+ next invocation resumes at 03: /wuwei-execute-phase
102
104
  ```
103
105
 
106
+ Name the invocation: this skill's sonnet / xhigh holds for the turn it runs
107
+ in, and a reply in chat continues on the session's model.
108
+
104
109
  **Scope is distance, never permission.** `mode` is the stream's standing
105
110
  authorisation from sign-off; scope is only how far one invocation tries to
106
111
  get.
@@ -152,8 +157,9 @@ get.
152
157
  context, **the ladder below**, and **step 4's floor** — and nothing else.
153
158
  The ladder is a question the subagent writing the helper has to ask; an
154
159
  orchestrator holding it while the subagent writes is the same as not having
155
- it. Dispatch at the tier in `## Sign-off` (`inherit` by
156
- default; `sonnet` / `xhigh` under `roles`). Inline instead → watch for
160
+ it. Dispatch `wuwei-implementer` with **no `model` or `effort` on the
161
+ call** — its file pins sonnet / xhigh, and a value on the call beats it.
162
+ Inline instead → watch for
157
163
  drift, keep the phase small, and note that the model policy has nothing to
158
164
  attach to.
159
165
 
@@ -333,8 +339,7 @@ get.
333
339
  `ACCEPTANCE.md`, the **base ref just synced to**, the **tool facts**
334
340
  (`wuwei-review` §1, §5), and **the scope** (`wuwei-review` §6) — plus, for the
335
341
  document panel, **the intended reader and the facts this phase
336
- establishes** (`wuwei-review` §3). Models: the security auditor's pin, the
337
- `## Sign-off` policy for design and defects, the session's for the rest
342
+ establishes** (`wuwei-review` §3). Models: as `wuwei claim` printed them
338
343
  (`wuwei-review` §2).
339
344
 
340
345
  **Round 1 is full scope; every round after it is delta** — dispatched with
@@ -202,15 +202,18 @@ tags: [planning, workflow]
202
202
  - **merge method**: unsquashed by default — a merge commit — so ask only
203
203
  whether this stream squashes instead, plus which methods the repo allows
204
204
  (table below);
205
- - **model policy**: `inherit` (default) | `roles` | a mix. Ask only
205
+ - **model policy** for review: `inherit` (default) | `roles`. Ask only
206
206
  whether to change the default;
207
+ - **Codex**, when it runs execute: the model and effort it is started
208
+ with — `codex: <model> / <effort>`. A skill cannot set Codex's model;
209
+ `wuwei claim` prints the launch (`wuwei init` offers the profile);
207
210
  - whether the plan goes through **its own PR** first — if so, steps 8–9
208
211
  wait for that PR to merge.
209
212
 
210
- Two tiers are configurable, and both are honoured because both dispatch as
211
- subagents: **implementation** (execute step 2, and step 5's
212
- `review:defects` lens) and **review** (step 5's `review:design` lens) —
213
- confirm both, not one. The panel's other lenses inherit the session's
213
+ Implementation is not on the menu: execute runs, and dispatches its
214
+ implementer, at sonnet / xhigh (`wuwei.config.json` `modelRoles`). The
215
+ policy sets **review** — step 5's `review:design` and `review:defects`,
216
+ which dispatch as subagents. The panel's other lenses inherit the session's
214
217
  model, **except `wuwei-security-auditor`**, which pins `fable` / `high` in
215
218
  frontmatter and holds under every policy including `inherit`. Say so,
216
219
  rather than letting a `roles` answer imply it was chosen.
package/wuwei.config.json CHANGED
@@ -16,6 +16,10 @@
16
16
  "failOnDrift": true,
17
17
  "stampGeneratedHeader": true
18
18
  },
19
+ "modelRoles": {
20
+ "implementation": { "skill": "wuwei-execute-phase", "agent": "wuwei-implementer", "model": "sonnet", "effort": "xhigh" },
21
+ "review": { "lenses": ["wuwei-review-design", "wuwei-review-defects"], "model": "opus" }
22
+ },
19
23
  "wordBudgets": {
20
24
  "skills/wuwei-brainstorm/skill.md": 500,
21
25
  "skills/wuwei-commit-hygiene/skill.md": 200,