@hecer/yoke 1.3.0 → 1.5.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 (69) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +33 -0
  4. package/README.md +108 -16
  5. package/TODOS.md +0 -3
  6. package/bench/README.md +55 -46
  7. package/bench/output-compaction.mjs +65 -0
  8. package/canon/loop/loop-spec.md +22 -8
  9. package/canon/manifest.yaml +1 -1
  10. package/dist/agents/contracts.js +50 -0
  11. package/dist/agents/process-incarnation.js +15 -0
  12. package/dist/agents/process-record.js +65 -0
  13. package/dist/agents/process-streams.js +40 -0
  14. package/dist/agents/process.js +177 -0
  15. package/dist/agents/providers.js +10 -7
  16. package/dist/agents/telemetry.js +62 -0
  17. package/dist/audit/command.js +13 -5
  18. package/dist/cli.js +55 -3
  19. package/dist/loop/candidate-boundaries.js +43 -0
  20. package/dist/loop/candidate-cleanup.js +98 -0
  21. package/dist/loop/candidate-contracts.js +1 -0
  22. package/dist/loop/candidate-selection.js +84 -0
  23. package/dist/loop/candidates.js +228 -0
  24. package/dist/loop/claim-lease.js +131 -0
  25. package/dist/loop/claims.js +177 -40
  26. package/dist/loop/cleanup.js +117 -15
  27. package/dist/loop/decision.js +31 -0
  28. package/dist/loop/dispatcher.js +334 -0
  29. package/dist/loop/git.js +6 -4
  30. package/dist/loop/loop.js +109 -16
  31. package/dist/loop/merge-queue.js +12 -6
  32. package/dist/loop/parallel-adapters.js +185 -0
  33. package/dist/loop/parallel-command.js +287 -0
  34. package/dist/loop/parallel.js +2 -4
  35. package/dist/loop/prd.js +4 -1
  36. package/dist/loop/reporter.js +86 -5
  37. package/dist/loop/run-command.js +216 -58
  38. package/dist/loop/runner.js +67 -32
  39. package/dist/loop/verify.js +65 -11
  40. package/dist/loop/watchdog.js +67 -8
  41. package/dist/loop/worker-cancellation.js +17 -0
  42. package/dist/loop/worker-cleanup.js +23 -0
  43. package/dist/loop/worker-contracts.js +1 -0
  44. package/dist/loop/worker.js +254 -0
  45. package/dist/output/artifact.js +63 -0
  46. package/dist/output/compact.js +192 -0
  47. package/dist/output/types.js +4 -0
  48. package/dist/quality/artifacts.js +59 -0
  49. package/dist/quality/candidate-comparison.js +130 -0
  50. package/dist/quality/command.js +316 -0
  51. package/dist/quality/loop.js +86 -0
  52. package/dist/quality/process-command.js +57 -0
  53. package/dist/quality/reference.js +187 -0
  54. package/dist/quality/repair.js +11 -0
  55. package/dist/quality/runner.js +66 -0
  56. package/dist/quality/types.js +60 -0
  57. package/dist/quality/verdict.js +142 -0
  58. package/dist/retrofit/config.js +26 -2
  59. package/dist/retrofit/gitignore.js +4 -0
  60. package/dist/review/command.js +27 -38
  61. package/dist/review/verdict.js +38 -7
  62. package/docs/MIGRATING-TO-1.4.md +70 -0
  63. package/docs/PUBLISHING.md +16 -2
  64. package/docs/superpowers/plans/2026-08-13-gauntlet-quality-loop.md +537 -0
  65. package/docs/superpowers/plans/2026-08-16-artifact-backed-output-compaction.md +329 -0
  66. package/docs/superpowers/specs/2026-08-13-gauntlet-quality-loop-design.md +422 -0
  67. package/docs/superpowers/specs/2026-08-16-artifact-backed-output-compaction-design.md +181 -0
  68. package/gemini-extension.json +1 -1
  69. package/package.json +4 -3
@@ -1,21 +1,49 @@
1
1
  import { existsSync, readFileSync, rmSync } from 'node:fs';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { z } from 'zod';
4
+ import { AgentSchema, PermissionProfileSchema } from '../agents/contracts.js';
4
5
  export const ReviewFindingSchema = z.object({
6
+ id: z.string().min(1).optional(),
5
7
  severity: z.enum(['blocking', 'warning', 'info']),
6
8
  message: z.string().min(1),
7
9
  file: z.string().min(1).optional(),
8
10
  line: z.number().int().positive().optional(),
11
+ actionable: z.boolean().optional(),
12
+ suggestedFix: z.string().min(1).optional(),
13
+ evidence: z.array(z.string().min(1)).optional(),
9
14
  });
