@hecer/yoke 1.11.0 → 1.13.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.
Files changed (155) hide show
  1. package/.claude-plugin/plugin.json +13 -13
  2. package/.codex-plugin/plugin.json +7 -7
  3. package/CHANGELOG.md +435 -398
  4. package/README.md +943 -915
  5. package/TODOS.md +5 -5
  6. package/agents/docs.toml +6 -6
  7. package/agents/implementer.toml +6 -6
  8. package/agents/reviewer.toml +6 -6
  9. package/agents/security.toml +6 -6
  10. package/bench/README.md +86 -86
  11. package/bench/RESULTS.md +35 -35
  12. package/bench/output-compaction.mjs +65 -65
  13. package/bench/result-schema.mjs +12 -12
  14. package/bench/results/claude-2026-07-27T18-03-26.json +50 -50
  15. package/bench/results/codex-unavailable-1785175418318.json +15 -15
  16. package/bench/results/gemini-2026-07-27T18-03-44.json +46 -46
  17. package/bench/run-matrix.mjs +26 -26
  18. package/bench/run.mjs +106 -106
  19. package/canon/AGENTS.md +30 -30
  20. package/canon/context/DECISIONS.md +4 -4
  21. package/canon/context/GLOSSARY.md +11 -11
  22. package/canon/context/KNOWLEDGE.md +4 -4
  23. package/canon/context/PROJECT.md +15 -15
  24. package/canon/loop/loop-spec.md +65 -65
  25. package/canon/loop/prd.schema.md +41 -41
  26. package/canon/manifest.yaml +59 -59
  27. package/canon/policy/gates.md +7 -7
  28. package/canon/policy/roles.md +9 -9
  29. package/canon/skills/ATTRIBUTION.md +99 -99
  30. package/canon/skills/authoring-prd/SKILL.md +57 -57
  31. package/canon/skills/brainstorming/SKILL.md +164 -164
  32. package/canon/skills/codebase-design/DEEPENING.md +15 -15
  33. package/canon/skills/codebase-design/DESIGN-IT-TWICE.md +12 -12
  34. package/canon/skills/codebase-design/SKILL.md +39 -39
  35. package/canon/skills/dispatching-parallel-agents/SKILL.md +182 -182
  36. package/canon/skills/document-release/SKILL.md +302 -302
  37. package/canon/skills/domain-modeling/ADR-FORMAT.md +19 -19
  38. package/canon/skills/domain-modeling/CONTEXT-FORMAT.md +39 -39
  39. package/canon/skills/domain-modeling/SKILL.md +35 -35
  40. package/canon/skills/executing-plans/SKILL.md +70 -70
  41. package/canon/skills/finishing-a-development-branch/SKILL.md +200 -200
  42. package/canon/skills/health/SKILL.md +177 -177
  43. package/canon/skills/maintaining-context/SKILL.md +34 -34
  44. package/canon/skills/minimal-code/SKILL.md +21 -21
  45. package/canon/skills/no-ai-slop/SKILL.md +103 -103
  46. package/canon/skills/no-ai-slop/eval.md +43 -43
  47. package/canon/skills/plan-ceo-review/SKILL.md +541 -541
  48. package/canon/skills/plan-eng-review/SKILL.md +362 -362
  49. package/canon/skills/receiving-code-review/SKILL.md +213 -213
  50. package/canon/skills/requesting-code-review/SKILL.md +105 -105
  51. package/canon/skills/resolving-merge-conflicts/SKILL.md +18 -18
  52. package/canon/skills/retro/SKILL.md +397 -397
  53. package/canon/skills/review/SKILL.md +246 -246
  54. package/canon/skills/ship/SKILL.md +691 -691
  55. package/canon/skills/subagent-driven-development/SKILL.md +277 -277
  56. package/canon/skills/systematic-debugging/SKILL.md +296 -296
  57. package/canon/skills/tdd/SKILL.md +371 -371
  58. package/canon/skills/unslop-ui/SKILL.md +34 -34
  59. package/canon/skills/using-git-worktrees/SKILL.md +218 -218
  60. package/canon/skills/verification-before-completion/SKILL.md +139 -139
  61. package/canon/skills/visual-verification/SKILL.md +54 -54
  62. package/canon/skills/workflow/SKILL.md +22 -22
  63. package/canon/skills/writing-for-agents/SKILL-MECHANICS.md +27 -27
  64. package/canon/skills/writing-for-agents/SKILL.md +42 -42
  65. package/canon/skills/writing-plans/SKILL.md +152 -152
  66. package/canon/skills/writing-skills/SKILL.md +655 -655
  67. package/canon/skills/yoke-retrofit/SKILL.md +26 -26
  68. package/canon/skills/yoke-workflow/SKILL.md +20 -20
  69. package/canon/tools/codex-rtk-hook.mjs +35 -35
  70. package/canon/tools/gemini-rtk-hook.mjs +25 -25
  71. package/canon/tools/graphify.md +3 -3
  72. package/canon/tools/playwright-mcp.md +3 -3
  73. package/canon/tools/qwen-rtk-hook.mjs +25 -0
  74. package/canon/tools/rtk.md +7 -7
  75. package/canon/tools/serena.md +6 -6
  76. package/dist/agents/catalog.js +7 -0
  77. package/dist/agents/contracts.js +3 -1
  78. package/dist/agents/host.js +5 -1
  79. package/dist/agents/process-streams.js +62 -0
  80. package/dist/agents/process.js +43 -3
  81. package/dist/agents/providers.js +61 -6
  82. package/dist/agents/telemetry.js +133 -37
  83. package/dist/canon/manifest.js +2 -1
  84. package/dist/change/inbox.js +1 -1
  85. package/dist/cli.js +30 -24
  86. package/dist/dashboard/page.js +122 -122
  87. package/dist/dashboard/panels.js +91 -91
  88. package/dist/goals/command.js +3 -2
  89. package/dist/loop/claims.js +2 -1
  90. package/dist/loop/decision.js +3 -2
  91. package/dist/loop/parallel-command.js +4 -2
  92. package/dist/loop/prd.js +2 -1
  93. package/dist/loop/reporter.js +1 -0
  94. package/dist/loop/run-command.js +31 -10
  95. package/dist/prd/command.js +19 -19
  96. package/dist/quality/candidate-comparison.js +6 -1
  97. package/dist/quality/command.js +17 -2
  98. package/dist/quality/types.js +6 -1
  99. package/dist/retrofit/apply.js +95 -2
  100. package/dist/retrofit/config.js +9 -1
  101. package/dist/retrofit/detect.js +8 -0
  102. package/dist/retrofit/plan.js +6 -0
  103. package/dist/retrofit/planners/claude.js +14 -14
  104. package/dist/retrofit/planners/kilo.js +44 -0
  105. package/dist/retrofit/planners/opencode.js +44 -0
  106. package/dist/retrofit/planners/pi.js +24 -0
  107. package/dist/retrofit/planners/qwen.js +3 -3
  108. package/dist/retrofit/preserve.js +2 -2
  109. package/dist/retrofit/qwen-settings.js +17 -0
  110. package/dist/retrofit/skill-actions.js +4 -1
  111. package/dist/retrofit/tools.js +8 -0
  112. package/dist/review/command.js +3 -2
  113. package/dist/review/verdict.js +1 -1
  114. package/dist/routing/capability.js +2 -2
  115. package/dist/routing/planning.js +2 -0
  116. package/dist/routing/registry.js +3 -1
  117. package/dist/routing/router.js +7 -3
  118. package/dist/setup/command.js +35 -11
  119. package/dist/setup/model-presets.js +48 -0
  120. package/docs/CAPABILITY-ROUTING.md +51 -51
  121. package/docs/DASHBOARD-EVOLUTION.md +33 -33
  122. package/docs/HARNESSES.md +81 -0
  123. package/docs/MIGRATING-TO-1.0.md +33 -33
  124. package/docs/MIGRATING-TO-1.1.md +27 -27
  125. package/docs/MIGRATING-TO-1.4.md +70 -70
  126. package/docs/PRODUCT-DIRECTION-2026-09-05.md +210 -210
  127. package/docs/PUBLISHING.md +114 -114
  128. package/docs/QWEN-MODEL-SUPPORT.md +142 -0
  129. package/docs/VERIFIED-PROJECTS-VALIDATION.md +29 -29
  130. package/docs/VERIFIED-PROJECTS.md +167 -167
  131. package/docs/superpowers/plans/2026-06-28-baustein-e-context-layer.md +981 -981
  132. package/docs/superpowers/plans/2026-06-29-baustein-f-routing.md +258 -258
  133. package/docs/superpowers/plans/2026-06-29-baustein-g-loop-observability.md +1006 -1006
  134. package/docs/superpowers/plans/2026-06-29-baustein-h-loop-robustness.md +374 -374
  135. package/docs/superpowers/plans/2026-06-30-baustein-i-visual-design-verification.md +450 -450
  136. package/docs/superpowers/plans/2026-07-02-baustein-k-zero-to-100-bootstrap.md +1024 -1024
  137. package/docs/superpowers/plans/2026-07-02-baustein-m-flow-smoke-proofs.md +574 -574
  138. package/docs/superpowers/plans/2026-08-13-gauntlet-quality-loop.md +537 -537
  139. package/docs/superpowers/plans/2026-08-16-artifact-backed-output-compaction.md +329 -329
  140. package/docs/superpowers/plans/2026-09-05-verified-projects.md +83 -83
  141. package/docs/superpowers/specs/2026-06-28-baustein-e-context-layer-design.md +146 -146
  142. package/docs/superpowers/specs/2026-06-29-baustein-f-routing-design.md +106 -106
  143. package/docs/superpowers/specs/2026-06-29-baustein-g-loop-observability-design.md +186 -186
  144. package/docs/superpowers/specs/2026-06-29-baustein-h-loop-robustness-design.md +113 -113
  145. package/docs/superpowers/specs/2026-06-30-baustein-i-visual-design-verification-design.md +98 -98
  146. package/docs/superpowers/specs/2026-07-02-baustein-k-zero-to-100-bootstrap-design.md +200 -200
  147. package/docs/superpowers/specs/2026-07-02-baustein-m-flow-smoke-proofs-design.md +155 -155
  148. package/docs/superpowers/specs/2026-08-13-gauntlet-quality-loop-design.md +422 -422
  149. package/docs/superpowers/specs/2026-08-16-artifact-backed-output-compaction-design.md +166 -166
  150. package/gemini-extension.json +6 -6
  151. package/hooks/hooks.json +19 -19
  152. package/package.json +91 -87
  153. package/dist/dashboard/discovery.js +0 -73
  154. package/docs/community-outreach-2026-08-20.md +0 -85
  155. 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 the supported Yoke harnesses.
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`, `gemini`, `qwen`, `opencode`, `kilo`, or `pi`).
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 every supported Yoke harness.
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
+ }
@@ -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 Claude, Codex, Gemini, Qwen, OpenCode and Kilo (stdio). Pi has no native MCP layer. 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 the harnesses that support native MCP configuration (Claude, Codex, Gemini, Qwen, OpenCode and Kilo). Pi has no native MCP layer, so Pi projects use the portable skills and explicit Yoke gates instead. 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
+ }
@@ -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.
@@ -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 Claude, Codex, Gemini, Qwen, OpenCode and Kilo. Pi has no native MCP layer. 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
@@ -0,0 +1,7 @@
1
+ import { AgentSchema } from './contracts.js';
2
+ /** Single source of truth for CLI help, setup prompts, and default resolution. */
3
+ export const SUPPORTED_AGENTS = AgentSchema.options;
4
+ export const AGENT_LIST = SUPPORTED_AGENTS.join(',');
5
+ export function isSupportedAgent(value) {
6
+ return SUPPORTED_AGENTS.includes(value);
7
+ }
@@ -1,9 +1,11 @@
1
1
  import { z } from 'zod';
2
- export const AgentSchema = z.enum(['claude', 'codex', 'gemini', 'qwen']);
2
+ export const AgentSchema = z.enum(['claude', 'codex', 'gemini', 'qwen', 'opencode', 'kilo', 'pi']);
3
3
  export const PermissionProfileSchema = z.enum(['safe', 'unsafe', 'read-only']);
4
4
  export const ModelSelectionSchema = z.object({
5
+ provider: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/).optional(),
5
6
  model: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/).optional(),
6
7
  reasoningEffort: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/).optional(),
8
+ variant: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/).optional(),
7
9
  nativeMultiAgent: z.boolean().optional(),
8
10
  bare: z.boolean().optional(),
9
11
  });
@@ -7,8 +7,12 @@ export function detectHostAgent(env = process.env) {
7
7
  return 'claude';
8
8
  if (env.GEMINI_CLI)
9
9
  return 'gemini';
10
- if (env.QWEN_CLI)
10
+ if (env.QWEN_CODE || env.QWEN_CODE_SESSION_ID || env.QWEN_CLI)
11
11
  return 'qwen';
12
+ if (env.OPENCODE_CLIENT || env.OPENCODE_CONFIG || env.OPENCODE_CONFIG_DIR)
13
+ return 'opencode';
14
+ if (env.KILO_CLIENT || env.KILO_CONFIG || env.KILO_CONFIG_DIR)
15
+ return 'kilo';
12
16
  if (env.CODEX_HOME)
13
17
  return 'codex';
14
18
  if (env.CLAUDE_CONFIG_DIR)
@@ -20,6 +20,9 @@ export function createTelemetryAccumulator(agent) {
20
20
  let trailing = '';
21
21
  let telemetry = { usageAvailable: false };
22
22
  let reportedModels = [];
23
+ const stepTotals = agent === 'opencode' || agent === 'kilo'
24
+ ? { input: 0, output: 0, cached: 0, cacheWrite: 0, reasoning: 0, cost: 0, hasInput: false, hasOutput: false, hasCached: false, hasCacheWrite: false, hasReasoning: false, hasCost: false }
25
+ : undefined;
23
26
  const update = (lines) => {
24
27
  for (const line of lines) {
25
28
  const next = parseProviderTelemetry(agent, [line]);
@@ -31,6 +34,35 @@ export function createTelemetryAccumulator(agent) {
31
34
  // never add it to earlier results or to assistant-message snapshots.
32
35
  if (next.tokens || next.partialUsage)
33
36
  telemetry = next;
37
+ if (stepTotals && isStepFinish(line)) {
38
+ const usage = next.tokens ?? next.partialUsage;
39
+ if (usage) {
40
+ if (usage.inputTokens !== undefined) {
41
+ stepTotals.input += usage.inputTokens;
42
+ stepTotals.hasInput = true;
43
+ }
44
+ if (usage.outputTokens !== undefined) {
45
+ stepTotals.output += usage.outputTokens;
46
+ stepTotals.hasOutput = true;
47
+ }
48
+ if (usage.cachedInputTokens !== undefined) {
49
+ stepTotals.cached += usage.cachedInputTokens;
50
+ stepTotals.hasCached = true;
51
+ }
52
+ if (usage.cacheWriteInputTokens !== undefined) {
53
+ stepTotals.cacheWrite += usage.cacheWriteInputTokens;
54
+ stepTotals.hasCacheWrite = true;
55
+ }
56
+ if (usage.reasoningOutputTokens !== undefined) {
57
+ stepTotals.reasoning += usage.reasoningOutputTokens;
58
+ stepTotals.hasReasoning = true;
59
+ }
60
+ if (usage.totalCostUsd !== undefined) {
61
+ stepTotals.cost += usage.totalCostUsd;
62
+ stepTotals.hasCost = true;
63
+ }
64
+ }
65
+ }
34
66
  }
35
67
  };
36
68
  return {
@@ -43,6 +75,26 @@ export function createTelemetryAccumulator(agent) {
43
75
  if (trailing)
44
76
  update([trailing]);
45
77
  trailing = '';
78
+ if (stepTotals && (stepTotals.hasInput || stepTotals.hasOutput)) {
79
+ const latest = telemetry.tokens;
80
+ const inputTokens = stepTotals.hasInput ? stepTotals.input : latest?.inputTokens;
81
+ const outputTokens = stepTotals.hasOutput ? stepTotals.output : latest?.outputTokens;
82
+ const partialUsage = {
83
+ ...(inputTokens !== undefined ? { inputTokens } : {}),
84
+ ...(outputTokens !== undefined ? { outputTokens } : {}),
85
+ ...(stepTotals.hasCached ? { cachedInputTokens: stepTotals.cached } : latest?.cachedInputTokens !== undefined ? { cachedInputTokens: latest.cachedInputTokens } : {}),
86
+ ...(stepTotals.hasCacheWrite ? { cacheWriteInputTokens: stepTotals.cacheWrite } : latest?.cacheWriteInputTokens !== undefined ? { cacheWriteInputTokens: latest.cacheWriteInputTokens } : {}),
87
+ ...(stepTotals.hasReasoning ? { reasoningOutputTokens: stepTotals.reasoning } : latest?.reasoningOutputTokens !== undefined ? { reasoningOutputTokens: latest.reasoningOutputTokens } : {}),
88
+ ...(stepTotals.hasCost ? { totalCostUsd: stepTotals.cost } : latest?.totalCostUsd !== undefined ? { totalCostUsd: latest.totalCostUsd } : {}),
89
+ ...(latest?.model ? { model: latest.model } : {}),
90
+ };
91
+ if (inputTokens !== undefined && outputTokens !== undefined) {
92
+ telemetry = { usageAvailable: true, tokens: { ...partialUsage, inputTokens, outputTokens } };
93
+ }
94
+ else {
95
+ telemetry = { usageAvailable: false, partialUsage };
96
+ }
97
+ }
46
98
  if (telemetry.tokens) {
47
99
  const { model: _model, ...tokens } = telemetry.tokens;
48
100
  return { usageAvailable: telemetry.usageAvailable, tokens: { ...tokens, ...(reportedModels.length === 1 ? { model: reportedModels[0] } : {}) },
@@ -52,3 +104,13 @@ export function createTelemetryAccumulator(agent) {
52
104
  },
53
105
  };
54
106
  }
107
+ function isStepFinish(line) {
108
+ try {
109
+ const value = JSON.parse(line);
110
+ const part = value.part && typeof value.part === 'object' ? value.part : undefined;
111
+ return value.type === 'step_finish' || part?.type === 'step-finish';
112
+ }
113
+ catch {
114
+ return false;
115
+ }
116
+ }
@@ -58,7 +58,32 @@ export function startProviderProcess(agent, invocation, options = {}) {
58
58
  catch {
59
59
  return true;
60
60
  }
61
- if (startedAt.startsWith('unverified:') || processIncarnation(processPid) !== startedAt)
61
+ // A slow or unavailable Windows identity query must not strand the exact
62
+ // child process Yoke just spawned. The live ChildProcess handle proves
63
+ // ownership more strongly than a late PID lookup. Terminate that exact
64
+ // handle immediately and ask taskkill asynchronously to catch descendants;
65
+ // waiting synchronously for taskkill can itself exceed the provider timeout
66
+ // on a heavily loaded Windows host. If the identity was verified, retain the
67
+ // PID-reuse guard for cleanup records.
68
+ if (startedAt.startsWith('unverified:')) {
69
+ if (child.exitCode !== null || child.signalCode !== null || child.killed)
70
+ return true;
71
+ try {
72
+ const killed = child.kill('SIGKILL');
73
+ if (killed && process.platform === 'win32') {
74
+ try {
75
+ const tree = spawn('taskkill', ['/PID', String(processPid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
76
+ tree.unref();
77
+ }
78
+ catch { /* direct child termination already succeeded */ }
79
+ }
80
+ return killed;
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ }
86
+ if (processIncarnation(processPid) !== startedAt)
62
87
  return false;
63
88
  return killProcessTreeForCleanup(processPid);
64
89
  });
@@ -76,6 +101,7 @@ export function startProviderProcess(agent, invocation, options = {}) {
76
101
  let completionTimer;
77
102
  let recordFailure;
78
103
  let terminationConfirmed = false;
104
+ let forcedTerminationAttempted = false;
79
105
  let settled = false;
80
106
  let resolveCompletion = () => { };
81
107
  const completion = new Promise(resolveCompletionValue => {
@@ -119,14 +145,24 @@ export function startProviderProcess(agent, invocation, options = {}) {
119
145
  stderrTruncated: stderr.truncated,
120
146
  telemetry: telemetry.finish(),
121
147
  });
148
+ const forceTerminateExactChild = () => {
149
+ if (child.exitCode !== null || child.signalCode !== null || child.killed)
150
+ return;
151
+ try {
152
+ child.kill('SIGKILL');
153
+ }
154
+ catch { /* the tree killer may have won the race */ }
155
+ };
122
156
  const finalize = (exitCode) => {
123
157
  if (settled)
124
158
  return;
125
159
  supervision.flush();
126
160
  // Windows can emit close before taskkill's process-tree state is observable.
127
161
  // Reconfirm here so successful termination does not leave a stale ownership record.
128
- if (termination && pid !== undefined && !terminationConfirmed) {
162
+ if (termination && pid !== undefined && !terminationConfirmed && !forcedTerminationAttempted) {
129
163
  terminationConfirmed = terminateProcessTree(pid, true);
164
+ if (terminationConfirmed)
165
+ forceTerminateExactChild();
130
166
  }
131
167
  const details = evidence();
132
168
  if (recordFailure) {
@@ -154,8 +190,12 @@ export function startProviderProcess(agent, invocation, options = {}) {
154
190
  if (pid !== undefined)
155
191
  terminationConfirmed = terminateProcessTree(pid, false);
156
192
  forceTimer = setTimeout(() => {
157
- if (pid !== undefined && !settled)
193
+ if (pid !== undefined && !settled) {
194
+ forcedTerminationAttempted = true;
158
195
  terminationConfirmed = terminateProcessTree(pid, true);
196
+ if (terminationConfirmed)
197
+ forceTerminateExactChild();
198
+ }
159
199
  // Allow close/pipe draining to confirm termination before the bounded fallback.
160
200
  if (!settled)
161
201
  completionTimer = setTimeout(() => {
@@ -17,12 +17,26 @@ 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 CLI is a Gemini fork with similar arguments
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' : 'auto_edit';
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
+ }
27
+ if (agent === 'opencode' || agent === 'kilo') {
28
+ const args = ['run', '--format', 'json', '--auto'];
29
+ if (permissions === 'read-only')
30
+ args.push('--agent', 'plan');
31
+ if (permissions === 'unsafe')
32
+ args.push('--dangerously-skip-permissions');
33
+ return args;
34
+ }
35
+ if (agent === 'pi') {
36
+ const args = ['--mode', 'json', '--no-session'];
37
+ if (permissions !== 'unsafe')
38
+ args.push('--tools', permissions === 'read-only' ? 'read,grep,find,ls' : 'read,bash,edit,write');
39
+ return args;
26
40
  }
27
41
  if (permissions === 'unsafe')
28
42
  return ['--yolo', '--output-format', 'stream-json'];
@@ -43,6 +57,10 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
43
57
  throw new Error('Qwen does not support the reasoningEffort selection');
44
58
  if (agent === 'qwen' && parsedSelection.nativeMultiAgent === true)
45
59
  throw new Error('Qwen does not support enabling the nativeMultiAgent selection');
60
+ if (parsedSelection.provider && !['opencode', 'kilo', 'pi'].includes(agent))
61
+ throw new Error(`${agent} does not support an explicit provider selection`);
62
+ if (parsedSelection.variant && !['opencode', 'kilo', 'pi'].includes(agent))
63
+ throw new Error(`${agent} does not support a model variant selection`);
46
64
  const args = argsFor(agent, permissions);
47
65
  if (output.schemaFile !== undefined || output.jsonSchema !== undefined) {
48
66
  if (agent === 'codex' && output.schemaFile && output.jsonSchema === undefined) {
@@ -56,26 +74,63 @@ export function buildProviderInvocation(agent, prompt, cwd, permissions = 'safe'
56
74
  throw new Error('Inline structured output is unsupported by the Windows provider shell shim');
57
75
  args.push('--json-schema', schema);
58
76
  }
59
- else
77
+ else if (!['opencode', 'kilo', 'pi'].includes(agent))
60
78
  throw new Error(`${agent} structured output schema requires ${agent === 'codex' ? 'schemaFile' : agent === 'claude' ? 'jsonSchema' : 'a supported native schema option (unavailable)'}`);
61
79
  }
62
- if (parsedSelection.model)
63
- args.push('--model', parsedSelection.model);
80
+ if (parsedSelection.provider && agent === 'pi')
81
+ args.push('--provider', parsedSelection.provider);
82
+ if (parsedSelection.model) {
83
+ const qualified = agent === 'qwen' && parsedSelection.model.includes('::')
84
+ ? parsedSelection.model.match(/^(openai|anthropic|gemini|vertex-ai|qwen-oauth)::(.+)$/u) : undefined;
85
+ if (agent === 'qwen' && parsedSelection.model.includes('::') && !qualified)
86
+ throw Error('Invalid Qwen auth/model selector');
87
+ if (qualified) {
88
+ const model = ModelSelectionSchema.shape.model.parse(qualified[2]);
89
+ args.push('--auth-type', qualified[1], '--model', model);
90
+ }
91
+ else {
92
+ const qualified = parsedSelection.provider && (agent === 'opencode' || agent === 'kilo')
93
+ ? `${parsedSelection.provider}/${parsedSelection.model}`
94
+ : parsedSelection.model;
95
+ args.push('--model', qualified);
96
+ }
97
+ }
64
98
  if (parsedSelection.reasoningEffort) {
65
99
  if (agent === 'claude')
66
100
  args.push('--effort', parsedSelection.reasoningEffort);
67
101
  else if (agent === 'codex')
68
102
  args.push('--config', `model_reasoning_effort=${parsedSelection.reasoningEffort}`);
103
+ else if (agent === 'opencode' || agent === 'kilo')
104
+ args.push('--variant', parsedSelection.reasoningEffort);
105
+ else if (agent === 'pi')
106
+ args.push('--thinking', parsedSelection.reasoningEffort);
107
+ }
108
+ if (parsedSelection.variant) {
109
+ if (agent === 'opencode' || agent === 'kilo')
110
+ args.push('--variant', parsedSelection.variant);
111
+ else if (agent === 'pi') {
112
+ if (parsedSelection.reasoningEffort && parsedSelection.reasoningEffort !== parsedSelection.variant)
113
+ throw new Error('Pi reasoningEffort and variant selections must match');
114
+ if (!parsedSelection.reasoningEffort)
115
+ args.push('--thinking', parsedSelection.variant);
116
+ }
69
117
  }
70
118
  if (agent === 'codex' && parsedSelection.nativeMultiAgent === false)
71
119
  args.push('--disable', 'multi_agent');
72
120
  if (agent === 'claude' && parsedSelection.nativeMultiAgent === false)
73
121
  args.push('--disallowedTools', 'Agent', 'Task', 'TeamCreate', 'SendMessage');
122
+ if (agent === 'qwen' && parsedSelection.nativeMultiAgent === false) {
123
+ args.push('--exclude-tools', 'agent', 'task', 'create_sub_session', 'team_create', 'send_message');
124
+ }
74
125
  if (parsedSelection.bare) {
75
126
  if (agent === 'codex')
76
127
  args.push('--ignore-user-config');
77
128
  else if (agent === 'claude')
78
129
  args.push('--bare');
130
+ else if (agent === 'opencode' || agent === 'kilo')
131
+ args.push('--pure');
132
+ else if (agent === 'pi')
133
+ throw new Error('Pi does not support the bare startup selection');
79
134
  }
80
135
  if (agent === 'gemini' && parsedSelection.nativeMultiAgent === false) {
81
136
  return { command: process.execPath, args: [fileURLToPath(new URL('../../hooks/bounded-gemini.mjs', import.meta.url)), ...args], input: prompt, cwd };