cohorte 1.3.3 → 1.4.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/CHANGELOG.md +133 -0
- package/README.md +4 -7
- package/bin/cli.js +49 -4
- package/core/agents/implementer.template.md +10 -5
- package/core/agents/profile-reader.md +22 -0
- package/core/commands/doctor.md +8 -4
- package/core/hooks/gate.py +21 -6
- package/core/templates/agent-handoff.md +7 -2
- package/core/templates/review-feedback.md +7 -4
- package/core/templates/spec.template.md +5 -2
- package/core/templates/steps/init-pipeline/04-write-render.md +2 -1
- package/core/workflows/audit.js +88 -7
- package/core/workflows/refactor.js +85 -9
- package/core/workflows/review.js +132 -11
- package/dashboard/README.md +22 -5
- package/dashboard/dist/assets/index-AFQnlfjO.css +1 -0
- package/dashboard/dist/assets/{index-BxgA_mz1.js → index-DLBzciIC.js} +12 -11
- package/dashboard/dist/index.html +2 -2
- package/dashboard/server/doctor.js +68 -20
- package/dashboard/server/fleet.js +19 -5
- package/dashboard/server/index.js +79 -7
- package/dashboard/server/metrics.js +16 -4
- package/dashboard/server/versions.js +28 -6
- package/dashboard/server/yaml.js +4 -1
- package/install.ps1 +4 -0
- package/install.sh +19 -1
- package/package.json +5 -2
- package/profile/SCHEMA.md +23 -26
- package/scripts/kanban-move.sh +34 -20
- package/scripts/metrics/collect.mjs +495 -0
- package/scripts/metrics/prices.json +39 -0
- package/scripts/new-feature.sh.template +3 -1
- package/scripts/preflight.sh +16 -3
- package/scripts/remove-feature.sh.template +2 -1
- package/scripts/telemetry-send.sh +15 -1
- package/scripts/test-dashboard.mjs +362 -0
- package/scripts/test-gate.mjs +273 -0
- package/scripts/test-metrics.mjs +135 -0
- package/scripts/test-workflows.mjs +321 -0
- package/scripts/validate-core.mjs +51 -1
- package/core/commands/cycle.md +0 -54
- package/core/workflows/cycle.js +0 -407
- package/dashboard/dist/assets/index-Cj0SpgEY.css +0 -1
package/profile/SCHEMA.md
CHANGED
|
@@ -162,7 +162,11 @@ Coarse first, specialize on evidence: start with one `frontend` / `backend` surf
|
|
|
162
162
|
surface that's proven slow and cleanly separable. The evidence lives in
|
|
163
163
|
the **main checkout's** `.claude/pipeline-metrics.jsonl` (gitignored) — one JSONL line per phase batch
|
|
164
164
|
(`ts`/`feature`/`phase`/`seconds`/`surfaces:{key: result}`), appended by `/build`, `/review`, `/fix`
|
|
165
|
-
and `/smoke`.
|
|
165
|
+
and `/smoke`.
|
|
166
|
+
**`surfaces` keys are surface keys, nothing else** — run-level facts go in their own top-level
|
|
167
|
+
fields. Anything put inside `surfaces` is read
|
|
168
|
+
as a surface: the dashboard renders it as a row in the per-surface table and scores a non-`ok`
|
|
169
|
+
value as that surface failing. Always the main checkout, never the feature worktree (which dies at teardown while
|
|
166
170
|
metrics must accumulate across features) — resolve from anywhere with
|
|
167
171
|
`$(dirname "$(git rev-parse --git-common-dir)")/.claude/pipeline-metrics.jsonl`. Read it before
|
|
168
172
|
proposing a split: split the surface that actually dominates wall-clock, not the one that feels big.
|
|
@@ -268,7 +272,9 @@ this exact procedure so a surface is always defined the same way. To add surface
|
|
|
268
272
|
this feature also changes. The link is self-contained — no stored project id. Build with the code UI
|
|
269
273
|
kit (the `design_system_project`'s materialization: `@/components/ui/*` + tokens); read a primitive
|
|
270
274
|
via `get_file` only if it's missing/stale in code. Mobile-first."_
|
|
271
|
-
- `<SURFACE_TDD_STEP1>` —
|
|
275
|
+
- `<SURFACE_TDD_STEP1>` — a **lead-in paragraph** above the TDD list (not a numbered item; it
|
|
276
|
+
renders as nothing for a non-design surface, which is why the list must not start at it):
|
|
277
|
+
_"**Pull the feature design first** (skip if your dispatch's design slot
|
|
272
278
|
says `none`): `DesignSync get_file(<projectId>, <file>)` for each link in the slot and translate
|
|
273
279
|
each into the code design system (`@/components/ui/*`, `cn()` + CVA), mobile-first — never ad-hoc
|
|
274
280
|
CSS. Then:"_
|
|
@@ -318,14 +324,13 @@ Workflow runtime executes instead of the lead reasoning out the fan-out turn by
|
|
|
318
324
|
`<core>/workflows/review.js`, `audit.js`, `refactor.js` (installed to `.claude/workflows/` bundled or
|
|
319
325
|
`~/.claude/workflows/` global). The conversational commands (`/review`, `/audit`, `/refactor`)
|
|
320
326
|
**remain the default path and the fallback** — a workflow runs only when the human explicitly asks
|
|
321
|
-
for it ("run the review workflow",
|
|
322
|
-
`cycle.js` and invokes the runtime for them), and requires Claude Code ≥ **2.1.154** with workflows
|
|
327
|
+
for it ("run the review workflow"), and requires Claude Code ≥ **2.1.154** with workflows
|
|
323
328
|
enabled.
|
|
324
329
|
`/doctor` reports which path a session will take. The interactive commands (`/init-pipeline`,
|
|
325
330
|
`/brainstorm`, `/spec`) and the dispatch-only ones (`/build`, `/ship`) have **no** workflow variant on
|
|
326
331
|
purpose: they're interviews or already a single parallel dispatch — a script adds nothing.
|
|
327
332
|
|
|
328
|
-
Shared design, all
|
|
333
|
+
Shared design, all four scripts:
|
|
329
334
|
|
|
330
335
|
- **Phase 0 is always `profile-reader`** — workflow scripts have no filesystem or shell access, so a
|
|
331
336
|
dedicated agent (`core/agents/profile-reader.md`, haiku, read-only) reads `PIPELINE.md` and returns
|
|
@@ -336,6 +341,12 @@ Shared design, all three scripts:
|
|
|
336
341
|
- **Only the verdict comes back.** Bulk (diffs, reports, backlogs) is staged to the same disk
|
|
337
342
|
buffers the commands use (`specs/reports/`, `specs/refactor-backlog.md`); the workflow's return is
|
|
338
343
|
counts + verdict + paths.
|
|
344
|
+
- **A dead agent is never a clean result.** `agent()` resolves to `null` when a subagent dies, and a
|
|
345
|
+
dead *reviewer* returns zero findings — byte-identical to a surface that is genuinely clean. Any
|
|
346
|
+
script that derives a verdict from "how many findings came back" must first subtract the agents
|
|
347
|
+
that never answered: `review.js` names them in `unreviewedSurfaces` and refuses to score
|
|
348
|
+
`SHIP`. `scripts/test-workflows.mjs`
|
|
349
|
+
pins this — it is the one invariant the structural checks in `validate-core.mjs` cannot see.
|
|
339
350
|
- **`review.js`** — preflight gate (aborts red, zero agents), one `git diff --stat` staged per
|
|
340
351
|
touched surface, one reviewer per surface in parallel, then an **adversarial cross-check** phase
|
|
341
352
|
that tries to refute each CRITICAL/security finding before it can trigger a fix loop.
|
|
@@ -344,27 +355,9 @@ Shared design, all three scripts:
|
|
|
344
355
|
- **`refactor.js`** — big domains only (it skips domains with a handful of open items — the
|
|
345
356
|
conversational `/refactor` is cheaper there): `shared` first and alone, then the other domains'
|
|
346
357
|
implementers in parallel, each verified per-domain.
|
|
347
|
-
- **`cycle.js`** — the **full dev cycle** on a frozen spec: contract → parallel build → rounds of
|
|
348
|
-
[preflight → smoke ∥ review(+cross-check) → fix on the surfaces with findings], looping until
|
|
349
|
-
**zero open findings + a PASS smoke** (`maxRounds`, default 5, and the token budget are runaway
|
|
350
|
-
protection, not targets). Since a workflow can't ask anything mid-run, the decisions move to the
|
|
351
|
-
edges: a **readiness gate** aborts up front if the spec isn't frozen (other gaps ride along as
|
|
352
|
-
deferred questions), and everything genuinely human comes back at the END in the result's
|
|
353
|
-
`questions` array — empty when `/brainstorm` + `/spec` did their job. Even a finding that implies
|
|
354
|
-
a **contract change stays inside the loop**: a lead-equivalent agent re-authors spec §5 + the
|
|
355
|
-
contract file (exactly what conversational `/fix` §1 does — implementers still never touch it),
|
|
356
|
-
the consuming surfaces re-dispatch, and the loop continues; the re-authorings are reported in the
|
|
357
|
-
result's `contractChanges` for the human to review in the diff. A clean exit ticks the DoD and
|
|
358
|
-
stamps the freshness gate so `/ship <id>` is a straight shot; a stopped run appends its open
|
|
359
|
-
findings to the spec's `## Remediation` so a rerun of the cycle — or a conversational `/fix` —
|
|
360
|
-
continues seamlessly. `/ship` itself stays outside on purpose — outward-facing and irreversible,
|
|
361
|
-
it keeps its human confirmation.
|
|
362
|
-
**Corollary — harden the spec:** the more `/brainstorm` + `/spec` pre-answer (edge cases, error
|
|
363
|
-
envelopes, role matrix, design links), the further the cycle runs and the emptier `questions`
|
|
364
|
-
comes back; a vague spec just converts into deferred questions.
|
|
365
358
|
- **No input mid-run.** A workflow runs to completion without questions; anything interactive
|
|
366
|
-
(contract changes, human decisions) belongs to the conversational path
|
|
367
|
-
|
|
359
|
+
(contract changes, human decisions) belongs to the conversational path. The gate hook still
|
|
360
|
+
fires on workflow subagents (see
|
|
368
361
|
§Preflight) — in unattended runs its asks become denies.
|
|
369
362
|
- **Permissions:** `/init-pipeline` and `/update-pipeline` extend the generated `settings.json`
|
|
370
363
|
`allow` list with what workflow agents need (the quiet commands, the shipped
|
|
@@ -416,7 +409,7 @@ card created in the target column if missing.
|
|
|
416
409
|
| `/spec` opens (draft) | `spec` |
|
|
417
410
|
| `/spec` freezes (`status: frozen`) | `ready` |
|
|
418
411
|
| `/build` | `building` |
|
|
419
|
-
| `/
|
|
412
|
+
| `/review` (owns the move — `/smoke` never moves the card) | `review` |
|
|
420
413
|
| `/fix` | `fix` |
|
|
421
414
|
| `/ship` starts | `ship` |
|
|
422
415
|
| PR opened (`status: shipped`) | `shipped` (+ `PR #<num>` on the card) |
|
|
@@ -457,6 +450,10 @@ see where features stall, so every stage of `idea → PR` reports and nothing el
|
|
|
457
450
|
| `fix` | after the batch metrics line | wall-clock | `<fixed>/<found>` |
|
|
458
451
|
| `ship` | the release agent succeeded | `0` | `pr` / `compare` |
|
|
459
452
|
|
|
453
|
+
> Workflow-variant runs (`review.js`) report `seconds: 0` for their phases — only the
|
|
454
|
+
> conversational commands measure wall-clock. `results` is a free-text summary field, so both
|
|
455
|
+
> forms are valid — but read the `fix` column knowing which path produced it.
|
|
456
|
+
|
|
460
457
|
`seconds: 0` marks a phase whose duration is human thinking time, not pipeline wall-clock — the
|
|
461
458
|
funnel signal there is the event, not how long it took. `/doctor`, `/audit`, `/refactor`,
|
|
462
459
|
`/align-ds`, `/init-pipeline` and `/update-pipeline` **never** ping: they sit outside the funnel, and
|
package/scripts/kanban-move.sh
CHANGED
|
@@ -28,28 +28,44 @@ done
|
|
|
28
28
|
|
|
29
29
|
tmp="${board}.kanban-move.$$"
|
|
30
30
|
ID="$id" COL="$col" PR="$pr" TITLE="${title:-$id}" awk '
|
|
31
|
+
# Emit one line, collapsing runs of blank lines to a single one. Without this
|
|
32
|
+
# every move netted +1 blank line in the target column (the card is inserted
|
|
33
|
+
# with its own separator, right after the blank that already preceded the next
|
|
34
|
+
# heading) — ten moves of one card padded a board with fifteen blank lines.
|
|
35
|
+
function emit(s) {
|
|
36
|
+
if (s == "") { if (lastblank) return; lastblank = 1 } else lastblank = 0
|
|
37
|
+
print s
|
|
38
|
+
}
|
|
39
|
+
function put_card( k) {
|
|
40
|
+
emit(card)
|
|
41
|
+
for (k = 1; k <= sn; k++) emit(snote[k])
|
|
42
|
+
}
|
|
31
43
|
BEGIN {
|
|
32
44
|
id = ENVIRON["ID"]; col = tolower(ENVIRON["COL"])
|
|
33
45
|
pr = ENVIRON["PR"]; title = ENVIRON["TITLE"]
|
|
34
|
-
n = 0; card = "";
|
|
46
|
+
n = 0; card = ""; found = 0; colseen = 0; sn = 0; lastblank = 0
|
|
35
47
|
}
|
|
36
48
|
{ lines[++n] = $0 }
|
|
37
49
|
END {
|
|
38
50
|
# pass 1: extract the FIRST card block tagged #id; mark every block for removal
|
|
39
51
|
for (i = 1; i <= n; i++) {
|
|
40
52
|
l = lines[i]
|
|
41
|
-
if (l
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
if (l !~ /^- \[.\] /) continue
|
|
54
|
+
p = index(l, "#" id)
|
|
55
|
+
if (p == 0) continue
|
|
56
|
+
# word-boundary check: char after the tag must not extend the id
|
|
57
|
+
rest = substr(l, p + length(id) + 1, 1)
|
|
58
|
+
if (rest != "" && rest ~ /[A-Za-z0-9_-]/) continue
|
|
59
|
+
# Capture the sub-notes of the FIRST match only. Testing `card == lines[i]`
|
|
60
|
+
# instead also captured a later duplicate whose text happened to match.
|
|
61
|
+
isfirst = 0
|
|
62
|
+
if (!found) { found = 1; card = l; isfirst = 1 }
|
|
63
|
+
del[i] = 1
|
|
64
|
+
for (j = i + 1; j <= n && lines[j] ~ /^[ \t]/; j++) {
|
|
65
|
+
del[j] = 1
|
|
66
|
+
if (isfirst) snote[++sn] = lines[j]
|
|
52
67
|
}
|
|
68
|
+
i = j - 1
|
|
53
69
|
}
|
|
54
70
|
if (!found) card = "- [ ] " title " #" id
|
|
55
71
|
if (pr != "" && index(card, "PR #") == 0) card = card " — PR #" pr
|
|
@@ -63,22 +79,20 @@ ID="$id" COL="$col" PR="$pr" TITLE="${title:-$id}" awk '
|
|
|
63
79
|
atheading = (l ~ /^## /)
|
|
64
80
|
atsettings = (l ~ /^%% kanban:settings/)
|
|
65
81
|
if (intarget && (atheading || atsettings)) {
|
|
66
|
-
# back up over
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
print ""
|
|
82
|
+
# can not back up over already-printed lines; place the card just before
|
|
83
|
+
# this boundary instead
|
|
84
|
+
put_card()
|
|
85
|
+
emit("")
|
|
71
86
|
intarget = 0; placed = 1
|
|
72
87
|
}
|
|
73
88
|
if (atheading) {
|
|
74
89
|
h = tolower(l); sub(/^## +/, "", h); gsub(/ +$/, "", h)
|
|
75
90
|
if (h == col) { intarget = 1; colseen = 1 }
|
|
76
91
|
}
|
|
77
|
-
|
|
92
|
+
emit(l)
|
|
78
93
|
}
|
|
79
94
|
if (intarget && !placed) {
|
|
80
|
-
|
|
81
|
-
if (sub_notes != "") printf "%s", sub_notes
|
|
95
|
+
put_card()
|
|
82
96
|
placed = 1
|
|
83
97
|
}
|
|
84
98
|
if (!colseen) exit 3
|