@hecer/yoke 1.11.0 → 1.12.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/.claude-plugin/plugin.json +13 -13
- package/.codex-plugin/plugin.json +7 -7
- package/CHANGELOG.md +416 -398
- package/README.md +931 -915
- package/TODOS.md +5 -5
- package/agents/docs.toml +6 -6
- package/agents/implementer.toml +6 -6
- package/agents/reviewer.toml +6 -6
- package/agents/security.toml +6 -6
- package/bench/README.md +86 -86
- package/bench/RESULTS.md +35 -35
- package/bench/output-compaction.mjs +65 -65
- package/bench/result-schema.mjs +12 -12
- package/bench/results/claude-2026-07-27T18-03-26.json +50 -50
- package/bench/results/codex-unavailable-1785175418318.json +15 -15
- package/bench/results/gemini-2026-07-27T18-03-44.json +46 -46
- package/bench/run-matrix.mjs +26 -26
- package/bench/run.mjs +106 -106
- package/canon/AGENTS.md +30 -30
- package/canon/context/DECISIONS.md +4 -4
- package/canon/context/GLOSSARY.md +11 -11
- package/canon/context/KNOWLEDGE.md +4 -4
- package/canon/context/PROJECT.md +15 -15
- package/canon/loop/loop-spec.md +65 -65
- package/canon/loop/prd.schema.md +41 -41
- package/canon/manifest.yaml +59 -59
- package/canon/policy/gates.md +7 -7
- package/canon/policy/roles.md +9 -9
- package/canon/skills/ATTRIBUTION.md +99 -99
- package/canon/skills/authoring-prd/SKILL.md +56 -56
- package/canon/skills/brainstorming/SKILL.md +164 -164
- package/canon/skills/codebase-design/DEEPENING.md +15 -15
- package/canon/skills/codebase-design/DESIGN-IT-TWICE.md +12 -12
- package/canon/skills/codebase-design/SKILL.md +39 -39
- package/canon/skills/dispatching-parallel-agents/SKILL.md +182 -182
- package/canon/skills/document-release/SKILL.md +302 -302
- package/canon/skills/domain-modeling/ADR-FORMAT.md +19 -19
- package/canon/skills/domain-modeling/CONTEXT-FORMAT.md +39 -39
- package/canon/skills/domain-modeling/SKILL.md +35 -35
- package/canon/skills/executing-plans/SKILL.md +70 -70
- package/canon/skills/finishing-a-development-branch/SKILL.md +200 -200
- package/canon/skills/health/SKILL.md +177 -177
- package/canon/skills/maintaining-context/SKILL.md +34 -34
- package/canon/skills/minimal-code/SKILL.md +21 -21
- package/canon/skills/no-ai-slop/SKILL.md +103 -103
- package/canon/skills/no-ai-slop/eval.md +43 -43
- package/canon/skills/plan-ceo-review/SKILL.md +541 -541
- package/canon/skills/plan-eng-review/SKILL.md +362 -362
- package/canon/skills/receiving-code-review/SKILL.md +213 -213
- package/canon/skills/requesting-code-review/SKILL.md +105 -105
- package/canon/skills/resolving-merge-conflicts/SKILL.md +18 -18
- package/canon/skills/retro/SKILL.md +397 -397
- package/canon/skills/review/SKILL.md +246 -246
- package/canon/skills/ship/SKILL.md +691 -691
- package/canon/skills/subagent-driven-development/SKILL.md +277 -277
- package/canon/skills/systematic-debugging/SKILL.md +296 -296
- package/canon/skills/tdd/SKILL.md +371 -371
- package/canon/skills/unslop-ui/SKILL.md +34 -34
- package/canon/skills/using-git-worktrees/SKILL.md +218 -218
- package/canon/skills/verification-before-completion/SKILL.md +139 -139
- package/canon/skills/visual-verification/SKILL.md +54 -54
- package/canon/skills/workflow/SKILL.md +22 -22
- package/canon/skills/writing-for-agents/SKILL-MECHANICS.md +27 -27
- package/canon/skills/writing-for-agents/SKILL.md +42 -42
- package/canon/skills/writing-plans/SKILL.md +152 -152
- package/canon/skills/writing-skills/SKILL.md +655 -655
- package/canon/skills/yoke-retrofit/SKILL.md +26 -26
- package/canon/skills/yoke-workflow/SKILL.md +20 -20
- package/canon/tools/codex-rtk-hook.mjs +35 -35
- package/canon/tools/gemini-rtk-hook.mjs +25 -25
- package/canon/tools/graphify.md +3 -3
- package/canon/tools/playwright-mcp.md +3 -3
- package/canon/tools/qwen-rtk-hook.mjs +25 -0
- package/canon/tools/rtk.md +7 -7
- package/canon/tools/serena.md +6 -6
- package/dist/agents/host.js +1 -1
- package/dist/agents/providers.js +18 -5
- package/dist/agents/telemetry.js +35 -36
- package/dist/cli.js +18 -10
- package/dist/dashboard/page.js +122 -122
- package/dist/dashboard/panels.js +91 -91
- package/dist/loop/run-command.js +3 -3
- package/dist/prd/command.js +17 -17
- package/dist/retrofit/apply.js +8 -1
- package/dist/retrofit/config.js +1 -1
- package/dist/retrofit/detect.js +2 -0
- package/dist/retrofit/planners/claude.js +14 -14
- package/dist/retrofit/planners/qwen.js +3 -3
- package/dist/retrofit/preserve.js +2 -2
- package/dist/retrofit/qwen-settings.js +17 -0
- package/dist/retrofit/skill-actions.js +1 -1
- package/dist/setup/command.js +22 -8
- package/dist/setup/model-presets.js +48 -0
- package/docs/CAPABILITY-ROUTING.md +51 -51
- package/docs/DASHBOARD-EVOLUTION.md +33 -33
- package/docs/MIGRATING-TO-1.0.md +33 -33
- package/docs/MIGRATING-TO-1.1.md +27 -27
- package/docs/MIGRATING-TO-1.4.md +70 -70
- package/docs/PRODUCT-DIRECTION-2026-09-05.md +210 -210
- package/docs/PUBLISHING.md +114 -114
- package/docs/QWEN-MODEL-SUPPORT.md +142 -0
- package/docs/VERIFIED-PROJECTS-VALIDATION.md +29 -29
- package/docs/VERIFIED-PROJECTS.md +167 -167
- package/docs/superpowers/plans/2026-06-28-baustein-e-context-layer.md +981 -981
- package/docs/superpowers/plans/2026-06-29-baustein-f-routing.md +258 -258
- package/docs/superpowers/plans/2026-06-29-baustein-g-loop-observability.md +1006 -1006
- package/docs/superpowers/plans/2026-06-29-baustein-h-loop-robustness.md +374 -374
- package/docs/superpowers/plans/2026-06-30-baustein-i-visual-design-verification.md +450 -450
- package/docs/superpowers/plans/2026-07-02-baustein-k-zero-to-100-bootstrap.md +1024 -1024
- package/docs/superpowers/plans/2026-07-02-baustein-m-flow-smoke-proofs.md +574 -574
- package/docs/superpowers/plans/2026-08-13-gauntlet-quality-loop.md +537 -537
- package/docs/superpowers/plans/2026-08-16-artifact-backed-output-compaction.md +329 -329
- package/docs/superpowers/plans/2026-09-05-verified-projects.md +83 -83
- package/docs/superpowers/specs/2026-06-28-baustein-e-context-layer-design.md +146 -146
- package/docs/superpowers/specs/2026-06-29-baustein-f-routing-design.md +106 -106
- package/docs/superpowers/specs/2026-06-29-baustein-g-loop-observability-design.md +186 -186
- package/docs/superpowers/specs/2026-06-29-baustein-h-loop-robustness-design.md +113 -113
- package/docs/superpowers/specs/2026-06-30-baustein-i-visual-design-verification-design.md +98 -98
- package/docs/superpowers/specs/2026-07-02-baustein-k-zero-to-100-bootstrap-design.md +200 -200
- package/docs/superpowers/specs/2026-07-02-baustein-m-flow-smoke-proofs-design.md +155 -155
- package/docs/superpowers/specs/2026-08-13-gauntlet-quality-loop-design.md +422 -422
- package/docs/superpowers/specs/2026-08-16-artifact-backed-output-compaction-design.md +166 -166
- package/gemini-extension.json +6 -6
- package/hooks/hooks.json +19 -19
- package/package.json +87 -87
- package/dist/dashboard/discovery.js +0 -73
- package/docs/community-outreach-2026-08-20.md +0 -85
- package/docs/launch-copy-2026-08-21.md +0 -193
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: yoke-retrofit
|
|
3
|
-
description: Use when asked to "retrofit", "yoke this project", or set up the Yoke harness in a project — runs the shared setup wizard and configures the same behavior for Claude, Codex, and Gemini.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Yoke Retrofit
|
|
7
|
-
|
|
8
|
-
Set up or update Yoke through the shared `yoke setup` contract.
|
|
9
|
-
|
|
10
|
-
1. Inspect the project and identify the current host (`claude`, `codex`, or `gemini`).
|
|
11
|
-
2. Ask these setup questions one at a time and give a direct recommendation:
|
|
12
|
-
- target agents (recommend the current host; use `all` for deliberately cross-agent projects),
|
|
13
|
-
- code-graph tool,
|
|
14
|
-
- autonomous loop on/off,
|
|
15
|
-
- default runner (recommend the current host),
|
|
16
|
-
- decision mode: `auto` or `critical`.
|
|
17
|
-
3. Recommend the code graph based on this project:
|
|
18
|
-
- **Serena** is LSP-accurate and best for large typed codebases or systematic symbol refactors where a missed reference is costly. It needs a language server per language.
|
|
19
|
-
- **graphify** is fast and multimodal, and is best for exploration, migration, onboarding, or mixed code and document repositories. Its graph is an index and can become stale.
|
|
20
|
-
4. Apply the answers without a second round of prompts:
|
|
21
|
-
`yoke setup . --yes --host=<host> --agent=<agents> --code-graph=<choice> --runner=<runner> --decision-policy=<auto|critical> --loop|--no-loop`.
|
|
22
|
-
A human who runs `yoke setup .` directly receives the same five terminal questions.
|
|
23
|
-
5. Show the generated report and backup paths. Existing files are backed up under `.yoke/backup/`; settings are merged where supported.
|
|
24
|
-
6. If an old generated `CLAUDE.md` or `GEMINI.md` contained project-specific instructions, restore them inside its `<!-- yoke:preserve:start -->` / `<!-- yoke:preserve:end -->` block. Preserve blocks survive every later retrofit.
|
|
25
|
-
|
|
26
|
-
The generated harness includes the provider-neutral `yoke-workflow` skill. It owns the planning questions, approved-plan handoff, autonomous stories, and critical-decision resume flow.
|
|
1
|
+
---
|
|
2
|
+
name: yoke-retrofit
|
|
3
|
+
description: Use when asked to "retrofit", "yoke this project", or set up the Yoke harness in a project — runs the shared setup wizard and configures the same behavior for Claude, Codex, and Gemini.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Yoke Retrofit
|
|
7
|
+
|
|
8
|
+
Set up or update Yoke through the shared `yoke setup` contract.
|
|
9
|
+
|
|
10
|
+
1. Inspect the project and identify the current host (`claude`, `codex`, or `gemini`).
|
|
11
|
+
2. Ask these setup questions one at a time and give a direct recommendation:
|
|
12
|
+
- target agents (recommend the current host; use `all` for deliberately cross-agent projects),
|
|
13
|
+
- code-graph tool,
|
|
14
|
+
- autonomous loop on/off,
|
|
15
|
+
- default runner (recommend the current host),
|
|
16
|
+
- decision mode: `auto` or `critical`.
|
|
17
|
+
3. Recommend the code graph based on this project:
|
|
18
|
+
- **Serena** is LSP-accurate and best for large typed codebases or systematic symbol refactors where a missed reference is costly. It needs a language server per language.
|
|
19
|
+
- **graphify** is fast and multimodal, and is best for exploration, migration, onboarding, or mixed code and document repositories. Its graph is an index and can become stale.
|
|
20
|
+
4. Apply the answers without a second round of prompts:
|
|
21
|
+
`yoke setup . --yes --host=<host> --agent=<agents> --code-graph=<choice> --runner=<runner> --decision-policy=<auto|critical> --loop|--no-loop`.
|
|
22
|
+
A human who runs `yoke setup .` directly receives the same five terminal questions.
|
|
23
|
+
5. Show the generated report and backup paths. Existing files are backed up under `.yoke/backup/`; settings are merged where supported.
|
|
24
|
+
6. If an old generated `CLAUDE.md` or `GEMINI.md` contained project-specific instructions, restore them inside its `<!-- yoke:preserve:start -->` / `<!-- yoke:preserve:end -->` block. Preserve blocks survive every later retrofit.
|
|
25
|
+
|
|
26
|
+
The generated harness includes the provider-neutral `yoke-workflow` skill. It owns the planning questions, approved-plan handoff, autonomous stories, and critical-decision resume flow.
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: yoke-workflow
|
|
3
|
-
description: Use when the user asks Yoke to plan and build a feature, run stories autonomously, continue a Yoke loop, or only interrupt for major decisions.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Yoke Workflow
|
|
7
|
-
|
|
8
|
-
Provide the same interaction contract in Claude, Codex, and Gemini.
|
|
9
|
-
|
|
10
|
-
1. Read `.yoke/config.yaml`. If it is missing, offer `yoke setup . --host=<current-agent>` and run the setup flow before planning.
|
|
11
|
-
2. Plan before starting the loop. Inspect the project, then ask one focused question at a time only where the answer changes product behavior, scope, architecture, security, data ownership, external cost, or an irreversible choice. Include a recommended answer. Resolve routine implementation details yourself.
|
|
12
|
-
3. Summarize the agreed design in `.yoke/plan.md`, including goals, non-goals, constraints, and decisions. Use the `authoring-prd` skill to turn it into small stories with testable acceptance criteria. Run `yoke prd check .`.
|
|
13
|
-
4. Ask once for approval of the complete plan and story set. Do not begin implementation before that approval.
|
|
14
|
-
5. If `loop.enabled` is true, run the stories without routine follow-up questions using the configured runner. Prefer `yoke loop run . --max=5 --isolate`, report status after each batch, and continue until complete or genuinely blocked.
|
|
15
|
-
6. Respect `loop.decisionPolicy`:
|
|
16
|
-
- `auto`: choose the most suitable option from the plan, current code, and established conventions. Record the interpretation and continue.
|
|
17
|
-
- `critical`: routine ambiguity is still resolved automatically. If the loop reports a pending critical decision, run `yoke loop decision .`, present its options and recommendation to the user, ask exactly that question, then run `yoke loop answer . --choice=<id> --rationale="<answer>"`. The answer command records the decision and resumes the same story.
|
|
18
|
-
7. Never ask whether to run tests, review, commit, or continue to the next approved story. Those are part of the approved workflow.
|
|
19
|
-
|
|
20
|
-
The user's configured commit identity is authoritative. Do not add an AI co-author unless `commit.allowCoAuthors` explicitly permits it.
|
|
1
|
+
---
|
|
2
|
+
name: yoke-workflow
|
|
3
|
+
description: Use when the user asks Yoke to plan and build a feature, run stories autonomously, continue a Yoke loop, or only interrupt for major decisions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Yoke Workflow
|
|
7
|
+
|
|
8
|
+
Provide the same interaction contract in Claude, Codex, and Gemini.
|
|
9
|
+
|
|
10
|
+
1. Read `.yoke/config.yaml`. If it is missing, offer `yoke setup . --host=<current-agent>` and run the setup flow before planning.
|
|
11
|
+
2. Plan before starting the loop. Inspect the project, then ask one focused question at a time only where the answer changes product behavior, scope, architecture, security, data ownership, external cost, or an irreversible choice. Include a recommended answer. Resolve routine implementation details yourself.
|
|
12
|
+
3. Summarize the agreed design in `.yoke/plan.md`, including goals, non-goals, constraints, and decisions. Use the `authoring-prd` skill to turn it into small stories with testable acceptance criteria. Run `yoke prd check .`.
|
|
13
|
+
4. Ask once for approval of the complete plan and story set. Do not begin implementation before that approval.
|
|
14
|
+
5. If `loop.enabled` is true, run the stories without routine follow-up questions using the configured runner. Prefer `yoke loop run . --max=5 --isolate`, report status after each batch, and continue until complete or genuinely blocked.
|
|
15
|
+
6. Respect `loop.decisionPolicy`:
|
|
16
|
+
- `auto`: choose the most suitable option from the plan, current code, and established conventions. Record the interpretation and continue.
|
|
17
|
+
- `critical`: routine ambiguity is still resolved automatically. If the loop reports a pending critical decision, run `yoke loop decision .`, present its options and recommendation to the user, ask exactly that question, then run `yoke loop answer . --choice=<id> --rationale="<answer>"`. The answer command records the decision and resumes the same story.
|
|
18
|
+
7. Never ask whether to run tests, review, commit, or continue to the next approved story. Those are part of the approved workflow.
|
|
19
|
+
|
|
20
|
+
The user's configured commit identity is authoritative. Do not add an AI co-author unless `commit.allowCoAuthors` explicitly permits it.
|
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import { pathToFileURL } from 'node:url'
|
|
4
|
-
|
|
5
|
-
function rtkCheck(command) {
|
|
6
|
-
const result = spawnSync('rtk', ['hook', 'check', command], { encoding: 'utf8', timeout: 3000 })
|
|
7
|
-
return result.status === 0 ? result.stdout.trim() : ''
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function rewriteHookInput(input, check = rtkCheck) {
|
|
11
|
-
if (input?.tool_name !== 'Bash' && input?.toolName !== 'Bash') return null
|
|
12
|
-
const toolInput = input.tool_input ?? input.toolInput
|
|
13
|
-
const command = toolInput?.command
|
|
14
|
-
if (typeof command !== 'string' || command.trim() === '') return null
|
|
15
|
-
const rewritten = check(command)
|
|
16
|
-
if (!rewritten || rewritten === command) return null
|
|
17
|
-
return {
|
|
18
|
-
hookSpecificOutput: {
|
|
19
|
-
hookEventName: 'PreToolUse',
|
|
20
|
-
updatedInput: { ...toolInput, command: rewritten },
|
|
21
|
-
},
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function main() {
|
|
26
|
-
let raw = ''
|
|
27
|
-
for await (const chunk of process.stdin) raw += chunk
|
|
28
|
-
try {
|
|
29
|
-
const output = rewriteHookInput(JSON.parse(raw))
|
|
30
|
-
if (output) process.stdout.write(JSON.stringify(output))
|
|
31
|
-
} catch {
|
|
32
|
-
// Compression is an optimization. Malformed input must never block Codex.
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) await main()
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
|
+
|
|
5
|
+
function rtkCheck(command) {
|
|
6
|
+
const result = spawnSync('rtk', ['hook', 'check', command], { encoding: 'utf8', timeout: 3000 })
|
|
7
|
+
return result.status === 0 ? result.stdout.trim() : ''
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function rewriteHookInput(input, check = rtkCheck) {
|
|
11
|
+
if (input?.tool_name !== 'Bash' && input?.toolName !== 'Bash') return null
|
|
12
|
+
const toolInput = input.tool_input ?? input.toolInput
|
|
13
|
+
const command = toolInput?.command
|
|
14
|
+
if (typeof command !== 'string' || command.trim() === '') return null
|
|
15
|
+
const rewritten = check(command)
|
|
16
|
+
if (!rewritten || rewritten === command) return null
|
|
17
|
+
return {
|
|
18
|
+
hookSpecificOutput: {
|
|
19
|
+
hookEventName: 'PreToolUse',
|
|
20
|
+
updatedInput: { ...toolInput, command: rewritten },
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
let raw = ''
|
|
27
|
+
for await (const chunk of process.stdin) raw += chunk
|
|
28
|
+
try {
|
|
29
|
+
const output = rewriteHookInput(JSON.parse(raw))
|
|
30
|
+
if (output) process.stdout.write(JSON.stringify(output))
|
|
31
|
+
} catch {
|
|
32
|
+
// Compression is an optimization. Malformed input must never block Codex.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) await main()
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Pure BeforeTool argument adapter. Never evaluates or executes tool commands.
|
|
3
|
-
import { readFileSync } from 'node:fs'
|
|
4
|
-
|
|
5
|
-
const record = value => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
6
|
-
try {
|
|
7
|
-
const event = JSON.parse(readFileSync(0, 'utf8'))
|
|
8
|
-
if (!record(event) || typeof event.tool_name !== 'string' ||
|
|
9
|
-
(event.hook_event_name !== undefined && event.hook_event_name !== 'BeforeTool')) throw new Error('event')
|
|
10
|
-
let response = {}
|
|
11
|
-
if (event.tool_name === 'run_shell_command') {
|
|
12
|
-
if (!record(event.tool_input) || typeof event.tool_input.command !== 'string' || !event.tool_input.command.trim() || event.tool_input.command.includes('\0')) throw new Error('command')
|
|
13
|
-
const command = event.tool_input.command
|
|
14
|
-
// Restrict rewriting to simple supported invocations. Leave shell syntax,
|
|
15
|
-
// quoted executables, assignments and existing RTK wrappers untouched.
|
|
16
|
-
if (!/[;&|<>`$\r\n()]/u.test(command) && /^\s*(?:git|rg|npm|npx|cargo|pytest|go|docker|kubectl)\s/u.test(command)) {
|
|
17
|
-
const rewritten = command.trimStart().replace(/^rg\s/u, 'grep ')
|
|
18
|
-
response = { hookSpecificOutput: { tool_input: { ...event.tool_input, command: `rtk ${rewritten}` } } }
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
process.stdout.write(JSON.stringify(response) + '\n')
|
|
22
|
-
} catch {
|
|
23
|
-
process.stderr.write('Invalid Gemini BeforeTool hook input\n')
|
|
24
|
-
process.exitCode = 2
|
|
25
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Pure BeforeTool argument adapter. Never evaluates or executes tool commands.
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
|
|
5
|
+
const record = value => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
6
|
+
try {
|
|
7
|
+
const event = JSON.parse(readFileSync(0, 'utf8'))
|
|
8
|
+
if (!record(event) || typeof event.tool_name !== 'string' ||
|
|
9
|
+
(event.hook_event_name !== undefined && event.hook_event_name !== 'BeforeTool')) throw new Error('event')
|
|
10
|
+
let response = {}
|
|
11
|
+
if (event.tool_name === 'run_shell_command') {
|
|
12
|
+
if (!record(event.tool_input) || typeof event.tool_input.command !== 'string' || !event.tool_input.command.trim() || event.tool_input.command.includes('\0')) throw new Error('command')
|
|
13
|
+
const command = event.tool_input.command
|
|
14
|
+
// Restrict rewriting to simple supported invocations. Leave shell syntax,
|
|
15
|
+
// quoted executables, assignments and existing RTK wrappers untouched.
|
|
16
|
+
if (!/[;&|<>`$\r\n()]/u.test(command) && /^\s*(?:git|rg|npm|npx|cargo|pytest|go|docker|kubectl)\s/u.test(command)) {
|
|
17
|
+
const rewritten = command.trimStart().replace(/^rg\s/u, 'grep ')
|
|
18
|
+
response = { hookSpecificOutput: { tool_input: { ...event.tool_input, command: `rtk ${rewritten}` } } }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
process.stdout.write(JSON.stringify(response) + '\n')
|
|
22
|
+
} catch {
|
|
23
|
+
process.stderr.write('Invalid Gemini BeforeTool hook input\n')
|
|
24
|
+
process.exitCode = 2
|
|
25
|
+
}
|
package/canon/tools/graphify.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
# Tool: graphify (code-graph)
|
|
2
|
-
|
|
3
|
-
MIT, multimodal code/doc graph. Wired as an MCP server for all three agents (stdio). Prefer symbol/graph lookups over reading whole files. Caveat: heuristic edges (INFERRED/AMBIGUOUS) and a static index that can go stale — rebuild on significant changes.
|
|
1
|
+
# Tool: graphify (code-graph)
|
|
2
|
+
|
|
3
|
+
MIT, multimodal code/doc graph. Wired as an MCP server for all three agents (stdio). Prefer symbol/graph lookups over reading whole files. Caveat: heuristic edges (INFERRED/AMBIGUOUS) and a static index that can go stale — rebuild on significant changes.
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
# Tool: Playwright MCP (browser / dogfooding)
|
|
2
|
-
|
|
3
|
-
Microsoft Playwright MCP, Apache-2.0. Wired as an MCP server for all three agents — the only browser tool with native MCP parity across Claude/Codex/Gemini. Used for QA, dogfooding user flows, screenshots, and deploy verification.
|
|
1
|
+
# Tool: Playwright MCP (browser / dogfooding)
|
|
2
|
+
|
|
3
|
+
Microsoft Playwright MCP, Apache-2.0. Wired as an MCP server for all three agents — the only browser tool with native MCP parity across Claude/Codex/Gemini. Used for QA, dogfooding user flows, screenshots, and deploy verification.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PreToolUse guard requesting a retry through RTK. Never evaluates or executes tool commands.
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
|
|
5
|
+
const record = value => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
6
|
+
try {
|
|
7
|
+
const event = JSON.parse(readFileSync(0, 'utf8'))
|
|
8
|
+
if (!record(event) || typeof event.tool_name !== 'string' ||
|
|
9
|
+
(event.hook_event_name !== undefined && event.hook_event_name !== 'PreToolUse')) throw new Error('event')
|
|
10
|
+
let response = {}
|
|
11
|
+
if (event.tool_name === 'run_shell_command') {
|
|
12
|
+
if (!record(event.tool_input) || typeof event.tool_input.command !== 'string' || !event.tool_input.command.trim() || event.tool_input.command.includes('\0')) throw new Error('command')
|
|
13
|
+
const command = event.tool_input.command
|
|
14
|
+
// Restrict rewriting to simple supported invocations. Leave shell syntax,
|
|
15
|
+
// quoted executables, assignments and existing RTK wrappers untouched.
|
|
16
|
+
if (!/[;&|<>`$\r\n()]/u.test(command) && /^\s*(?:git|rg|npm|npx|cargo|pytest|go|docker|kubectl)\s/u.test(command)) {
|
|
17
|
+
const rewritten = command.trimStart().replace(/^rg\s/u, 'grep ')
|
|
18
|
+
response = { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: `Retry this command through RTK: rtk ${rewritten}` } }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
process.stdout.write(JSON.stringify(response) + '\n')
|
|
22
|
+
} catch {
|
|
23
|
+
process.stderr.write('Invalid Qwen PreToolUse hook input\n')
|
|
24
|
+
process.exitCode = 2
|
|
25
|
+
}
|
package/canon/tools/rtk.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# Tool: rtk (token compression)
|
|
2
|
-
|
|
3
|
-
Per-agent wiring (generated by Baustein B):
|
|
4
|
-
|
|
5
|
-
- **Claude Code:** PreToolUse hook auto-rewrites commands (`git status` → `rtk git status`). On Windows this needs WSL; otherwise fall back to instruction mode (this file injected into CLAUDE.md telling the agent to prefix commands with `rtk`).
|
|
6
|
-
- **Codex CLI:** inject `RTK.md` / AGENTS.md instruction.
|
|
7
|
-
- **Gemini CLI:** no hook system — register rtk as an MCP tool or inject the GEMINI.md instruction.
|
|
1
|
+
# Tool: rtk (token compression)
|
|
2
|
+
|
|
3
|
+
Per-agent wiring (generated by Baustein B):
|
|
4
|
+
|
|
5
|
+
- **Claude Code:** PreToolUse hook auto-rewrites commands (`git status` → `rtk git status`). On Windows this needs WSL; otherwise fall back to instruction mode (this file injected into CLAUDE.md telling the agent to prefix commands with `rtk`).
|
|
6
|
+
- **Codex CLI:** inject `RTK.md` / AGENTS.md instruction.
|
|
7
|
+
- **Gemini CLI:** no hook system — register rtk as an MCP tool or inject the GEMINI.md instruction.
|
package/canon/tools/serena.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Tool: Serena (code-graph, LSP-accurate)
|
|
2
|
-
|
|
3
|
-
MIT, MCP-first. The alternative to graphify, selected via `yoke retrofit --code-graph=serena`. Serena uses real language servers (LSP) for symbol-accurate, cross-file retrieval and refactoring (`find_symbol`, `find_referencing_symbols`, rename/move) — no static index that goes stale, so it will not miss a reference.
|
|
4
|
-
|
|
5
|
-
Wired as an MCP server for all three agents. Best for large, strongly-typed codebases (TypeScript, Python, Go) doing systematic refactoring, where missing a caller is costly.
|
|
6
|
-
|
|
1
|
+
# Tool: Serena (code-graph, LSP-accurate)
|
|
2
|
+
|
|
3
|
+
MIT, MCP-first. The alternative to graphify, selected via `yoke retrofit --code-graph=serena`. Serena uses real language servers (LSP) for symbol-accurate, cross-file retrieval and refactoring (`find_symbol`, `find_referencing_symbols`, rename/move) — no static index that goes stale, so it will not miss a reference.
|
|
4
|
+
|
|
5
|
+
Wired as an MCP server for all three agents. Best for large, strongly-typed codebases (TypeScript, Python, Go) doing systematic refactoring, where missing a caller is costly.
|
|
6
|
+
|
|
7
7
|
Caveat: needs one language server per language (can be fiddly on Windows for exotic languages) and requires `uv`. The launch command is a best-effort template — adjust to your install, e.g. `uvx --from git+https://github.com/oraios/serena serena-mcp-server`.
|
|
8
8
|
|
|
9
9
|
Yoke disables Serena's automatic web-dashboard launch in generated MCP configurations. The
|
package/dist/agents/host.js
CHANGED
package/dist/agents/providers.js
CHANGED
|
@@ -17,12 +17,12 @@ const argsFor = (agent, permissions) => {
|
|
|
17
17
|
// Automatic review already selects workspace-write and conflicts with --sandbox.
|
|
18
18
|
return ['exec', '--approve-for-me', '--json'];
|
|
19
19
|
}
|
|
20
|
-
// Qwen
|
|
20
|
+
// Qwen Code uses its own approval modes and native tool exclusions.
|
|
21
21
|
if (agent === 'qwen') {
|
|
22
22
|
if (permissions === 'unsafe')
|
|
23
23
|
return ['--yolo', '--output-format', 'stream-json'];
|
|
24
|
-
const approval = permissions === 'read-only' ? 'plan' : '
|
|
25
|
-
return ['--approval-mode', approval, '--sandbox', '--output-format', 'stream-json'];
|
|
24
|
+
const approval = permissions === 'read-only' ? 'plan' : 'auto-edit';
|
|
25
|
+
return ['--approval-mode', approval, '--sandbox', '--output-format', 'stream-json', ...(permissions === 'safe' ? ['--allowed-tools', 'run_shell_command'] : [])];
|
|
26
26
|
}
|
|
27
27
|
if (permissions === 'unsafe')
|
|
28
28
|
return ['--yolo', '--output-format', 'stream-json'];
|
|
@@ -59,8 +59,18 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
|
|
|
59
59
|
else
|
|
60
60
|
throw new Error(`${agent} structured output schema requires ${agent === 'codex' ? 'schemaFile' : agent === 'claude' ? 'jsonSchema' : 'a supported native schema option (unavailable)'}`);
|
|
61
61
|
}
|
|
62
|
-
if (parsedSelection.model)
|
|
63
|
-
|
|
62
|
+
if (parsedSelection.model) {
|
|
63
|
+
const qualified = agent === 'qwen' && parsedSelection.model.includes('::')
|
|
64
|
+
? parsedSelection.model.match(/^(openai|anthropic|gemini|vertex-ai|qwen-oauth)::(.+)$/u) : undefined;
|
|
65
|
+
if (agent === 'qwen' && parsedSelection.model.includes('::') && !qualified)
|
|
66
|
+
throw Error('Invalid Qwen auth/model selector');
|
|
67
|
+
if (qualified) {
|
|
68
|
+
const model = ModelSelectionSchema.shape.model.parse(qualified[2]);
|
|
69
|
+
args.push('--auth-type', qualified[1], '--model', model);
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
args.push('--model', parsedSelection.model);
|
|
73
|
+
}
|
|
64
74
|
if (parsedSelection.reasoningEffort) {
|
|
65
75
|
if (agent === 'claude')
|
|
66
76
|
args.push('--effort', parsedSelection.reasoningEffort);
|
|
@@ -71,6 +81,9 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
|
|
|
71
81
|
args.push('--disable', 'multi_agent');
|
|
72
82
|
if (agent === 'claude' && parsedSelection.nativeMultiAgent === false)
|
|
73
83
|
args.push('--disallowedTools', 'Agent', 'Task', 'TeamCreate', 'SendMessage');
|
|
84
|
+
if (agent === 'qwen' && parsedSelection.nativeMultiAgent === false) {
|
|
85
|
+
args.push('--exclude-tools', 'agent', 'task', 'create_sub_session', 'team_create', 'send_message');
|
|
86
|
+
}
|
|
74
87
|
if (parsedSelection.bare) {
|
|
75
88
|
if (agent === 'codex')
|
|
76
89
|
args.push('--ignore-user-config');
|
package/dist/agents/telemetry.js
CHANGED
|
@@ -22,6 +22,8 @@ export function parseProviderResult(agent, output) {
|
|
|
22
22
|
if (direct !== undefined)
|
|
23
23
|
return direct;
|
|
24
24
|
}
|
|
25
|
+
if (agent === 'qwen')
|
|
26
|
+
return parseQwenResult(output);
|
|
25
27
|
const fragments = [];
|
|
26
28
|
for (const line of output.split(/\r?\n/u)) {
|
|
27
29
|
const parsed = parseJson(line);
|
|
@@ -45,10 +47,6 @@ export function parseProviderResult(agent, output) {
|
|
|
45
47
|
if (event.type === 'message' && event.role === 'assistant' && typeof event.content === 'string')
|
|
46
48
|
fragments.push(event.content);
|
|
47
49
|
break;
|
|
48
|
-
case 'qwen':
|
|
49
|
-
if (event.type === 'message' && event.role === 'assistant' && typeof event.content === 'string')
|
|
50
|
-
fragments.push(event.content);
|
|
51
|
-
break;
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
52
|
const joined = parseJson(fragments.join(''));
|
|
@@ -67,6 +65,34 @@ export function parseProviderResult(agent, output) {
|
|
|
67
65
|
}
|
|
68
66
|
return null;
|
|
69
67
|
}
|
|
68
|
+
/** Qwen uses assistant content blocks and result envelopes, not Gemini messages. */
|
|
69
|
+
function parseQwenResult(output) {
|
|
70
|
+
let candidate = null;
|
|
71
|
+
for (const line of output.split(/\r?\n/u)) {
|
|
72
|
+
const parsed = parseJson(line);
|
|
73
|
+
if (!parsed.ok || !isRecord(parsed.value))
|
|
74
|
+
continue;
|
|
75
|
+
const event = parsed.value;
|
|
76
|
+
if (event.parent_tool_use_id != null)
|
|
77
|
+
continue;
|
|
78
|
+
if (event.type === 'result') {
|
|
79
|
+
if (event.is_error === true) {
|
|
80
|
+
candidate = null;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const structured = directMachineResult(event.structured_result);
|
|
84
|
+
const result = typeof event.result === 'string' ? parseJson(event.result) : { ok: false };
|
|
85
|
+
candidate = structured ?? (result.ok ? directMachineResult(result.value) : undefined) ?? null;
|
|
86
|
+
}
|
|
87
|
+
else if (event.type === 'assistant' && isRecord(event.message) && Array.isArray(event.message.content)) {
|
|
88
|
+
const text = event.message.content.filter(isRecord).filter(part => part.type === 'text' && typeof part.text === 'string').map(part => part.text).join('');
|
|
89
|
+
const result = parseJson(text);
|
|
90
|
+
if (result.ok)
|
|
91
|
+
candidate = directMachineResult(result.value) ?? candidate;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return candidate;
|
|
95
|
+
}
|
|
70
96
|
export function parseProviderTelemetry(agent, lines) {
|
|
71
97
|
let inputTokens;
|
|
72
98
|
let cachedInputTokens;
|
|
@@ -87,6 +113,8 @@ export function parseProviderTelemetry(agent, lines) {
|
|
|
87
113
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
88
114
|
continue;
|
|
89
115
|
const event = parsed;
|
|
116
|
+
if (agent === 'qwen' && event.parent_tool_use_id != null)
|
|
117
|
+
continue;
|
|
90
118
|
const message = event.message && typeof event.message === 'object' ? event.message : undefined;
|
|
91
119
|
const stats = event.stats && typeof event.stats === 'object' ? event.stats : undefined;
|
|
92
120
|
const usage = (event.usage && typeof event.usage === 'object'
|
|
@@ -105,41 +133,12 @@ export function parseProviderTelemetry(agent, lines) {
|
|
|
105
133
|
// Older JSON stats only provide model-local token objects. Sum a field
|
|
106
134
|
// only when every model measured it; a missing measurement is not zero.
|
|
107
135
|
let source = usage ?? nestedModelTokens ?? modelUsage;
|
|
108
|
-
if (agent === 'gemini' && modelEntries.length > 0) {
|
|
109
|
-
reportedModels = modelEntries.map(([name]) => name);
|
|
110
|
-
model = firstModel?.[0];
|
|
111
|
-
const fields = {
|
|
112
|
-
input_tokens: ['input_tokens', 'inputTokens', 'promptTokenCount', 'input'],
|
|
113
|
-
output_tokens: ['output_tokens', 'outputTokens', 'candidatesTokenCount', 'output'],
|
|
114
|
-
cached_input_tokens: ['cached_input_tokens', 'cachedInputTokens', 'cachedContentTokenCount', 'cached'],
|
|
115
|
-
reasoning_output_tokens: ['reasoning_output_tokens', 'reasoningOutputTokens', 'thoughtsTokenCount', 'thoughts'],
|
|
116
|
-
};
|
|
117
|
-
const totals = {};
|
|
118
|
-
for (const [field, aliases] of Object.entries(fields)) {
|
|
119
|
-
const aggregate = aliases.map(key => finite(usage?.[key])).find(value => value !== undefined);
|
|
120
|
-
if (aggregate !== undefined) {
|
|
121
|
-
totals[field] = aggregate;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
const values = modelEntries.map(([, value]) => {
|
|
125
|
-
const entry = isRecord(value) ? value : {};
|
|
126
|
-
const tokens = isRecord(entry.tokens) ? entry.tokens : entry;
|
|
127
|
-
return aliases.map(key => finite(tokens[key])).find(value => value !== undefined);
|
|
128
|
-
});
|
|
129
|
-
if (values.every(value => value !== undefined))
|
|
130
|
-
totals[field] = values.reduce((sum, value) => sum + value, 0);
|
|
131
|
-
}
|
|
132
|
-
source = { ...totals, ...usage };
|
|
133
|
-
const aggregateCached = finite(usage?.cached_input_tokens ?? usage?.cached);
|
|
134
|
-
if (aggregateCached !== undefined)
|
|
135
|
-
source.cached_input_tokens = aggregateCached;
|
|
136
|
-
}
|
|
137
|
-
if (agent === 'qwen' && modelEntries.length > 0) {
|
|
136
|
+
if ((agent === 'gemini' || agent === 'qwen') && modelEntries.length > 0) {
|
|
138
137
|
reportedModels = modelEntries.map(([name]) => name);
|
|
139
138
|
model = firstModel?.[0];
|
|
140
139
|
const fields = {
|
|
141
|
-
input_tokens: ['input_tokens', 'inputTokens', 'promptTokenCount', 'input'],
|
|
142
|
-
output_tokens: ['output_tokens', 'outputTokens', 'candidatesTokenCount', 'output'],
|
|
140
|
+
input_tokens: ['input_tokens', 'inputTokens', 'promptTokenCount', 'input', 'prompt'],
|
|
141
|
+
output_tokens: ['output_tokens', 'outputTokens', 'candidatesTokenCount', 'output', 'candidates'],
|
|
143
142
|
cached_input_tokens: ['cached_input_tokens', 'cachedInputTokens', 'cachedContentTokenCount', 'cached'],
|
|
144
143
|
reasoning_output_tokens: ['reasoning_output_tokens', 'reasoningOutputTokens', 'thoughtsTokenCount', 'thoughts'],
|
|
145
144
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { MODEL_PROVIDERS } from './setup/model-presets.js';
|
|
2
3
|
import { pathToFileURL } from 'node:url';
|
|
3
4
|
import { realpathSync } from 'node:fs';
|
|
4
5
|
import { checkProject, checkExitCode, protectAcceptance } from './check/command.js';
|
|
@@ -114,7 +115,7 @@ export function main(argv) {
|
|
|
114
115
|
const agentTokens = agentArg === 'all' ? valid : agentArg?.split(',').map(a => a.trim());
|
|
115
116
|
const invalidAgents = agentTokens?.filter(a => !valid.includes(a)) ?? [];
|
|
116
117
|
if (agentArg && (invalidAgents.length > 0 || agentTokens?.length === 0)) {
|
|
117
|
-
console.error(`Invalid --agent value: ${agentArg} (expected claude,codex,gemini|all)`);
|
|
118
|
+
console.error(`Invalid --agent value: ${agentArg} (expected claude,codex,gemini,qwen|all)`);
|
|
118
119
|
return 1;
|
|
119
120
|
}
|
|
120
121
|
const agents = agentTokens ? [...new Set(agentTokens)] : undefined;
|
|
@@ -140,7 +141,14 @@ export function main(argv) {
|
|
|
140
141
|
console.error('Invalid routing strategy');
|
|
141
142
|
return 1;
|
|
142
143
|
}
|
|
144
|
+
const modelProviderArg = rest.find(a => a.startsWith('--model-provider='))?.slice('--model-provider='.length);
|
|
145
|
+
const modelProviders = modelProviderArg?.split(',').map(value => value.trim());
|
|
146
|
+
if (modelProviders?.some(value => !MODEL_PROVIDERS.includes(value))) {
|
|
147
|
+
console.error('Invalid --model-provider (expected deepseek,kimi)');
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
143
150
|
return runSetup(targetDir, {
|
|
151
|
+
modelProviders: modelProviders,
|
|
144
152
|
host: hostArg, agents, runner: runnerArg,
|
|
145
153
|
codeGraph: graphArg,
|
|
146
154
|
loop, routing, decisionPolicy: policyArg,
|
|
@@ -479,7 +487,7 @@ export function main(argv) {
|
|
|
479
487
|
let reviewer;
|
|
480
488
|
if (reviewerArg) {
|
|
481
489
|
if (!valid.includes(reviewerArg)) {
|
|
482
|
-
console.error(`Invalid --reviewer value: ${reviewerArg} (expected claude|codex|gemini)`);
|
|
490
|
+
console.error(`Invalid --reviewer value: ${reviewerArg} (expected claude|codex|gemini|qwen)`);
|
|
483
491
|
return 1;
|
|
484
492
|
}
|
|
485
493
|
reviewer = reviewerArg;
|
|
@@ -522,13 +530,13 @@ export function main(argv) {
|
|
|
522
530
|
}
|
|
523
531
|
return runLoopCommand(targetDir, { maxIterations: rawMax, agent, isolate, resumeWorktree: rest.includes('--resume-worktree'), parallel, parallelAuto: parallelArg === '--parallel=auto', reviewer, review, allowSelfReview, timeoutMinutes, json, routing, onAmbiguity: oaArg, decisionPolicy: dpArg, permissions, ...qualityFlags.options });
|
|
524
532
|
}
|
|
525
|
-
console.log('usage: yoke loop <on|off|status|decision|answer|resume [--discard] [--quality|--no-quality] [--quality-rounds=N] [--quality-minutes=N] [--quality-policy=<blocking|advisory>] [--quality-unbounded] [--candidates=N]|cleanup [--remove-worktrees] [--discard-stale-recovery]|run [--max=N] [--parallel=<auto|N>] [--runner=<claude|codex|gemini>] [--reviewer=<claude|codex|gemini>] [--review] [--allow-self-review] [--routing|--no-routing] [--isolate|--no-isolate] [--unsafe] [--timeout=<minutes>] [--decision-policy=<auto|critical>] [--quality|--no-quality] [--quality-rounds=N] [--quality-minutes=N] [--quality-policy=<blocking|advisory>] [--quality-unbounded] [--candidates=N] [--json]> [targetDir]');
|
|
533
|
+
console.log('usage: yoke loop <on|off|status|decision|answer|resume [--discard] [--quality|--no-quality] [--quality-rounds=N] [--quality-minutes=N] [--quality-policy=<blocking|advisory>] [--quality-unbounded] [--candidates=N]|cleanup [--remove-worktrees] [--discard-stale-recovery]|run [--max=N] [--parallel=<auto|N>] [--runner=<claude|codex|gemini|qwen>] [--reviewer=<claude|codex|gemini|qwen>] [--review] [--allow-self-review] [--routing|--no-routing] [--isolate|--no-isolate] [--unsafe] [--timeout=<minutes>] [--decision-policy=<auto|critical>] [--quality|--no-quality] [--quality-rounds=N] [--quality-minutes=N] [--quality-policy=<blocking|advisory>] [--quality-unbounded] [--candidates=N] [--json]> [targetDir]');
|
|
526
534
|
return 1;
|
|
527
535
|
}
|
|
528
536
|
case 'new': {
|
|
529
537
|
const dir = rest.find(a => !a.startsWith('-'));
|
|
530
538
|
if (!dir) {
|
|
531
|
-
console.error('usage: yoke new <dir> [--idea="..."] [--agent=claude,codex,gemini|all] [--runner=<claude|codex|gemini>] [--loop]');
|
|
539
|
+
console.error('usage: yoke new <dir> [--idea="..."] [--agent=claude,codex,gemini,qwen|all] [--runner=<claude|codex|gemini|qwen>] [--loop]');
|
|
532
540
|
return 1;
|
|
533
541
|
}
|
|
534
542
|
const idea = rest.find(a => a.startsWith('--idea='))?.slice('--idea='.length);
|
|
@@ -543,7 +551,7 @@ export function main(argv) {
|
|
|
543
551
|
}
|
|
544
552
|
const runnerArg = rest.find(a => a.startsWith('--runner='))?.slice('--runner='.length);
|
|
545
553
|
if (runnerArg && !all.includes(runnerArg)) {
|
|
546
|
-
console.error(`Invalid --runner value: ${runnerArg} (expected claude|codex|gemini)`);
|
|
554
|
+
console.error(`Invalid --runner value: ${runnerArg} (expected claude|codex|gemini|qwen)`);
|
|
547
555
|
return 1;
|
|
548
556
|
}
|
|
549
557
|
return runNew(dir, { idea, agents, runner: runnerArg, loop });
|
|
@@ -562,7 +570,7 @@ export function main(argv) {
|
|
|
562
570
|
if (sub === 'draft') {
|
|
563
571
|
const idea = rest.find(a => a.startsWith('--idea='))?.slice('--idea='.length);
|
|
564
572
|
if (!idea) {
|
|
565
|
-
console.error('usage: yoke prd draft [dir] --idea="..." [--runner=<claude|codex|gemini>] [--force] [--timeout=<minutes>]');
|
|
573
|
+
console.error('usage: yoke prd draft [dir] --idea="..." [--runner=<claude|codex|gemini|qwen>] [--force] [--timeout=<minutes>]');
|
|
566
574
|
return 1;
|
|
567
575
|
}
|
|
568
576
|
const valid = ['claude', 'codex', 'gemini', 'qwen'];
|
|
@@ -586,7 +594,7 @@ export function main(argv) {
|
|
|
586
594
|
}
|
|
587
595
|
if (sub === 'check')
|
|
588
596
|
return runPrdCheck(targetDir);
|
|
589
|
-
console.log('usage: yoke prd <draft|check|assess> [dir] [--idea="..."] [--runner=<claude|codex|gemini>] [--story=<id>] [--reassess] [--force] [--timeout=<minutes>]');
|
|
597
|
+
console.log('usage: yoke prd <draft|check|assess> [dir] [--idea="..."] [--runner=<claude|codex|gemini|qwen>] [--story=<id>] [--reassess] [--force] [--timeout=<minutes>]');
|
|
590
598
|
return 1;
|
|
591
599
|
}
|
|
592
600
|
case 'context': {
|
|
@@ -601,10 +609,10 @@ export function main(argv) {
|
|
|
601
609
|
}
|
|
602
610
|
case 'review': {
|
|
603
611
|
const targetDir = rest.find(a => !a.startsWith('-')) ?? '.';
|
|
604
|
-
const valid = ['claude', 'codex', 'gemini'];
|
|
612
|
+
const valid = ['claude', 'codex', 'gemini', 'qwen'];
|
|
605
613
|
const reviewerArg = rest.find(a => a.startsWith('--reviewer='))?.slice('--reviewer='.length);
|
|
606
614
|
if (reviewerArg && !valid.includes(reviewerArg)) {
|
|
607
|
-
console.error(`Invalid --reviewer value: ${reviewerArg} (expected claude|codex|gemini)`);
|
|
615
|
+
console.error(`Invalid --reviewer value: ${reviewerArg} (expected claude|codex|gemini|qwen)`);
|
|
608
616
|
return 1;
|
|
609
617
|
}
|
|
610
618
|
const base = rest.find(a => a.startsWith('--base='))?.slice('--base='.length);
|
|
@@ -651,7 +659,7 @@ export function main(argv) {
|
|
|
651
659
|
return runUpgrade();
|
|
652
660
|
default:
|
|
653
661
|
console.log('Project workflows: yoke check [dir] [--json|--protect] | goal set|run|resume|pause|status|handoff|budget [dir] | projects add|list|remove | dashboard [dir] [--port=N]');
|
|
654
|
-
console.log('usage: yoke <setup [dir] | new <dir> [--idea="..."] | validate [canonDir] | retrofit [targetDir] [--agent=claude,codex,gemini|all] [--code-graph=graphify|serena] [--loop] | change <add|status> [dir] | prd <draft|check|assess> [dir] | loop <on|off|status|decision|answer|resume|run|cleanup> | context <init|status> | review [dir] [--reviewer=<claude|codex|gemini>] [--base=<ref>] [--focus="..."] | design-scan [dir] [--max=N] [--report] | flow-smoke [dir] [--url=<baseUrl>] [--label=<name>] | upgrade>');
|
|
662
|
+
console.log('usage: yoke <setup [dir] | new <dir> [--idea="..."] | validate [canonDir] | retrofit [targetDir] [--agent=claude,codex,gemini,qwen|all] [--code-graph=graphify|serena] [--loop] | change <add|status> [dir] | prd <draft|check|assess> [dir] | loop <on|off|status|decision|answer|resume|run|cleanup> | context <init|status> | review [dir] [--reviewer=<claude|codex|gemini|qwen>] [--base=<ref>] [--focus="..."] | design-scan [dir] [--max=N] [--report] | flow-smoke [dir] [--url=<baseUrl>] [--label=<name>] | upgrade>');
|
|
655
663
|
return cmd ? 1 : 0;
|
|
656
664
|
}
|
|
657
665
|
}
|