10
15
  export const ReviewVerdictSchema = z.object({
16
+ schemaVersion: z.literal(1),
11
17
  approved: z.boolean(),
12
18
  summary: z.string().min(1),
13
19
  findings: z.array(ReviewFindingSchema),
20
+ provenance: z.object({
21
+ provider: AgentSchema,
22
+ model: z.string().min(1),
23
+ role: z.literal('review'),
24
+ promptVersion: z.literal(1),
25
+ permissions: PermissionProfileSchema,
26
+ }),
14
27
  });
28
+ export function parseReviewVerdict(value, expected) {
29
+ const result = ReviewVerdictSchema.safeParse(value);
30
+ if (!result.success)
31
+ throw new Error(`Review verdict is invalid: ${result.error.message}`);
32
+ if (expected && result.data.provenance.provider !== expected.provider)
33
+ throw new Error(`Review verdict provider mismatch: expected ${expected.provider}, received ${result.data.provenance.provider}`);
34
+ if (expected?.model && result.data.provenance.model !== expected.model)
35
+ throw new Error(`Review verdict model mismatch: expected ${expected.model}, received ${result.data.provenance.model}`);
36
+ return result.data;
37
+ }
38
+ export function selectRepairFinding(verdict) {
39
+ if (verdict.approved)
40
+ return null;
41
+ return verdict.findings.find(candidate => candidate.severity === 'blocking' && (candidate.actionable ?? true)) ?? null;
42
+ }
15
43
  export function reviewVerdictPath(targetDir) {
16
44
  return resolve(join(targetDir, '.yoke', 'review-verdict.json'));
17
45
  }
