@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
package/dist/prd/command.js
CHANGED
|
@@ -10,23 +10,23 @@ import { acquireLock, releaseLock } from '../loop/lock.js';
|
|
|
10
10
|
import { agentInvocation, buildWatchdogInvocation, runAgent, isAgentAvailable, } from '../loop/runner.js';
|
|
11
11
|
import { resolveIdleMs } from '../loop/run-command.js';
|
|
12
12
|
import { detectHostAgent, resolveRunnerAgent } from '../agents/host.js';
|
|
13
|
-
export const PRD_TEMPLATE = `# Yoke PRD — the loop picks the lowest-priority open story each iteration.
|
|
14
|
-
# Story format (see canon/loop/prd.schema.md):
|
|
15
|
-
# - id: STORY-1
|
|
16
|
-
# title: scaffold the project with a runnable test suite
|
|
17
|
-
# priority: 1
|
|
18
|
-
# needs: [] # optional story IDs that must pass first
|
|
19
|
-
# area: foundation # optional collision domain for parallel runs
|
|
20
|
-
# agent: codex # optional claude|codex|gemini affinity
|
|
21
|
-
# acceptance:
|
|
22
|
-
# - id: suite-runs
|
|
23
|
-
# text: "the project test suite can run"
|
|
24
|
-
# verify: ["npm run test:suite-runs"]
|
|
25
|
-
# - id: scaffold-starts
|
|
26
|
-
# text: "the scaffolded application starts"
|
|
27
|
-
# verify: ["npm run test:scaffold-starts"]
|
|
28
|
-
# passes: false
|
|
29
|
-
[]
|
|
13
|
+
export const PRD_TEMPLATE = `# Yoke PRD — the loop picks the lowest-priority open story each iteration.
|
|
14
|
+
# Story format (see canon/loop/prd.schema.md):
|
|
15
|
+
# - id: STORY-1
|
|
16
|
+
# title: scaffold the project with a runnable test suite
|
|
17
|
+
# priority: 1
|
|
18
|
+
# needs: [] # optional story IDs that must pass first
|
|
19
|
+
# area: foundation # optional collision domain for parallel runs
|
|
20
|
+
# agent: codex # optional claude|codex|gemini affinity
|
|
21
|
+
# acceptance:
|
|
22
|
+
# - id: suite-runs
|
|
23
|
+
# text: "the project test suite can run"
|
|
24
|
+
# verify: ["npm run test:suite-runs"]
|
|
25
|
+
# - id: scaffold-starts
|
|
26
|
+
# text: "the scaffolded application starts"
|
|
27
|
+
# verify: ["npm run test:scaffold-starts"]
|
|
28
|
+
# passes: false
|
|
29
|
+
[]
|
|
30
30
|
`;
|
|
31
31
|
export const MAX_PLANNING_BRIEF_CHARS = 20_000;
|
|
32
32
|
export const MAX_PLANNING_BRIEF_BYTES = MAX_PLANNING_BRIEF_CHARS * 4;
|
package/dist/retrofit/apply.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
|
+
import { mergeQwenSettings } from './qwen-settings.js';
|
|
3
4
|
import { mergeJson } from './merge-json.js';
|
|
4
5
|
import { carryPreserved } from './preserve.js';
|
|
5
6
|
export function applyActions(actions, targetDir, opts) {
|
|
@@ -28,7 +29,7 @@ export function applyActions(actions, targetDir, opts) {
|
|
|
28
29
|
catch {
|
|
29
30
|
throw new Error(`yoke: cannot merge ${action.target} — existing file is not valid JSON. Fix or delete it and re-run.`);
|
|
30
31
|
}
|
|
31
|
-
const merged = JSON.stringify(mergeJson(parsedCurrent, JSON.parse(action.content)), null, 2) + '\n';
|
|
32
|
+
const merged = JSON.stringify((action.target === '.qwen/settings.json' ? mergeQwenSettings : mergeJson)(parsedCurrent, JSON.parse(action.content)), null, 2) + '\n';
|
|
32
33
|
if (merged === current) {
|
|
33
34
|
results.push({ target: action.target, status: 'unchanged', reason: action.reason });
|
|
34
35
|
continue;
|
|
@@ -43,6 +44,12 @@ export function applyActions(actions, targetDir, opts) {
|
|
|
43
44
|
}
|
|
44
45
|
if (typeof action.content === 'string') {
|
|
45
46
|
const current = currentBytes.toString('utf8');
|
|
47
|
+
// Exact matches need no preservation pass. In particular, skill examples
|
|
48
|
+
// may contain literal preserve markers which must not be reinterpreted.
|
|
49
|
+
if (current === action.content) {
|
|
50
|
+
results.push({ target: action.target, status: 'unchanged', reason: action.reason });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
46
53
|
// Carry user content marked with yoke preserve markers into the new file.
|
|
47
54
|
content = carryPreserved(current, action.content);
|
|
48
55
|
if (content !== action.content)
|
package/dist/retrofit/config.js
CHANGED
|
@@ -80,7 +80,7 @@ export const YokeConfigSchema = z.object({
|
|
|
80
80
|
model: z.string().min(1).optional(),
|
|
81
81
|
reasoningEffort: z.string().min(1).optional(),
|
|
82
82
|
}).optional(),
|
|
83
|
-
workers: z.array(RoutingWorkerSchema).max(
|
|
83
|
+
workers: z.array(RoutingWorkerSchema).max(32).default([]),
|
|
84
84
|
rules: z.array(RoutingRuleSchema).max(100).optional(),
|
|
85
85
|
}).optional(),
|
|
86
86
|
commit: z.object({
|
package/dist/retrofit/detect.js
CHANGED
|
@@ -6,21 +6,21 @@ import { hasWsl } from '../wsl.js';
|
|
|
6
6
|
import { detectGstack } from '../gstack.js';
|
|
7
7
|
import { PRESERVE_SCAFFOLD } from '../preserve.js';
|
|
8
8
|
import { skillPackageActions } from '../skill-actions.js';
|
|
9
|
-
const GSTACK_COMPOSE = `## Composed tools (gstack detected)
|
|
10
|
-
|
|
11
|
-
This project also has [gstack](https://github.com/garrytan/gstack) installed. For capabilities Yoke does not ship, prefer gstack's skills:
|
|
12
|
-
|
|
13
|
-
- Live-browser QA → \`/qa\`
|
|
14
|
-
- Security audit → \`/cso\`
|
|
15
|
-
- Ship / deploy → \`/ship\`, \`/land-and-deploy\`
|
|
9
|
+
const GSTACK_COMPOSE = `## Composed tools (gstack detected)
|
|
10
|
+
|
|
11
|
+
This project also has [gstack](https://github.com/garrytan/gstack) installed. For capabilities Yoke does not ship, prefer gstack's skills:
|
|
12
|
+
|
|
13
|
+
- Live-browser QA → \`/qa\`
|
|
14
|
+
- Security audit → \`/cso\`
|
|
15
|
+
- Ship / deploy → \`/ship\`, \`/land-and-deploy\`
|
|
16
16
|
`;
|
|
17
|
-
const claudeMd = (rtkNote, composeNote) => `# Project Instructions
|
|
18
|
-
|
|
19
|
-
This project uses the Yoke harness. Baseline instructions:
|
|
20
|
-
|
|
21
|
-
@AGENTS.md
|
|
22
|
-
${rtkNote ? `\n${rtkNote}\n` : ''}${composeNote ? `\n${composeNote}\n` : ''}
|
|
23
|
-
${PRESERVE_SCAFFOLD}
|
|
17
|
+
const claudeMd = (rtkNote, composeNote) => `# Project Instructions
|
|
18
|
+
|
|
19
|
+
This project uses the Yoke harness. Baseline instructions:
|
|
20
|
+
|
|
21
|
+
@AGENTS.md
|
|
22
|
+
${rtkNote ? `\n${rtkNote}\n` : ''}${composeNote ? `\n${composeNote}\n` : ''}
|
|
23
|
+
${PRESERVE_SCAFFOLD}
|
|
24
24
|
`;
|
|
25
25
|
export function planClaude(canonDir, targetDir, wslAvailable = hasWsl(), codeGraph = 'graphify', gstackDetected = detectGstack(targetDir)) {
|
|
26
26
|
const manifest = loadManifest(join(canonDir, 'manifest.yaml'));
|
|
@@ -47,8 +47,8 @@ export function planQwen(canonDir, _targetDir, codeGraph = 'graphify') {
|
|
|
47
47
|
actions.push({
|
|
48
48
|
kind: 'write',
|
|
49
49
|
target: '.qwen/hooks/qwen-rtk-hook.mjs',
|
|
50
|
-
content: readFileSync(join(canonDir, 'tools/
|
|
51
|
-
reason: 'portable RTK
|
|
50
|
+
content: readFileSync(join(canonDir, 'tools/qwen-rtk-hook.mjs'), 'utf8'),
|
|
51
|
+
reason: 'portable RTK PreToolUse retry guard',
|
|
52
52
|
});
|
|
53
53
|
// Merge to preserve user MCP servers, context and unrelated hooks.
|
|
54
54
|
actions.push({
|
|
@@ -58,7 +58,7 @@ export function planQwen(canonDir, _targetDir, codeGraph = 'graphify') {
|
|
|
58
58
|
content: JSON.stringify({
|
|
59
59
|
mcpServers: mcpServers(codeGraph),
|
|
60
60
|
context: { fileName: ['AGENTS.md', 'QWEN.md'] },
|
|
61
|
-
hooks: {
|
|
61
|
+
hooks: { PreToolUse: [{ matcher: '^run_shell_command$', hooks: [{ name: 'yoke-rtk', type: 'command', command: 'node .qwen/hooks/qwen-rtk-hook.mjs' }] }] },
|
|
62
62
|
}, null, 2) + '\n',
|
|
63
63
|
reason: 'MCP servers + AGENTS.md context',
|
|
64
64
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const PRESERVE_START = '<!-- yoke:preserve:start -->';
|
|
2
2
|
export const PRESERVE_END = '<!-- yoke:preserve:end -->';
|
|
3
|
-
export const PRESERVE_SCAFFOLD = `${PRESERVE_START}
|
|
4
|
-
<!-- Project-specific instructions go here. Yoke keeps this block across retrofits. -->
|
|
3
|
+
export const PRESERVE_SCAFFOLD = `${PRESERVE_START}
|
|
4
|
+
<!-- Project-specific instructions go here. Yoke keeps this block across retrofits. -->
|
|
5
5
|
${PRESERVE_END}`;
|
|
6
6
|
/**
|
|
7
7
|
* Extract the inner content of every balanced preserve-marker pair, in order.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { mergeJson } from './merge-json.js';
|
|
2
|
+
const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
/** Remove only Yoke 1.11's inert Gemini-shaped hook, preserving unrelated hooks. */
|
|
4
|
+
export function mergeQwenSettings(current, incoming) {
|
|
5
|
+
if (!record(current) || !record(current.hooks) || !Array.isArray(current.hooks.BeforeTool))
|
|
6
|
+
return mergeJson(current, incoming);
|
|
7
|
+
const beforeTool = current.hooks.BeforeTool.flatMap(group => {
|
|
8
|
+
if (!record(group) || group.matcher !== '^run_shell_command$' || !Array.isArray(group.hooks))
|
|
9
|
+
return [group];
|
|
10
|
+
const hooks = group.hooks.filter(hook => !(record(hook) && hook.name === 'yoke-rtk' && hook.type === 'command' && hook.command === 'node .qwen/hooks/qwen-rtk-hook.mjs'));
|
|
11
|
+
return hooks.length ? [{ ...group, hooks }] : [];
|
|
12
|
+
});
|
|
13
|
+
const hooks = { ...current.hooks, BeforeTool: beforeTool };
|
|
14
|
+
if (!beforeTool.length)
|
|
15
|
+
delete hooks.BeforeTool;
|
|
16
|
+
return mergeJson({ ...current, hooks }, incoming);
|
|
17
|
+
}
|
|
@@ -49,7 +49,7 @@ export function skillPackageActions(canonDir, skill, provider) {
|
|
|
49
49
|
.map(file => ({
|
|
50
50
|
kind: 'write',
|
|
51
51
|
target: `${roots[provider]}/${skill.id}/${file.relativePath}`,
|
|
52
|
-
content: provider === 'claude' && skill.invocation === 'manual' && file.relativePath === 'SKILL.md'
|
|
52
|
+
content: (provider === 'claude' || provider === 'qwen') && skill.invocation === 'manual' && file.relativePath === 'SKILL.md'
|
|
53
53
|
? manualClaudeSkill(file.content, skill)
|
|
54
54
|
: portableContent(file),
|
|
55
55
|
executable: file.executable,
|
package/dist/setup/command.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline/promises';
|
|
2
2
|
import { stdin as input, stdout as output } from 'node:process';
|
|
3
3
|
import { detectHostAgent } from '../agents/host.js';
|
|
4
|
-
import { loadConfig, saveConfig } from '../retrofit/config.js';
|
|
4
|
+
import { loadConfig, saveConfig, YokeConfigSchema } from '../retrofit/config.js';
|
|
5
5
|
import { detectProject } from '../retrofit/detect.js';
|
|
6
|
+
import { applyActions } from '../retrofit/apply.js';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { modelPresetWorkers, planModelPresets } from './model-presets.js';
|
|
6
9
|
import { runRetrofit } from '../retrofit/command.js';
|
|
7
10
|
const ALL_AGENTS = ['claude', 'codex', 'gemini', 'qwen'];
|
|
8
11
|
export function defaultRoutingWorkers(agents) {
|
|
@@ -26,10 +29,8 @@ export function defaultRoutingWorkers(agents) {
|
|
|
26
29
|
{ id: 'gemini-frontier', agent: 'gemini', model: 'gemini-2.5-pro', tier: 'frontier', costTier: 'high', capabilities: ['architecture'] },
|
|
27
30
|
],
|
|
28
31
|
qwen: [
|
|
29
|
-
|
|
30
|
-
{ id: 'qwen-standard', agent: 'qwen',
|
|
31
|
-
{ id: 'qwen-strong', agent: 'qwen', model: 'qwen3-coder-plus', tier: 'strong', costTier: 'medium', capabilities: ['debugging'] },
|
|
32
|
-
{ id: 'qwen-frontier', agent: 'qwen', model: 'qwen3-235b-a22b', tier: 'frontier', costTier: 'high', capabilities: ['architecture'] },
|
|
32
|
+
// Respect the user's Qwen Code account/model. API presets are opt-in.
|
|
33
|
+
{ id: 'qwen-standard', agent: 'qwen', tier: 'standard', costTier: 'medium', capabilities: ['implementation'] },
|
|
33
34
|
],
|
|
34
35
|
};
|
|
35
36
|
return agents.flatMap(agent => workers[agent]);
|
|
@@ -50,6 +51,9 @@ function yes(value, fallback) {
|
|
|
50
51
|
}
|
|
51
52
|
export async function runSetup(targetDir, opts = {}) {
|
|
52
53
|
const existing = loadConfig(targetDir);
|
|
54
|
+
const modelProviders = opts.modelProviders ?? [];
|
|
55
|
+
const presetActions = planModelPresets(targetDir, modelProviders);
|
|
56
|
+
const presetWorkers = modelPresetWorkers(modelProviders);
|
|
53
57
|
const detected = detectProject(targetDir);
|
|
54
58
|
const host = opts.host ?? detectHostAgent();
|
|
55
59
|
const configuredAgents = existing?.agents.filter(a => ALL_AGENTS.includes(a)) ?? [];
|
|
@@ -59,7 +63,7 @@ export async function runSetup(targetDir, opts = {}) {
|
|
|
59
63
|
? configuredAgents
|
|
60
64
|
: detected.agents.length > 0
|
|
61
65
|
? detected.agents
|
|
62
|
-
: [host ?? 'claude'];
|
|
66
|
+
: modelProviders.length ? ['qwen'] : [host ?? 'claude'];
|
|
63
67
|
const defaultGraph = opts.codeGraph ?? existing?.codeGraph ?? 'graphify';
|
|
64
68
|
const defaultLoop = opts.loop ?? existing?.loop.enabled ?? true;
|
|
65
69
|
const defaultRunner = opts.runner ?? existing?.runner?.agent ?? (host && defaultAgents.includes(host) ? host : defaultAgents[0] ?? host ?? 'claude');
|
|
@@ -94,17 +98,26 @@ export async function runSetup(targetDir, opts = {}) {
|
|
|
94
98
|
decisionPolicy = policyAnswer;
|
|
95
99
|
routing = yes(await ask(`Enable adaptive multi-model routing? [${defaultRouting ? 'yes' : 'no'}]: `), defaultRouting);
|
|
96
100
|
}
|
|
101
|
+
if (modelProviders.length && !agents.includes('qwen'))
|
|
102
|
+
agents = [...agents, 'qwen'];
|
|
97
103
|
if (!agents.includes(runner))
|
|
98
104
|
agents = [...agents, runner];
|
|
99
105
|
const code = runRetrofit(targetDir, { loop, agents, codeGraph, host });
|
|
100
106
|
if (code !== 0)
|
|
101
107
|
return code;
|
|
108
|
+
applyActions(presetActions, targetDir, { backupDir: join(targetDir, '.yoke', 'backups', `model-presets-${Date.now()}`) });
|
|
102
109
|
const config = loadConfig(targetDir);
|
|
103
110
|
if (!config)
|
|
104
111
|
return 1;
|
|
105
112
|
config.loop = { parallel: 'auto', isolate: true, ...config.loop, enabled: loop, decisionPolicy };
|
|
106
|
-
|
|
113
|
+
const priorRunner = existing?.runner?.agent ?? existing?.agents[0];
|
|
114
|
+
const selectPresetModel = presetWorkers.length > 0 && runner === 'qwen' && (!existing || (priorRunner !== undefined && priorRunner !== runner));
|
|
115
|
+
if (selectPresetModel && priorRunner !== 'qwen')
|
|
116
|
+
config.runner = { permissions: config.runner?.permissions };
|
|
117
|
+
config.runner = { ...config.runner, agent: runner, ...(selectPresetModel && !config.runner?.model ? { model: presetWorkers[0].model } : {}) };
|
|
107
118
|
const existingWorkers = config.routing?.workers ?? [];
|
|
119
|
+
const baseWorkers = existingWorkers.length > 0 && !opts.routingPreset ? existingWorkers : defaultRoutingWorkers(agents).filter(worker => !modelProviders.length || worker.agent !== 'qwen');
|
|
120
|
+
const workers = [...baseWorkers, ...presetWorkers.filter(worker => !baseWorkers.some(existing => existing.id === worker.id))];
|
|
108
121
|
config.routing = {
|
|
109
122
|
...config.routing,
|
|
110
123
|
enabled: routing,
|
|
@@ -113,8 +126,9 @@ export async function runSetup(targetDir, opts = {}) {
|
|
|
113
126
|
assessmentPolicy: existing?.routing?.assessmentPolicy ?? (existing ? 'on-demand' : 'prepared'),
|
|
114
127
|
fallback: existing?.routing?.fallback ?? (existing ? 'parent' : 'block'),
|
|
115
128
|
...(config.routing?.orchestrator ? { orchestrator: config.routing.orchestrator } : {}),
|
|
116
|
-
workers
|
|
129
|
+
workers,
|
|
117
130
|
};
|
|
131
|
+
YokeConfigSchema.parse(config);
|
|
118
132
|
saveConfig(targetDir, config);
|
|
119
133
|
console.log(`Yoke setup complete: agents=${agents.join(',')} · runner=${runner} · loop=${loop ? 'on' : 'off'} · routing=${routing ? 'on' : 'off'} · decisions=${decisionPolicy}`);
|
|
120
134
|
return 0;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export const MODEL_PROVIDERS = ['deepseek', 'kimi'];
|
|
4
|
+
const PRESETS = {
|
|
5
|
+
deepseek: {
|
|
6
|
+
baseUrl: 'https://api.deepseek.com/v1', envKey: 'DEEPSEEK_API_KEY',
|
|
7
|
+
models: [
|
|
8
|
+
{ id: 'deepseek-v4-flash', workerId: 'deepseek-standard', tier: 'standard', costTier: 'low', contextWindowSize: 1_000_000 },
|
|
9
|
+
{ id: 'deepseek-v4-pro', workerId: 'deepseek-strong', tier: 'strong', costTier: 'medium', contextWindowSize: 1_000_000 },
|
|
10
|
+
],
|
|
11
|
+
},
|
|
12
|
+
kimi: {
|
|
13
|
+
baseUrl: 'https://api.moonshot.ai/v1', envKey: 'MOONSHOT_API_KEY',
|
|
14
|
+
models: [
|
|
15
|
+
{ id: 'kimi-k2.6', workerId: 'kimi-standard', tier: 'standard', costTier: 'medium', contextWindowSize: 262_144 },
|
|
16
|
+
{ id: 'kimi-k2.7-code', workerId: 'kimi-strong', tier: 'strong', costTier: 'medium', contextWindowSize: 262_144 },
|
|
17
|
+
{ id: 'kimi-k3', workerId: 'kimi-frontier', tier: 'frontier', costTier: 'high', contextWindowSize: 1_000_000 },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
export function modelPresetWorkers(providers) {
|
|
22
|
+
return [...new Set(providers)].flatMap(provider => PRESETS[provider].models.map(model => ({
|
|
23
|
+
id: model.workerId, agent: 'qwen', model: `openai::${model.id}`, tier: model.tier,
|
|
24
|
+
costTier: model.costTier, capabilities: ['implementation'],
|
|
25
|
+
})));
|
|
26
|
+
}
|
|
27
|
+
/** Explicit opt-in only. Keep user routes intact; new entries contain only environment key references. */
|
|
28
|
+
export function planModelPresets(targetDir, providers) {
|
|
29
|
+
if (!providers.length)
|
|
30
|
+
return [];
|
|
31
|
+
const file = join(targetDir, '.qwen/settings.json');
|
|
32
|
+
const settings = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : {};
|
|
33
|
+
if (!settings || typeof settings !== 'object' || Array.isArray(settings))
|
|
34
|
+
throw Error('Qwen settings must be an object');
|
|
35
|
+
if (settings.modelProviders !== undefined && (!settings.modelProviders || typeof settings.modelProviders !== 'object' || Array.isArray(settings.modelProviders)))
|
|
36
|
+
throw Error('Qwen modelProviders must be an object');
|
|
37
|
+
const existing = settings.modelProviders?.openai ?? [];
|
|
38
|
+
if (!Array.isArray(existing) || existing.some(model => !model || typeof model.id !== 'string'))
|
|
39
|
+
throw Error('Qwen modelProviders.openai must be an array of models with ids');
|
|
40
|
+
const entries = [...new Set(providers)].flatMap(provider => {
|
|
41
|
+
const preset = PRESETS[provider];
|
|
42
|
+
return preset.models.filter(model => !existing.some(entry => entry.id === model.id)).map(model => ({
|
|
43
|
+
id: model.id, baseUrl: preset.baseUrl, envKey: preset.envKey,
|
|
44
|
+
generationConfig: { contextWindowSize: model.contextWindowSize },
|
|
45
|
+
}));
|
|
46
|
+
});
|
|
47
|
+
return [{ kind: 'write', target: '.qwen/settings.json', merge: true, content: JSON.stringify({ modelProviders: { openai: entries } }, null, 2) + '\n', reason: 'explicit DeepSeek/Kimi API model presets (environment key references only)' }];
|
|
48
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
# Routing by task requirements
|
|
2
|
-
|
|
1
|
+
# Routing by task requirements
|
|
2
|
+
|
|
3
3
|
Capability routing is available in Yoke 1.9.0. Batch preparation, separate planning settings and routing limits described below are local, unreleased additions.
|
|
4
|
-
|
|
5
|
-
New setups use `routing.strategy: capability`. Existing explicit strategies and profiles remain unchanged. To opt an existing project into capability routing with its current profiles:
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
yoke setup . --yes --routing --routing-strategy=capability
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Give each existing worker a `tier: light|standard|strong|frontier`. Profiles without a tier remain usable with legacy strategies but are not candidates for capability selection. To explicitly replace worker profiles with the supplied provider presets, add `--routing-preset`. This replaces customized worker profiles; omit it to retain them.
|
|
12
|
-
|
|
13
|
-
## Planning and selection
|
|
14
|
-
|
|
4
|
+
|
|
5
|
+
New setups use `routing.strategy: capability`. Existing explicit strategies and profiles remain unchanged. To opt an existing project into capability routing with its current profiles:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
yoke setup . --yes --routing --routing-strategy=capability
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Give each existing worker a `tier: light|standard|strong|frontier`. Profiles without a tier remain usable with legacy strategies but are not candidates for capability selection. To explicitly replace worker profiles with the supplied provider presets, add `--routing-preset`. This replaces customized worker profiles; omit it to retain them.
|
|
12
|
+
|
|
13
|
+
## Planning and selection
|
|
14
|
+
|
|
15
15
|
The start provider/model remains the planning default. Optional `planning.agent`, `planning.model` and `planning.reasoningEffort` select a separate planner without changing the execution model. Draft and change-inbox planning request complete assessments in the same pass that creates the tasks. The inbox still performs its separate coverage review.
|
|
16
16
|
|
|
17
17
|
New setups use `routing.assessmentPolicy: prepared` and `routing.fallback: block`. Before dispatch, each unfinished task must have 2–5 executable criteria and a current assessment. No per-task planning call runs in this mode. Existing configurations retain `on-demand` and `parent` unless explicitly changed; on-demand routing makes a read-only planning call for an unassessed task and caches its result.
|
|
@@ -41,42 +41,42 @@ routing:
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
`maxTier` limits automatic execution and escalation, including routing-rule selections. If a task needs frontier while the ceiling is strong, it blocks; Yoke does not lower the required capability. Planning itself may still use Astra. Explicit quality-role model overrides retain precedence. Goal execution retains its own protected manifest and budgets, uses the configured planner on demand, and honors routing fallback/tier limits; PRD preparation policy does not apply to synthetic goal tasks.
|
|
44
|
-
|
|
45
|
-
An assessment is a planning judgment, not a measured success probability. High testability means executable checks can detect an incorrect implementation. High uncertainty, architecture work or high risk require the frontier tier; difficult or broadly coupled work requires strong; routine implementation requires standard. Light is reserved for clear, low-risk mechanical work with strong checks. Weak testability raises the minimum tier. Reviews and critics have a standard minimum even for light tasks.
|
|
46
|
-
|
|
47
|
-
```yaml
|
|
48
|
-
assessment:
|
|
49
|
-
taskClass: implementation
|
|
50
|
-
difficulty: medium
|
|
51
|
-
uncertainty: low
|
|
52
|
-
risk: low
|
|
53
|
-
scope: low
|
|
54
|
-
testability: high
|
|
55
|
-
reason: Existing handler pattern and executable contract tests
|
|
56
|
-
approach: Extend the handler, cover the boundary cases, run contract tests
|
|
57
|
-
```
|
|
58
|
-
|
|
44
|
+
|
|
45
|
+
An assessment is a planning judgment, not a measured success probability. High testability means executable checks can detect an incorrect implementation. High uncertainty, architecture work or high risk require the frontier tier; difficult or broadly coupled work requires strong; routine implementation requires standard. Light is reserved for clear, low-risk mechanical work with strong checks. Weak testability raises the minimum tier. Reviews and critics have a standard minimum even for light tasks.
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
assessment:
|
|
49
|
+
taskClass: implementation
|
|
50
|
+
difficulty: medium
|
|
51
|
+
uncertainty: low
|
|
52
|
+
risk: low
|
|
53
|
+
scope: low
|
|
54
|
+
testability: high
|
|
55
|
+
reason: Existing handler pattern and executable contract tests
|
|
56
|
+
approach: Extend the handler, cover the boundary cases, run contract tests
|
|
57
|
+
```
|
|
58
|
+
|
|
59
59
|
Yoke chooses an eligible profile at or above the required tier, then compares declared cost tiers. Optional `roles: [implementation, reviewer, critic, repair]` limits a profile's uses. Task `agent` affinity restricts implementation to that provider. Explicit routing rules and explicit quality role models retain precedence. With legacy `fallback: parent` and no tier ceiling, a missing suitable profile falls back to the start model (or the explicitly bound provider's default) and labels the fallback; it does not prove sufficient capability. `fallback: block` or a configured tier ceiling prevents that fallback. An invalid assessment blocks implementation.
|
|
60
|
-
|
|
61
|
-
## Initial profiles
|
|
62
|
-
|
|
63
|
-
| Tier | Codex | Claude | Gemini |
|
|
64
|
-
| --- | --- | --- | --- |
|
|
65
|
-
| light | gpt-5.6-luna, low | haiku | gemini-2.5-flash |
|
|
66
|
-
| standard | gpt-5.6-terra, medium | sonnet | gemini-2.5-pro |
|
|
67
|
-
| strong | gpt-5.6-sol, high | sonnet, high effort | gemini-2.5-pro |
|
|
68
|
-
| frontier | gpt-6-astra, high | opus | gemini-2.5-pro |
|
|
69
|
-
|
|
70
|
-
These are editable starting hypotheses, not measured equivalences or price claims. The Codex names follow the requested profile family. Account access is not established by finding an installed CLI. Gemini uses documented explicit model IDs and receives no unsupported reasoning-effort parameter. Several Gemini tiers deliberately share Pro; moving between those tiers alone is not a stronger-model transition. Adjust the presets to the models available to your account. Claude aliases can resolve to different concrete models over time. Provider-reported model identity remains separate from requested identity.
|
|
71
|
-
|
|
72
|
-
Provider references: [Claude model configuration](https://code.claude.com/docs/en/model-config), [Gemini model selection](https://geminicli.com/docs/cli/model/).
|
|
73
|
-
|
|
74
|
-
## Repair, escalation and evidence
|
|
75
|
-
|
|
76
|
-
After an independent mechanical failure, capability routing permits one targeted repair at the initial tier, then raises the required tier on further failures. Attempts retain the current worktree and receive the previous gate findings. Every returned candidate still passes the normal acceptance, protection, quality, review and integration gates. Critical decisions, pause/cancellation and protected-acceptance violations stop retries. Provider process failures are classified conservatively as infrastructure; they do not count as evidence that a stronger model is needed.
|
|
77
|
-
|
|
78
|
-
`routing.maxAttempts` limits implementation calls per unchanged task contract (default 5, configurable 1–8). The initial tier imposes an additional bound: light at most 5, standard 4, strong 3, frontier 2. An exhausted task blocks and requires a revised plan. These are inner implementation attempts; the outer loop's iteration count still counts task dispatches. Existing quality repair rounds and time limits remain separate bounds, and quality repairs can raise their profile tier by round. Goal execution keeps its existing global attempt, time and token budgets.
|
|
79
|
-
|
|
80
|
-
Routing observations record task class, required tier, requested and reported models, effort, independent result, duration and available consumption. Selection considers matching project/task-class/tier history from the last 30 days within the bounded registry read. At least ten matching observations are required before an observed success rate below 80% excludes a profile. History is scoped to the concrete reported model to avoid mixing changed aliases. This is a conservative exclusion rule; it does not lower the planner's safety floor or claim calibrated probabilities. Missing usage remains unknown. Financial optimization and cross-provider performance require authenticated benchmarks.
|
|
81
|
-
|
|
82
|
-
The dashboard's Now view shows the last recorded implementation profile, requested model/effort, rationale and next escalation tier. Usage & time retains reported model and role consumption, including assessment calls. Cached planning has no new model-call charge. Routing state is local runtime data and excluded from Yoke story commits.
|
|
60
|
+
|
|
61
|
+
## Initial profiles
|
|
62
|
+
|
|
63
|
+
| Tier | Codex | Claude | Gemini |
|
|
64
|
+
| --- | --- | --- | --- |
|
|
65
|
+
| light | gpt-5.6-luna, low | haiku | gemini-2.5-flash |
|
|
66
|
+
| standard | gpt-5.6-terra, medium | sonnet | gemini-2.5-pro |
|
|
67
|
+
| strong | gpt-5.6-sol, high | sonnet, high effort | gemini-2.5-pro |
|
|
68
|
+
| frontier | gpt-6-astra, high | opus | gemini-2.5-pro |
|
|
69
|
+
|
|
70
|
+
These are editable starting hypotheses, not measured equivalences or price claims. The Codex names follow the requested profile family. Account access is not established by finding an installed CLI. Gemini uses documented explicit model IDs and receives no unsupported reasoning-effort parameter. Several Gemini tiers deliberately share Pro; moving between those tiers alone is not a stronger-model transition. Adjust the presets to the models available to your account. Claude aliases can resolve to different concrete models over time. Provider-reported model identity remains separate from requested identity.
|
|
71
|
+
|
|
72
|
+
Provider references: [Claude model configuration](https://code.claude.com/docs/en/model-config), [Gemini model selection](https://geminicli.com/docs/cli/model/).
|
|
73
|
+
|
|
74
|
+
## Repair, escalation and evidence
|
|
75
|
+
|
|
76
|
+
After an independent mechanical failure, capability routing permits one targeted repair at the initial tier, then raises the required tier on further failures. Attempts retain the current worktree and receive the previous gate findings. Every returned candidate still passes the normal acceptance, protection, quality, review and integration gates. Critical decisions, pause/cancellation and protected-acceptance violations stop retries. Provider process failures are classified conservatively as infrastructure; they do not count as evidence that a stronger model is needed.
|
|
77
|
+
|
|
78
|
+
`routing.maxAttempts` limits implementation calls per unchanged task contract (default 5, configurable 1–8). The initial tier imposes an additional bound: light at most 5, standard 4, strong 3, frontier 2. An exhausted task blocks and requires a revised plan. These are inner implementation attempts; the outer loop's iteration count still counts task dispatches. Existing quality repair rounds and time limits remain separate bounds, and quality repairs can raise their profile tier by round. Goal execution keeps its existing global attempt, time and token budgets.
|
|
79
|
+
|
|
80
|
+
Routing observations record task class, required tier, requested and reported models, effort, independent result, duration and available consumption. Selection considers matching project/task-class/tier history from the last 30 days within the bounded registry read. At least ten matching observations are required before an observed success rate below 80% excludes a profile. History is scoped to the concrete reported model to avoid mixing changed aliases. This is a conservative exclusion rule; it does not lower the planner's safety floor or claim calibrated probabilities. Missing usage remains unknown. Financial optimization and cross-provider performance require authenticated benchmarks.
|
|
81
|
+
|
|
82
|
+
The dashboard's Now view shows the last recorded implementation profile, requested model/effort, rationale and next escalation tier. Usage & time retains reported model and role consumption, including assessment calls. Cached planning has no new model-call charge. Routing state is local runtime data and excluded from Yoke story commits.
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
# Dashboard evolution
|
|
2
|
-
|
|
3
|
-
The local dashboard is an actionable workspace for registered Yoke projects. It reads the same saved project, loop, goal, acceptance, and measurement data as the CLI. Project-controlled text is rendered through `textContent`, and the server remains bound to the loopback interface with same-origin authorization for pause requests.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
Project status is derived from both the saved goal and the latest loop report. A blocked or running loop is not hidden by a completed goal. When goal and loop states differ, the card displays both. Blocked, failed, paused, unavailable, and stale active reports need attention; those projects are ordered before active projects, followed by the remaining projects.
|
|
8
|
-
|
|
9
|
-
Cards also show the reported current task and saved blocker reason when available, so the overview explains why a project needs attention before opening it.
|
|
10
|
-
|
|
11
|
-
An active loop report more than 20 minutes old is labeled **unconfirmed**. This means Yoke has an old active report, not evidence that the process is still live. The overview can be searched by project name, canonical path, or goal objective and filtered to All, Active, or Needs attention. A no-match state explains the result and provides a clear action that resets both search and filter.
|
|
12
|
-
|
|
13
|
-
## Durable navigation
|
|
14
|
-
|
|
15
|
-
The URL hash stores the current screen, project, project tab, period, UTC grouping, and complete custom date range. Supported screens are the overview, workspace comparison, and project detail. Supported project tabs are Now, Usage & time, and Results; periods are 1, 7, 30, 90, or 365 days; groupings are day, week, or month. Custom dates must be real ISO calendar dates in chronological order and cover at most 366 inclusive days.
|
|
16
|
-
|
|
17
|
-
Invalid hash state returns to the overview with the 30-day/day defaults. Browser back and forward, a page reload, and Refresh restore the validated state. Refresh reloads data without resetting the selected view or controls. Starting any navigation aborts earlier fetches and changes a request generation, so an older response cannot replace the current screen.
|
|
18
|
-
|
|
19
|
-
The workspace comparison schedules at most three project analytics requests at once. If navigation changes, in-flight fetches are aborted and no additional obsolete project requests are scheduled. All projects and individual project links remain available in the navigation while viewing the comparison.
|
|
20
|
-
|
|
21
|
-
## Usage comparisons
|
|
22
|
-
|
|
23
|
-
Usage & time compares the selected period with the immediately preceding period of exactly the same duration. A single time boundary is captured before either analytics request is made. The comparison covers recorded input plus output tokens, recorded acceptance events, and reported cost.
|
|
24
|
-
|
|
25
|
-
When the preceding value is zero, the dashboard describes no change or new recorded activity instead of calculating an infinite percentage. A cost percentage is shown only when both periods have fully measured cost. Otherwise the dashboard says the percentage is unavailable. Current and previous missing-usage coverage is displayed from calls with unknown usage and unmeasured attempts. Recorded portions remain recorded portions; missing tokens or charges are not estimated as zero.
|
|
26
|
-
|
|
27
|
-
Charts and their tables include periods that contain recorded events. Empty UTC calendar buckets are omitted and the chart explains that a gap means no recorded activity, not a measured zero. Detailed provider/model/role, model timeline, task, and phase tables remain below the comparison.
|
|
28
|
-
|
|
29
|
-
## Design findings and limitations
|
|
30
|
-
|
|
31
|
-
Goal state and loop state describe different durable facts and need independent presentation. Freshness is also separate from state: a saved `running` value can become unconfirmed without being rewritten. Navigation state belongs in the URL because the dashboard has several independently useful views and time controls. Analytics fan-out needs cancellation as well as a concurrency bound because registered workspaces may contain many projects.
|
|
32
|
-
|
|
33
|
-
The dashboard combines local saved evidence with process-identity checks for supervised providers (see [Windows runner validation](WINDOWS-RUNNER-VALIDATION.md)). Unverified process identity remains unknown. It does not reconstruct activity that predates retained measurements, estimate missing provider usage or prices, or turn requested model names into proof of models used. Parallel call durations can overlap, so summed call time is consumption intensity rather than generation speed. The 20-minute freshness threshold is a presentation rule, not a process-health guarantee. No provider performance benchmark is implied by these views.
|
|
1
|
+
# Dashboard evolution
|
|
2
|
+
|
|
3
|
+
The local dashboard is an actionable workspace for registered Yoke projects. It reads the same saved project, loop, goal, acceptance, and measurement data as the CLI. Project-controlled text is rendered through `textContent`, and the server remains bound to the loopback interface with same-origin authorization for pause requests.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Project status is derived from both the saved goal and the latest loop report. A blocked or running loop is not hidden by a completed goal. When goal and loop states differ, the card displays both. Blocked, failed, paused, unavailable, and stale active reports need attention; those projects are ordered before active projects, followed by the remaining projects.
|
|
8
|
+
|
|
9
|
+
Cards also show the reported current task and saved blocker reason when available, so the overview explains why a project needs attention before opening it.
|
|
10
|
+
|
|
11
|
+
An active loop report more than 20 minutes old is labeled **unconfirmed**. This means Yoke has an old active report, not evidence that the process is still live. The overview can be searched by project name, canonical path, or goal objective and filtered to All, Active, or Needs attention. A no-match state explains the result and provides a clear action that resets both search and filter.
|
|
12
|
+
|
|
13
|
+
## Durable navigation
|
|
14
|
+
|
|
15
|
+
The URL hash stores the current screen, project, project tab, period, UTC grouping, and complete custom date range. Supported screens are the overview, workspace comparison, and project detail. Supported project tabs are Now, Usage & time, and Results; periods are 1, 7, 30, 90, or 365 days; groupings are day, week, or month. Custom dates must be real ISO calendar dates in chronological order and cover at most 366 inclusive days.
|
|
16
|
+
|
|
17
|
+
Invalid hash state returns to the overview with the 30-day/day defaults. Browser back and forward, a page reload, and Refresh restore the validated state. Refresh reloads data without resetting the selected view or controls. Starting any navigation aborts earlier fetches and changes a request generation, so an older response cannot replace the current screen.
|
|
18
|
+
|
|
19
|
+
The workspace comparison schedules at most three project analytics requests at once. If navigation changes, in-flight fetches are aborted and no additional obsolete project requests are scheduled. All projects and individual project links remain available in the navigation while viewing the comparison.
|
|
20
|
+
|
|
21
|
+
## Usage comparisons
|
|
22
|
+
|
|
23
|
+
Usage & time compares the selected period with the immediately preceding period of exactly the same duration. A single time boundary is captured before either analytics request is made. The comparison covers recorded input plus output tokens, recorded acceptance events, and reported cost.
|
|
24
|
+
|
|
25
|
+
When the preceding value is zero, the dashboard describes no change or new recorded activity instead of calculating an infinite percentage. A cost percentage is shown only when both periods have fully measured cost. Otherwise the dashboard says the percentage is unavailable. Current and previous missing-usage coverage is displayed from calls with unknown usage and unmeasured attempts. Recorded portions remain recorded portions; missing tokens or charges are not estimated as zero.
|
|
26
|
+
|
|
27
|
+
Charts and their tables include periods that contain recorded events. Empty UTC calendar buckets are omitted and the chart explains that a gap means no recorded activity, not a measured zero. Detailed provider/model/role, model timeline, task, and phase tables remain below the comparison.
|
|
28
|
+
|
|
29
|
+
## Design findings and limitations
|
|
30
|
+
|
|
31
|
+
Goal state and loop state describe different durable facts and need independent presentation. Freshness is also separate from state: a saved `running` value can become unconfirmed without being rewritten. Navigation state belongs in the URL because the dashboard has several independently useful views and time controls. Analytics fan-out needs cancellation as well as a concurrency bound because registered workspaces may contain many projects.
|
|
32
|
+
|
|
33
|
+
The dashboard combines local saved evidence with process-identity checks for supervised providers (see [Windows runner validation](WINDOWS-RUNNER-VALIDATION.md)). Unverified process identity remains unknown. It does not reconstruct activity that predates retained measurements, estimate missing provider usage or prices, or turn requested model names into proof of models used. Parallel call durations can overlap, so summed call time is consumption intensity rather than generation speed. The 20-minute freshness threshold is a presentation rule, not a process-health guarantee. No provider performance benchmark is implied by these views.
|
package/docs/MIGRATING-TO-1.0.md
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
# Migrating to Yoke 1.0
|
|
2
|
-
|
|
3
|
-
Yoke 1.0 changes unsafe implicit behavior into explicit policy.
|
|
4
|
-
|
|
5
|
-
## Runner permissions
|
|
6
|
-
|
|
7
|
-
The default is `runner.permissions: safe`. Automation that intentionally requires a full
|
|
8
|
-
sandbox bypass must pass `--unsafe` or configure `runner.permissions: unsafe`. Use
|
|
9
|
-
`read-only` for planning and probing.
|
|
10
|
-
|
|
11
|
-
## Reviews
|
|
12
|
-
|
|
13
|
-
Reviewers write `.yoke/review-verdict.json`; Yoke validates and consumes it. The reviewer must
|
|
14
|
-
differ from the implementer unless `--allow-self-review` is explicit. CI can add `--json`.
|
|
15
|
-
|
|
16
|
-
## Commit ownership
|
|
17
|
-
|
|
18
|
-
Yoke resolves identity before implementation. Configure it when Git has no identity:
|
|
19
|
-
|
|
20
|
-
```yaml
|
|
21
|
-
commit:
|
|
22
|
-
authorName: HECer
|
|
23
|
-
authorEmail: hec_er@web.de
|
|
24
|
-
allowCoAuthors: false
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## PRDs, audit, and cleanup
|
|
28
|
-
|
|
29
|
-
Existing PRDs remain valid. Optional `needs`, `area`, and `agent` fields add dependencies,
|
|
30
|
-
collision domains, and affinity. Enable the story audit gate with `audit.enabled: true` and
|
|
31
|
-
version suppressions with `suppressionsVersion: 1`.
|
|
32
|
-
|
|
33
|
-
`yoke loop cleanup` now reports retained worktrees. Add `--remove-worktrees` for deletion.
|
|
1
|
+
# Migrating to Yoke 1.0
|
|
2
|
+
|
|
3
|
+
Yoke 1.0 changes unsafe implicit behavior into explicit policy.
|
|
4
|
+
|
|
5
|
+
## Runner permissions
|
|
6
|
+
|
|
7
|
+
The default is `runner.permissions: safe`. Automation that intentionally requires a full
|
|
8
|
+
sandbox bypass must pass `--unsafe` or configure `runner.permissions: unsafe`. Use
|
|
9
|
+
`read-only` for planning and probing.
|
|
10
|
+
|
|
11
|
+
## Reviews
|
|
12
|
+
|
|
13
|
+
Reviewers write `.yoke/review-verdict.json`; Yoke validates and consumes it. The reviewer must
|
|
14
|
+
differ from the implementer unless `--allow-self-review` is explicit. CI can add `--json`.
|
|
15
|
+
|
|
16
|
+
## Commit ownership
|
|
17
|
+
|
|
18
|
+
Yoke resolves identity before implementation. Configure it when Git has no identity:
|
|
19
|
+
|
|
20
|
+
```yaml
|
|
21
|
+
commit:
|
|
22
|
+
authorName: HECer
|
|
23
|
+
authorEmail: hec_er@web.de
|
|
24
|
+
allowCoAuthors: false
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## PRDs, audit, and cleanup
|
|
28
|
+
|
|
29
|
+
Existing PRDs remain valid. Optional `needs`, `area`, and `agent` fields add dependencies,
|
|
30
|
+
collision domains, and affinity. Enable the story audit gate with `audit.enabled: true` and
|
|
31
|
+
version suppressions with `suppressionsVersion: 1`.
|
|
32
|
+
|
|
33
|
+
`yoke loop cleanup` now reports retained worktrees. Add `--remove-worktrees` for deletion.
|