cohorte 2.10.1 → 3.0.0-dev.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 (211) hide show
  1. package/README.md +7 -0
  2. package/assets/manifest.json +193 -0
  3. package/assets/migrations/config/README.md +12 -0
  4. package/assets/migrations/state/0001_init.sql +154 -0
  5. package/assets/prompts/README.md +11 -0
  6. package/assets/prompts/agents/fixer.md +7 -0
  7. package/assets/prompts/agents/implementer.md +12 -0
  8. package/assets/prompts/agents/reviewer.md +8 -0
  9. package/assets/prompts/agents/security-reviewer.md +7 -0
  10. package/assets/prompts/phases/build.md +5 -0
  11. package/assets/prompts/phases/review.md +5 -0
  12. package/assets/prompts/phases/test.md +5 -0
  13. package/assets/prompts/system.md +11 -0
  14. package/assets/schemas/agent-output.schema.json +225 -0
  15. package/assets/schemas/auth-status.schema.json +87 -0
  16. package/assets/schemas/command-result.schema.json +192 -0
  17. package/assets/schemas/commands.schema.json +952 -0
  18. package/assets/schemas/config.schema.json +2259 -0
  19. package/assets/schemas/doctor-report.schema.json +104 -0
  20. package/assets/schemas/events.schema.json +8514 -0
  21. package/assets/schemas/fake-script.schema.json +549 -0
  22. package/assets/schemas/inspect.schema.json +3133 -0
  23. package/assets/schemas/manifest.schema.json +79 -0
  24. package/assets/schemas/ownership.schema.json +52 -0
  25. package/assets/schemas/policy-verdict.schema.json +282 -0
  26. package/assets/schemas/project-model.schema.json +783 -0
  27. package/assets/schemas/project-status.schema.json +217 -0
  28. package/assets/schemas/reconcile-plan.schema.json +155 -0
  29. package/assets/schemas/run-diff.schema.json +177 -0
  30. package/assets/schemas/run-snapshot-manifest.schema.json +2104 -0
  31. package/assets/schemas/run-state.schema.json +1755 -0
  32. package/assets/schemas/runtime-capabilities.schema.json +1545 -0
  33. package/assets/schemas/sandbox-capabilities.schema.json +116 -0
  34. package/assets/schemas/skill.schema.json +87 -0
  35. package/assets/schemas/spec.schema.json +202 -0
  36. package/assets/schemas/tool-catalogue.schema.json +655 -0
  37. package/assets/schemas/trust-record.schema.json +38 -0
  38. package/assets/skills/README.md +9 -0
  39. package/assets/skills/inspect-surface.md +4 -0
  40. package/dist/agent-host.mjs +50 -0
  41. package/dist/agent-host.mjs.map +1 -0
  42. package/dist/bundle-manifest.json +102 -0
  43. package/dist/chunks/__host-DgGPyco9.mjs +18 -0
  44. package/dist/chunks/__host-DgGPyco9.mjs.map +1 -0
  45. package/dist/chunks/approve-Cv135UqI.mjs +19 -0
  46. package/dist/chunks/approve-Cv135UqI.mjs.map +1 -0
  47. package/dist/chunks/auth-BT5oVp76.mjs +132 -0
  48. package/dist/chunks/auth-BT5oVp76.mjs.map +1 -0
  49. package/dist/chunks/auth-D8rCVY33.mjs +84 -0
  50. package/dist/chunks/auth-D8rCVY33.mjs.map +1 -0
  51. package/dist/chunks/brainstorm-CU10T1tm.mjs +7 -0
  52. package/dist/chunks/brainstorm-CU10T1tm.mjs.map +1 -0
  53. package/dist/chunks/cancel-KV_cpsqS.mjs +15 -0
  54. package/dist/chunks/cancel-KV_cpsqS.mjs.map +1 -0
  55. package/dist/chunks/canonical-C8UxeEmB.mjs +57 -0
  56. package/dist/chunks/canonical-C8UxeEmB.mjs.map +1 -0
  57. package/dist/chunks/catalogue-BvwSrM_j.mjs +1549 -0
  58. package/dist/chunks/catalogue-BvwSrM_j.mjs.map +1 -0
  59. package/dist/chunks/compose-Cr0hP0qm.mjs +7303 -0
  60. package/dist/chunks/compose-Cr0hP0qm.mjs.map +1 -0
  61. package/dist/chunks/config-DefHA2Lw.mjs +96 -0
  62. package/dist/chunks/config-DefHA2Lw.mjs.map +1 -0
  63. package/dist/chunks/contract-B0ra2-bm.mjs +295 -0
  64. package/dist/chunks/contract-B0ra2-bm.mjs.map +1 -0
  65. package/dist/chunks/contract-vYbwYr0Y.mjs +123 -0
  66. package/dist/chunks/contract-vYbwYr0Y.mjs.map +1 -0
  67. package/dist/chunks/deny-ClTxSvHJ.mjs +15 -0
  68. package/dist/chunks/deny-ClTxSvHJ.mjs.map +1 -0
  69. package/dist/chunks/diff-Qw0II0sj.mjs +101 -0
  70. package/dist/chunks/diff-Qw0II0sj.mjs.map +1 -0
  71. package/dist/chunks/discover-30Ciya_0.mjs +20 -0
  72. package/dist/chunks/discover-30Ciya_0.mjs.map +1 -0
  73. package/dist/chunks/doctor-DfCrKpfT.mjs +222 -0
  74. package/dist/chunks/doctor-DfCrKpfT.mjs.map +1 -0
  75. package/dist/chunks/fix-D4Tj8RVA.mjs +18 -0
  76. package/dist/chunks/fix-D4Tj8RVA.mjs.map +1 -0
  77. package/dist/chunks/gc-BBXlBF5w.mjs +161 -0
  78. package/dist/chunks/gc-BBXlBF5w.mjs.map +1 -0
  79. package/dist/chunks/init-Okz4mlhW.mjs +30 -0
  80. package/dist/chunks/init-Okz4mlhW.mjs.map +1 -0
  81. package/dist/chunks/inspect-Buby4cQD.mjs +22 -0
  82. package/dist/chunks/inspect-Buby4cQD.mjs.map +1 -0
  83. package/dist/chunks/logs-Giq5sUau.mjs +25 -0
  84. package/dist/chunks/logs-Giq5sUau.mjs.map +1 -0
  85. package/dist/chunks/migrate-DRoFUg53.mjs +19 -0
  86. package/dist/chunks/migrate-DRoFUg53.mjs.map +1 -0
  87. package/dist/chunks/models-Btp6X2YZ.mjs +24 -0
  88. package/dist/chunks/models-Btp6X2YZ.mjs.map +1 -0
  89. package/dist/chunks/pause-BASBfKLO.mjs +16 -0
  90. package/dist/chunks/pause-BASBfKLO.mjs.map +1 -0
  91. package/dist/chunks/policy-DUIgzF3W.mjs +139 -0
  92. package/dist/chunks/policy-DUIgzF3W.mjs.map +1 -0
  93. package/dist/chunks/providers-Dx78imKj.mjs +19 -0
  94. package/dist/chunks/providers-Dx78imKj.mjs.map +1 -0
  95. package/dist/chunks/reconcile-BfD785gd.mjs +26 -0
  96. package/dist/chunks/reconcile-BfD785gd.mjs.map +1 -0
  97. package/dist/chunks/render-DyZr5Lgi.mjs +53 -0
  98. package/dist/chunks/render-DyZr5Lgi.mjs.map +1 -0
  99. package/dist/chunks/resume-GJQRb2Eb.mjs +15 -0
  100. package/dist/chunks/resume-GJQRb2Eb.mjs.map +1 -0
  101. package/dist/chunks/retry-CLrUem1H.mjs +15 -0
  102. package/dist/chunks/retry-CLrUem1H.mjs.map +1 -0
  103. package/dist/chunks/review-BDdjb6kB.mjs +17 -0
  104. package/dist/chunks/review-BDdjb6kB.mjs.map +1 -0
  105. package/dist/chunks/run-CgTWYlcd.mjs +82 -0
  106. package/dist/chunks/run-CgTWYlcd.mjs.map +1 -0
  107. package/dist/chunks/run-tool-C3rAv4XF.mjs +26 -0
  108. package/dist/chunks/run-tool-C3rAv4XF.mjs.map +1 -0
  109. package/dist/chunks/schema-C9QbUW4H.mjs +578 -0
  110. package/dist/chunks/schema-C9QbUW4H.mjs.map +1 -0
  111. package/dist/chunks/send-BdnJ85Nz.mjs +19 -0
  112. package/dist/chunks/send-BdnJ85Nz.mjs.map +1 -0
  113. package/dist/chunks/ship-DTuLGoaJ.mjs +19 -0
  114. package/dist/chunks/ship-DTuLGoaJ.mjs.map +1 -0
  115. package/dist/chunks/shutdown-BM92Iyv2.mjs +15 -0
  116. package/dist/chunks/shutdown-BM92Iyv2.mjs.map +1 -0
  117. package/dist/chunks/skip-DZG5kpcH.mjs +18 -0
  118. package/dist/chunks/skip-DZG5kpcH.mjs.map +1 -0
  119. package/dist/chunks/spec-Bq0P3jVh.mjs +20 -0
  120. package/dist/chunks/spec-Bq0P3jVh.mjs.map +1 -0
  121. package/dist/chunks/src-CId6kMsY.mjs +1689 -0
  122. package/dist/chunks/src-CId6kMsY.mjs.map +1 -0
  123. package/dist/chunks/src-CfAOde7C.mjs +5403 -0
  124. package/dist/chunks/src-CfAOde7C.mjs.map +1 -0
  125. package/dist/chunks/src-Cu3TxNob.mjs +783 -0
  126. package/dist/chunks/src-Cu3TxNob.mjs.map +1 -0
  127. package/dist/chunks/src-DcU4IXMe.mjs +117 -0
  128. package/dist/chunks/src-DcU4IXMe.mjs.map +1 -0
  129. package/dist/chunks/src-JxawBQBn.mjs +446 -0
  130. package/dist/chunks/src-JxawBQBn.mjs.map +1 -0
  131. package/dist/chunks/src-s7YRvk0t.mjs +2116 -0
  132. package/dist/chunks/src-s7YRvk0t.mjs.map +1 -0
  133. package/dist/chunks/status-SMjiOMYy.mjs +29 -0
  134. package/dist/chunks/status-SMjiOMYy.mjs.map +1 -0
  135. package/dist/chunks/tail-BuhT-2PH.mjs +25 -0
  136. package/dist/chunks/tail-BuhT-2PH.mjs.map +1 -0
  137. package/dist/chunks/update-BXIppsbi.mjs +16 -0
  138. package/dist/chunks/update-BXIppsbi.mjs.map +1 -0
  139. package/dist/cli.mjs +175 -0
  140. package/dist/cli.mjs.map +1 -0
  141. package/package.json +17 -32
  142. package/CHANGELOG.md +0 -1461
  143. package/bin/cli.js +0 -817
  144. package/bin/report.js +0 -141
  145. package/core/adapter/render.js +0 -415
  146. package/core/agents/implementer.template.md +0 -142
  147. package/core/agents/profile-reader.md +0 -50
  148. package/core/agents/release.md +0 -58
  149. package/core/agents/review.md +0 -179
  150. package/core/commands/cohorte-align-ds.md +0 -33
  151. package/core/commands/cohorte-audit.md +0 -58
  152. package/core/commands/cohorte-brainstorm.md +0 -70
  153. package/core/commands/cohorte-build.md +0 -219
  154. package/core/commands/cohorte-doctor.md +0 -194
  155. package/core/commands/cohorte-fix.md +0 -80
  156. package/core/commands/cohorte-fleet.md +0 -103
  157. package/core/commands/cohorte-init-pipeline.md +0 -31
  158. package/core/commands/cohorte-intake.md +0 -92
  159. package/core/commands/cohorte-patch.md +0 -118
  160. package/core/commands/cohorte-refactor.md +0 -59
  161. package/core/commands/cohorte-retro.md +0 -85
  162. package/core/commands/cohorte-review.md +0 -270
  163. package/core/commands/cohorte-ship.md +0 -122
  164. package/core/commands/cohorte-spec.md +0 -101
  165. package/core/commands/cohorte-update-pipeline.md +0 -165
  166. package/core/hooks/gate.py +0 -588
  167. package/core/runtimes/claude.json +0 -74
  168. package/core/runtimes/codex.json +0 -85
  169. package/core/runtimes/cursor.json +0 -76
  170. package/core/runtimes/gemini.json +0 -76
  171. package/core/runtimes/opencode.json +0 -73
  172. package/core/templates/agent-handoff.md +0 -34
  173. package/core/templates/brainstorm-return.md +0 -36
  174. package/core/templates/decisions.template.md +0 -42
  175. package/core/templates/design-brief.md +0 -44
  176. package/core/templates/patch.template.md +0 -86
  177. package/core/templates/pr-body.md +0 -25
  178. package/core/templates/review-feedback.md +0 -38
  179. package/core/templates/spec.template.md +0 -87
  180. package/core/templates/steps/init-pipeline/01-detect-stack.md +0 -44
  181. package/core/templates/steps/init-pipeline/02-interview-gaps.md +0 -55
  182. package/core/templates/steps/init-pipeline/03-draft-profile.md +0 -10
  183. package/core/templates/steps/init-pipeline/04-write-render.md +0 -145
  184. package/core/templates/steps/init-pipeline/05-report.md +0 -12
  185. package/core/workflows/audit.js +0 -240
  186. package/core/workflows/loop.js +0 -641
  187. package/core/workflows/refactor.js +0 -276
  188. package/core/workflows/review.js +0 -456
  189. package/install.ps1 +0 -103
  190. package/install.sh +0 -114
  191. package/lib/doctor.js +0 -534
  192. package/lib/runtime.js +0 -139
  193. package/lib/versions.js +0 -149
  194. package/lib/yaml.js +0 -129
  195. package/profile/PIPELINE.template.md +0 -259
  196. package/profile/SCHEMA.md +0 -735
  197. package/profile/cohorte.config.template.yaml +0 -43
  198. package/scripts/assert-gate-hook.mjs +0 -75
  199. package/scripts/kanban-move.sh +0 -313
  200. package/scripts/metrics/collect.mjs +0 -531
  201. package/scripts/metrics/prices.json +0 -42
  202. package/scripts/new-feature.sh.template +0 -98
  203. package/scripts/preflight.sh +0 -124
  204. package/scripts/remove-feature.sh.template +0 -56
  205. package/scripts/test-adapter.mjs +0 -437
  206. package/scripts/test-gate.mjs +0 -443
  207. package/scripts/test-kanban.mjs +0 -304
  208. package/scripts/test-lib.mjs +0 -356
  209. package/scripts/test-metrics.mjs +0 -156
  210. package/scripts/test-workflows.mjs +0 -758
  211. package/scripts/validate-core.mjs +0 -325