18
- export function readReviewVerdict(path) {
46
+ export function readReviewVerdict(path, expected) {
19
47
  if (!existsSync(path))
20
48
  throw new Error(`Review verdict is missing: ${path}`);
21
49
  try {
@@ -26,20 +54,23 @@ export function readReviewVerdict(path) {
26
54
  catch (error) {
27
55
  throw new Error(`Review verdict is malformed JSON: ${error.message}`);
28
56
  }
29
- const result = ReviewVerdictSchema.safeParse(value);
30
- if (!result.success)
31
- throw new Error(`Review verdict is invalid: ${result.error.message}`);
32
- return result.data;
57
+ return parseReviewVerdict(value, expected);
33
58
  }
34
59
  finally {
35
60
  rmSync(path, { force: true });
36
61
  }
37
62
  }
38
- export function formatReviewContract(path) {
63
+ export function formatReviewStdoutContract(provider) {
64
+ return [
65
+ 'Return exactly one JSON object as your final response. Do not write any file.',
66
+ `{"schemaVersion":1,"approved":boolean,"summary":"non-empty string","findings":[],"provenance":{"provider":"${provider}","model":"provider-reported model","role":"review","promptVersion":1,"permissions":"read-only"}}`,
67
+ ].join('\n');
68
+ }
69
+ export function formatReviewContract(path, provider) {
39
70
  return [
40
71
  `Write your final verdict to this absolute path: ${path}`,
41
72
  'The file must contain exactly one JSON object with this contract:',
42
- '{"approved":boolean,"summary":"non-empty string","findings":[{"severity":"blocking|warning|info","message":"non-empty string","file":"optional path","line":1}]}',
73
+ `{"schemaVersion":1,"approved":boolean,"summary":"non-empty string","findings":[{"id":"optional id","severity":"blocking|warning|info","message":"non-empty string","file":"optional path","line":1,"actionable":true,"suggestedFix":"optional repair","evidence":["optional evidence reference"]}],"provenance":{"provider":"${provider ?? 'claude|codex|gemini'}","model":"provider-reported model","role":"review","promptVersion":1,"permissions":"safe"}}`,
43
74
  'Set approved=false when any blocking finding exists. Create the file even when the process also exits non-zero.',
44
75
  ].join('\n');
45
76
  }
@@ -0,0 +1,70 @@
1
+ # Migrating to Yoke 1.4
2
+
3
+ Yoke 1.4 adds dependency-aware parallel workers, isolated candidate selection, and a reference-driven quality gauntlet. Existing projects remain serial and skip quality comparison unless you opt in.
4
+
5
+ Upgrade and refresh generated harness files:
6
+
7
+ ```bash
8
+ npm install -g @hecer/yoke@1.4.0
9
+ yoke retrofit .
10
+ ```
11
+
12
+ ## Parallel execution
13
+
14
+ Run dependency-ready stories concurrently with an explicit worker limit:
15
+
16
+ ```bash
17
+ yoke loop run . --isolate --parallel=3
18
+ ```
19
+
20
+ Parallel runs use isolated worktrees, leased claims, and a FIFO integration queue. Every candidate must pass its worker gates and the merged result must pass the project gates again. Adaptive routing is disabled during parallel and multi-candidate runs so each worker has one auditable provider identity.
21
+
22
+ Use `--candidates=N` to produce and mechanically gate multiple implementations before an identity-blind comparison selects the winner. Candidate mode supports up to five candidates.
23
+
24
+ ## Reference-driven quality
25
+
26
+ Quality remains disabled by default. A story must declare a trusted reference, candidate artifacts, and a rubric:
27
+
28
+ ```yaml
29
+ quality:
30
+ reference: { name: approved-home, source: design/home.png, kind: file }
31
+ candidate: { kind: screenshots, paths: [.yoke/proof/STORY-1/home.png] }
32
+ rubric: Match the approved layout, hierarchy, spacing, and states.
33
+ policy: blocking
34
+ ```
35
+
36
+ Project defaults can bound critic and repair work:
37
+
38
+ ```yaml
39
+ quality:
40
+ enabled: false
41
+ policy: blocking
42
+ maxRounds: 3
43
+ maxMinutes: 60
44
+ consistencyChecks: 2
45
+ maxParallelCandidates: 2
46
+ critic: { agent: codex, model: gpt-5.6-sol } # model required for --candidates
47
+ repair: { agent: claude }
48
+ ```
49
+
50
+ Enable it for one run with:
51
+
52
+ ```bash
53
+ yoke loop run . --quality
54
+ ```
55
+
56
+ `consistencyChecks` is fixed at `2`: Yoke runs the swapped-label pair needed to detect identity-sensitive critic output. Advisory mode records both verdicts without blocking; blocking mode permits bounded repairs and reruns all mechanical gates.
57
+
58
+ ## Cleanup behavior
59
+
60
+ `yoke loop cleanup` now retains Yoke-created worktrees by default so failed candidates remain inspectable. Remove them explicitly when no longer needed:
61
+
62
+ ```bash
63
+ yoke loop cleanup . --remove-worktrees
64
+ ```
65
+
66
+ Cleanup still reaps only provider processes recorded for the current project and removes stale loop locks. It never kills providers by machine-wide process name.
67
+
68
+ ## Review verdicts
69
+
70
+ Review verdict files now require `schemaVersion: 1` and provenance containing the provider, provider-reported model, review role, prompt version, and permission profile. Custom reviewer integrations must emit the contract printed in the reviewer prompt. Legacy verdict files without this envelope fail closed.
@@ -1,6 +1,6 @@
1
1
  # Publishing channels — status & playbook
2
2
 
3
- Where Yoke is published, and how each channel gets updated. (Reviewed 2026-07-30.)
3
+ Where Yoke is published, and how each channel gets updated. (Reviewed 2026-08-15.)
4
4
 
5
5
  ## Live
6
6
 
@@ -14,12 +14,26 @@ Where Yoke is published, and how each channel gets updated. (Reviewed 2026-07-30
14
14
 
15
15
  ## GitHub release (required, not just a tag)
16
16
 
17
+ Before the release commit, update every user-facing version and README statistic, then require the
18
+ same checks npm will run:
19
+
20
+ ```bash
21
+ npm run docs:update
22
+ npm run docs:check
23
+ npm run prepublishOnly
24
+ ```
25
+
26
+ `docs:update` synchronizes the README's package version, test count, skill count, and supported
27
+ agents from `package.json`, Vitest discovery, and `canon/manifest.yaml`. The version must also be
28
+ kept in sync in `package-lock.json`, `canon/manifest.yaml`, `.claude-plugin/plugin.json`,
29
+ `.codex-plugin/plugin.json`, and `gemini-extension.json`.
30
+
17
31
  A pushed tag appears under **Tags**, but GitHub only shows an entry under **Releases** after a
18
32
  release object is created. Use this idempotent check after the version commit reaches `main`:
19
33
 
20
34
  ```bash
21
35
  set -euo pipefail
22
- VERSION=1.1.0
36
+ VERSION=1.5.0
23
37
  TARGET=$(git rev-parse HEAD)
24
38
  git fetch --tags origin
25
39