@@ -1,276 +0,0 @@
1
- // cohorte — /cohorte-refactor as a deterministic workflow (opt-in; the conversational
2
- // /cohorte-refactor command remains the default path and the fallback).
3
- //
4
- // BIG domains only: a domain with just a handful of open backlog items is
5
- // cheaper through the conversational /cohorte-refactor — this script skips it and says
6
- // so. Invoke with args = {domains: ["backend", …]} or {domains: "all"}.
7
- //
8
- // Shape (SCHEMA.md §Workflows): profile via profile-reader (phase 0), the open
9
- // backlog read once, the `shared` domain (contract package — every slice
10
- // imports it) refactored FIRST and alone, then the other domains' surface
11
- // implementers in parallel (their trees are disjoint by construction), each
12
- // verified per-domain with one bounded retry round, and the backlog ticked.
13
-
14
- export const meta = {
15
- name: 'cohorte-refactor',
16
- description: 'Apply the /cohorte-audit refactor backlog for big domains: shared first, then parallel surface implementers, per-domain verify + one retry',
17
- whenToUse: 'Only when the human explicitly asks for the refactor workflow on big domains. args = {domains: ["<surface key>", …] | "all"}.',
18
- phases: [
19
- { title: 'Profile', detail: 'PIPELINE.md → JSON via profile-reader', model: 'haiku' },
20
- { title: 'Backlog', detail: 'read the open items per requested domain', model: 'haiku' },
21
- { title: 'Shared', detail: 'refactor the contract package first, alone' },
22
- { title: 'Refactor', detail: 'one surface implementer per domain, parallel' },
23
- { title: 'Verify', detail: 'per-domain gates + item check, one retry round', model: 'haiku' },
24
- { title: 'Tick', detail: 'check cleared items off specs/refactor-backlog.md', model: 'haiku' },
25
- ],
26
- }
27
-
28
- // A domain below this many open items is not "big" — the conversational
29
- // /cohorte-refactor handles it with less overhead than a workflow run.
30
- const MIN_ITEMS = 5
31
-
32
- // The Workflow runtime hands `args` to a script verbatim, so a caller that passes a
33
- // JSON-ENCODED STRING instead of a real object gets that string back here. Parse it
34
- // back into the object it was meant to be. A bare slug is shorthand for the DOMAIN in
35
- // this script — mapping it to {feature, target} (review.js's keys, once copy-pasted
36
- // here) left ARGS.domains undefined, which fell through to 'all': the shorthand
37
- // "backend" dispatched code-editing implementers on EVERY big domain, not the one
38
- // the caller named.
39
- const ARGS = (() => {
40
- if (typeof args === 'string') {
41
- const t = args.trim()
42
- if (t.startsWith('{')) {
43
- try { const o = JSON.parse(t); if (o && typeof o === 'object' && !Array.isArray(o)) return o } catch {}
44
- }
45
- return { domains: [t] }
46
- }
47
- return args && typeof args === 'object' ? args : {}
48
- })()
49
-
50
- const wanted = (() => {
51
- const d = ARGS.domains
52
- if (!d || d === 'all') return 'all'
53
- return Array.isArray(d) ? d : [String(d)]
54
- })()
55
-
56
- // The profile-reader returns through a StructuredOutput tool call, and a haiku agent
57
- // intermittently nests the whole profile as a JSON *string* under a single wrapper field
58
- // ({"output": "{\"surfaces\": …}"}) instead of putting the profile's keys at the top level.
59
- // The schema here used to be {type:'object', additionalProperties:true} — no declared
60
- // properties, no required keys — so that wrapper validated cleanly and every field then read
61
- // as undefined: `surfaces` fell back to [], parallel([]) dispatched zero agents, the
62
- // dead-agent guard had no surfaces to find missing, and the run reported a verdict having
63
- // done nothing. On the surface it is indistinguishable from a clean run with an empty diff.
64
- // Declaring the shape gives the tool layer something to validate and the agent something to
65
- // aim at; unwrapProfile() salvages a wrapped return that still gets through; and the
66
- // zero-surface abort below makes the silent-success path impossible either way.
67
- // See also the structured-output section of core/agents/profile-reader.md.
68
- const PROFILE = {
69
- type: 'object', additionalProperties: true,
70
- properties: {
71
- error: { type: 'string', description: 'set ONLY when PIPELINE.md is missing or unparseable' },
72
- surfaces: {
73
- type: 'array',
74
- description: "one entry per surface, at the TOP LEVEL of this object — never a JSON string",
75
- items: {
76
- type: 'object', required: ['key'], additionalProperties: true,
77
- properties: { key: { type: 'string' }, path: { type: 'string' }, agent: { type: 'string' } },
78
- },
79
- },
80
- },
81
- }
82
-
83
- // Salvage a profile handed back as JSON text rather than as an object — either the whole
84
- // return, or nested under a single wrapper field. Anything already shaped like a profile
85
- // (has `surfaces`, or is the documented `{error}` failure shape) passes through untouched.
86
- const unwrapProfile = p => {
87
- if (typeof p === 'string') { try { return JSON.parse(p) } catch { return null } }
88
- if (!p || typeof p !== 'object') return null
89
- if (Array.isArray(p.surfaces) || p.error) return p
90
- for (const v of Object.values(p)) {
91
- if (typeof v !== 'string') continue
92
- try {
93
- const inner = JSON.parse(v)
94
- if (inner && typeof inner === 'object' && !Array.isArray(inner)) return inner
95
- } catch {}
96
- }
97
- return p
98
- }
99
-
100
- const OPEN = {
101
- type: 'object', required: ['domains'], additionalProperties: false,
102
- properties: {
103
- domains: {
104
- type: 'array',
105
- items: {
106
- type: 'object', required: ['key', 'items'], additionalProperties: false,
107
- properties: {
108
- key: { type: 'string' },
109
- items: { type: 'array', items: { type: 'string', description: 'the open `- [ ] …` line verbatim' } },
110
- },
111
- },
112
- },
113
- },
114
- }
115
-
116
- const VERIFY = {
117
- type: 'object', required: ['cleared', 'remaining', 'gatesGreen'], additionalProperties: false,
118
- properties: {
119
- cleared: { type: 'array', items: { type: 'string' } },
120
- remaining: { type: 'array', items: { type: 'string' } },
121
- gatesGreen: { type: 'boolean' },
122
- failures: { type: 'string', description: 'one line per gate failure, no output dumps' },
123
- },
124
- }
125
-
126
- // ── Phase 0 — profile ────────────────────────────────────────────────────────
127
- phase('Profile')
128
- const profile = unwrapProfile(await agent(
129
- 'Return this project\'s PIPELINE.md `yaml pipeline-profile` block as JSON, per your instructions.',
130
- { agentType: 'profile-reader', label: 'profile', schema: PROFILE, effort: 'low' },
131
- ))
132
- if (!profile || profile.error) {
133
- return { error: `profile unreadable: ${(profile && profile.error) || 'profile-reader returned nothing'}` }
134
- }
135
- const surfaces = Array.isArray(profile.surfaces) ? profile.surfaces : []
136
- // A profile with no surfaces cannot do this workflow's work, and every later
137
- // guard compares against `surfaces` — an empty list makes them all vacuously
138
- // pass. Fail loudly here instead of finishing with nothing done.
139
- if (!surfaces.length) {
140
- return { error: 'profile has no surfaces — nothing would be refactored. the `yaml pipeline-profile` block in PIPELINE.md is empty or unparseable, or the profile-reader mis-returned; run /cohorte-doctor' }
141
- }
142
- const byKey = Object.fromEntries(surfaces.map(s => [s.key, s]))
143
- const contractPath = (profile.contract && profile.contract.path) || ''
144
- const base = (profile.vcs && profile.vcs.default_branch) || 'main'
145
- const quiet = s => (s.test_quiet_cmd && !String(s.test_quiet_cmd).startsWith('<'))
146
- ? s.test_quiet_cmd : s.test_cmd ? `${s.test_cmd} 2>&1 | tail -40` : ''
147
-
148
- // ── Phase 1 — read the open backlog ──────────────────────────────────────────
149
- phase('Backlog')
150
- const backlog = await agent(
151
- 'Read specs/refactor-backlog.md and return, per `## <domain>` heading, the OPEN `- [ ] …` item lines ' +
152
- 'verbatim (skip checked `- [x]` ones). File missing ⇒ return an empty domains array. ' +
153
- `Requested domains: ${wanted === 'all' ? 'all' : wanted.join(', ')} — return only those (all ⇒ every domain with open items).`,
154
- { model: 'haiku', label: 'read-backlog', schema: OPEN, effort: 'low' },
155
- )
156
- // A dead reader is not "the backlog is empty" — reporting it as such sends the
157
- // human to re-run /cohorte-audit on a backlog that is already there.
158
- if (!backlog) return { error: 'the backlog-reading agent died — nothing was read; re-run the refactor workflow' }
159
- const open = (backlog.domains || []).filter(d => d.items.length)
160
- if (!open.length) return { error: 'no open backlog items for the requested domains — run /cohorte-audit (or the audit workflow) first' }
161
-
162
- const big = open.filter(d => d.items.length >= MIN_ITEMS)
163
- const small = open.filter(d => d.items.length < MIN_ITEMS)
164
- for (const d of small) log(`Skipping ${d.key} (${d.items.length} open item(s) < ${MIN_ITEMS}) — use the conversational /cohorte-refactor ${d.key}, it's cheaper`)
165
- if (!big.length) return { skipped: Object.fromEntries(small.map(d => [d.key, d.items.length])), reason: `every requested domain is below the ${MIN_ITEMS}-item workflow threshold — use /cohorte-refactor` }
166
-
167
- const implementPrompt = d =>
168
- 'Refactor pass on your surface (no feature spec). Read PIPELINE.md first. Add the missing tests ' +
169
- 'FIRST (pin current behavior / cover the entry points), watch them pass, THEN refactor to clear each ' +
170
- 'item. Preserve current public behavior unless an item marks it a bug. Migrations stay additive. ' +
171
- `Need the current state of your tree? Compute it yourself: git diff ${base} -- <your surface path>. ` +
172
- 'Lint + format before handoff; return the handoff in the format your agent instructions define. ' +
173
- 'Backlog items for YOUR surface (self-contained — clear exactly these, reading only the files they name):\n' +
174
- d.items.join('\n')
175
-
176
- const verifyDomain = async (d, implHandoff) => {
177
- if (implHandoff == null) return { key: d.key, cleared: [], remaining: d.items, gatesGreen: false, failures: 'implementer died' }
178
- const s = byKey[d.key]
179
- const gateCmds = s ? [quiet(s), s.lint_quiet_cmd || (s.lint_cmd ? `${s.lint_cmd} 2>&1 | tail -40` : ''), s.typecheck_cmd]
180
- .filter(c => c && !String(c).startsWith('<')) : []
181
- let v = await agent(
182
- `Verify a cohorte refactor round for domain ${d.key}. 1. Run these gates, each redirected to ` +
183
- `specs/reports/refactor-verify.${d.key}.txt (append) — never print their output: ` +
184
- `${gateCmds.map(c => JSON.stringify(c)).join(' · ') || '(none declared — skip gates, gatesGreen=true)'}. ` +
185
- '2. For EACH backlog item below, open its file:line and check the prescribed fix actually landed. ' +
186
- 'Return the item lines split into cleared / remaining (verbatim), gatesGreen, and one line per gate failure.\n' +
187
- 'Items:\n' + d.items.join('\n'),
188
- { model: 'haiku', label: `verify:${d.key}`, phase: 'Verify', schema: VERIFY, effort: 'low' },
189
- )
190
- // One bounded retry: re-dispatch the implementer on what verification rejected.
191
- // The re-verify covers ONLY the retried items, so round 1's cleared list is carried
192
- // forward — overwriting it un-ticked every item the first pass verified, and the
193
- // next /cohorte-refactor re-dispatched finished work.
194
- if (v && (v.remaining.length || !v.gatesGreen) && byKey[d.key]) {
195
- const cleared1 = v.cleared || []
196
- // Never retry items round 1 already verified cleared: on a gates-red round with
197
- // nothing remaining, retrying ALL items put the same lines in both `cleared` and
198
- // `remaining` when the re-verifier died — ticked off the backlog AND reported open.
199
- const retryItems = v.remaining.length ? v.remaining : d.items.filter(i => !cleared1.includes(i))
200
- log(`${d.key}: ${v.remaining.length} item(s) remaining${v.gatesGreen ? '' : ' + red gates'} — one retry round`)
201
- await agent(
202
- implementPrompt({ key: d.key, items: retryItems }) + (v.failures ? `\nGate failures to clear too:\n${v.failures}` : ''),
203
- { agentType: byKey[d.key].agent, label: `retry:${d.key}`, phase: 'Refactor' },
204
- )
205
- const v2 = await agent(
206
- `Re-verify domain ${d.key} after a retry round — same procedure as before (gates redirected to ` +
207
- `specs/reports/refactor-verify.${d.key}.txt, per-item file:line check, verbatim cleared/remaining lines).\n` +
208
- 'Items:\n' + retryItems.join('\n'),
209
- { model: 'haiku', label: `reverify:${d.key}`, phase: 'Verify', schema: VERIFY, effort: 'low' },
210
- )
211
- // A dead re-verifier loses only the RETRY round's claim — round 1's verified
212
- // clears stay cleared; the retried items stay open (unverified ≠ cleared).
213
- v = v2
214
- ? { ...v2, cleared: [...new Set(cleared1.concat(v2.cleared || []))] }
215
- : { cleared: cleared1, remaining: retryItems, gatesGreen: false, failures: 'verifier died on the retry round' }
216
- }
217
- return { key: d.key, ...(v || { cleared: [], remaining: d.items, gatesGreen: false, failures: 'verifier died' }) }
218
- }
219
-
220
- // ── Phase 2 — `shared` first, alone (every slice imports the contract pkg) ───
221
- const results = []
222
- const shared = big.find(d => d.key === 'shared')
223
- if (shared) {
224
- phase('Shared')
225
- const handoff = await agent(
226
- `Refactor the shared contract package (${contractPath || 'the tree outside every surface'}) of this ` +
227
- 'cohorte project — normally lead-owned, so: additive changes only, never break a shape a surface ' +
228
- 'imports (grep consumers before changing any export), migrations stay additive, lint + format before ' +
229
- 'handoff. Clear exactly these open backlog items (self-contained; read only the files they name):\n' +
230
- shared.items.join('\n'),
231
- { label: 'refactor:shared', phase: 'Shared' },
232
- )
233
- results.push(await verifyDomain(shared, handoff))
234
- }
235
-
236
- // ── Phases 3+4 — the other domains in parallel, each verified as it lands ────
237
- const rest = big.filter(d => d.key !== 'shared' && byKey[d.key])
238
- for (const d of big) {
239
- if (d.key !== 'shared' && !byKey[d.key]) log(`Skipping ${d.key} — no matching surface in PIPELINE.md`)
240
- }
241
- const restResults = await pipeline(
242
- rest,
243
- d => agent(implementPrompt(d), { agentType: byKey[d.key].agent, label: `refactor:${d.key}`, phase: 'Refactor' }),
244
- (handoff, d) => verifyDomain(d, handoff),
245
- )
246
- results.push(...restResults.filter(Boolean))
247
-
248
- // ── Phase 5 — tick the cleared items ─────────────────────────────────────────
249
- phase('Tick')
250
- const clearedAll = results.flatMap(r => r.cleared)
251
- let tickedOk = true
252
- if (clearedAll.length) {
253
- const ticked = await agent(
254
- 'In specs/refactor-backlog.md flip EXACTLY these open `- [ ]` item lines to `- [x]` (match verbatim, ' +
255
- 'leave every other line untouched), then return the single word done:\n' + clearedAll.join('\n'),
256
- { model: 'haiku', label: 'tick-backlog', effort: 'low' },
257
- )
258
- // Reporting items as cleared while the backlog still shows them open means the
259
- // next /cohorte-refactor re-dispatches work that is already done.
260
- tickedOk = ticked != null && /done/i.test(String(ticked))
261
- }
262
-
263
- return {
264
- domains: Object.fromEntries(results.map(r => [r.key, {
265
- cleared: r.cleared.length, remaining: r.remaining.length, gatesGreen: r.gatesGreen,
266
- }])),
267
- skippedSmall: Object.fromEntries(small.map(d => [d.key, d.items.length])),
268
- stillOpen: results.flatMap(r => r.remaining.map(line => `[${r.key}] ${line}`)).slice(0, 15),
269
- backlogTicked: tickedOk,
270
- next: !tickedOk
271
- ? `${clearedAll.length} item(s) were cleared in code but NOT ticked off specs/refactor-backlog.md ` +
272
- '(the ticking agent died) — tick them by hand, or the next /cohorte-refactor re-dispatches finished work'
273
- : results.some(r => r.remaining.length || !r.gatesGreen)
274
- ? 'items remain — finish them with the conversational /cohorte-refactor <domain>'
275
- : 'all dispatched domains clean — optionally close with one final /cohorte-audit',
276
- }