cohorte 2.10.0 → 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.
- package/README.md +7 -0
- package/assets/manifest.json +193 -0
- package/assets/migrations/config/README.md +12 -0
- package/assets/migrations/state/0001_init.sql +154 -0
- package/assets/prompts/README.md +11 -0
- package/assets/prompts/agents/fixer.md +7 -0
- package/assets/prompts/agents/implementer.md +12 -0
- package/assets/prompts/agents/reviewer.md +8 -0
- package/assets/prompts/agents/security-reviewer.md +7 -0
- package/assets/prompts/phases/build.md +5 -0
- package/assets/prompts/phases/review.md +5 -0
- package/assets/prompts/phases/test.md +5 -0
- package/assets/prompts/system.md +11 -0
- package/assets/schemas/agent-output.schema.json +225 -0
- package/assets/schemas/auth-status.schema.json +87 -0
- package/assets/schemas/command-result.schema.json +192 -0
- package/assets/schemas/commands.schema.json +952 -0
- package/assets/schemas/config.schema.json +2259 -0
- package/assets/schemas/doctor-report.schema.json +104 -0
- package/assets/schemas/events.schema.json +8514 -0
- package/assets/schemas/fake-script.schema.json +549 -0
- package/assets/schemas/inspect.schema.json +3133 -0
- package/assets/schemas/manifest.schema.json +79 -0
- package/assets/schemas/ownership.schema.json +52 -0
- package/assets/schemas/policy-verdict.schema.json +282 -0
- package/assets/schemas/project-model.schema.json +783 -0
- package/assets/schemas/project-status.schema.json +217 -0
- package/assets/schemas/reconcile-plan.schema.json +155 -0
- package/assets/schemas/run-diff.schema.json +177 -0
- package/assets/schemas/run-snapshot-manifest.schema.json +2104 -0
- package/assets/schemas/run-state.schema.json +1755 -0
- package/assets/schemas/runtime-capabilities.schema.json +1545 -0
- package/assets/schemas/sandbox-capabilities.schema.json +116 -0
- package/assets/schemas/skill.schema.json +87 -0
- package/assets/schemas/spec.schema.json +202 -0
- package/assets/schemas/tool-catalogue.schema.json +655 -0
- package/assets/schemas/trust-record.schema.json +38 -0
- package/assets/skills/README.md +9 -0
- package/assets/skills/inspect-surface.md +4 -0
- package/dist/agent-host.mjs +50 -0
- package/dist/agent-host.mjs.map +1 -0
- package/dist/bundle-manifest.json +102 -0
- package/dist/chunks/__host-DgGPyco9.mjs +18 -0
- package/dist/chunks/__host-DgGPyco9.mjs.map +1 -0
- package/dist/chunks/approve-Cv135UqI.mjs +19 -0
- package/dist/chunks/approve-Cv135UqI.mjs.map +1 -0
- package/dist/chunks/auth-BT5oVp76.mjs +132 -0
- package/dist/chunks/auth-BT5oVp76.mjs.map +1 -0
- package/dist/chunks/auth-D8rCVY33.mjs +84 -0
- package/dist/chunks/auth-D8rCVY33.mjs.map +1 -0
- package/dist/chunks/brainstorm-CU10T1tm.mjs +7 -0
- package/dist/chunks/brainstorm-CU10T1tm.mjs.map +1 -0
- package/dist/chunks/cancel-KV_cpsqS.mjs +15 -0
- package/dist/chunks/cancel-KV_cpsqS.mjs.map +1 -0
- package/dist/chunks/canonical-C8UxeEmB.mjs +57 -0
- package/dist/chunks/canonical-C8UxeEmB.mjs.map +1 -0
- package/dist/chunks/catalogue-BvwSrM_j.mjs +1549 -0
- package/dist/chunks/catalogue-BvwSrM_j.mjs.map +1 -0
- package/dist/chunks/compose-Cr0hP0qm.mjs +7303 -0
- package/dist/chunks/compose-Cr0hP0qm.mjs.map +1 -0
- package/dist/chunks/config-DefHA2Lw.mjs +96 -0
- package/dist/chunks/config-DefHA2Lw.mjs.map +1 -0
- package/dist/chunks/contract-B0ra2-bm.mjs +295 -0
- package/dist/chunks/contract-B0ra2-bm.mjs.map +1 -0
- package/dist/chunks/contract-vYbwYr0Y.mjs +123 -0
- package/dist/chunks/contract-vYbwYr0Y.mjs.map +1 -0
- package/dist/chunks/deny-ClTxSvHJ.mjs +15 -0
- package/dist/chunks/deny-ClTxSvHJ.mjs.map +1 -0
- package/dist/chunks/diff-Qw0II0sj.mjs +101 -0
- package/dist/chunks/diff-Qw0II0sj.mjs.map +1 -0
- package/dist/chunks/discover-30Ciya_0.mjs +20 -0
- package/dist/chunks/discover-30Ciya_0.mjs.map +1 -0
- package/dist/chunks/doctor-DfCrKpfT.mjs +222 -0
- package/dist/chunks/doctor-DfCrKpfT.mjs.map +1 -0
- package/dist/chunks/fix-D4Tj8RVA.mjs +18 -0
- package/dist/chunks/fix-D4Tj8RVA.mjs.map +1 -0
- package/dist/chunks/gc-BBXlBF5w.mjs +161 -0
- package/dist/chunks/gc-BBXlBF5w.mjs.map +1 -0
- package/dist/chunks/init-Okz4mlhW.mjs +30 -0
- package/dist/chunks/init-Okz4mlhW.mjs.map +1 -0
- package/dist/chunks/inspect-Buby4cQD.mjs +22 -0
- package/dist/chunks/inspect-Buby4cQD.mjs.map +1 -0
- package/dist/chunks/logs-Giq5sUau.mjs +25 -0
- package/dist/chunks/logs-Giq5sUau.mjs.map +1 -0
- package/dist/chunks/migrate-DRoFUg53.mjs +19 -0
- package/dist/chunks/migrate-DRoFUg53.mjs.map +1 -0
- package/dist/chunks/models-Btp6X2YZ.mjs +24 -0
- package/dist/chunks/models-Btp6X2YZ.mjs.map +1 -0
- package/dist/chunks/pause-BASBfKLO.mjs +16 -0
- package/dist/chunks/pause-BASBfKLO.mjs.map +1 -0
- package/dist/chunks/policy-DUIgzF3W.mjs +139 -0
- package/dist/chunks/policy-DUIgzF3W.mjs.map +1 -0
- package/dist/chunks/providers-Dx78imKj.mjs +19 -0
- package/dist/chunks/providers-Dx78imKj.mjs.map +1 -0
- package/dist/chunks/reconcile-BfD785gd.mjs +26 -0
- package/dist/chunks/reconcile-BfD785gd.mjs.map +1 -0
- package/dist/chunks/render-DyZr5Lgi.mjs +53 -0
- package/dist/chunks/render-DyZr5Lgi.mjs.map +1 -0
- package/dist/chunks/resume-GJQRb2Eb.mjs +15 -0
- package/dist/chunks/resume-GJQRb2Eb.mjs.map +1 -0
- package/dist/chunks/retry-CLrUem1H.mjs +15 -0
- package/dist/chunks/retry-CLrUem1H.mjs.map +1 -0
- package/dist/chunks/review-BDdjb6kB.mjs +17 -0
- package/dist/chunks/review-BDdjb6kB.mjs.map +1 -0
- package/dist/chunks/run-CgTWYlcd.mjs +82 -0
- package/dist/chunks/run-CgTWYlcd.mjs.map +1 -0
- package/dist/chunks/run-tool-C3rAv4XF.mjs +26 -0
- package/dist/chunks/run-tool-C3rAv4XF.mjs.map +1 -0
- package/dist/chunks/schema-C9QbUW4H.mjs +578 -0
- package/dist/chunks/schema-C9QbUW4H.mjs.map +1 -0
- package/dist/chunks/send-BdnJ85Nz.mjs +19 -0
- package/dist/chunks/send-BdnJ85Nz.mjs.map +1 -0
- package/dist/chunks/ship-DTuLGoaJ.mjs +19 -0
- package/dist/chunks/ship-DTuLGoaJ.mjs.map +1 -0
- package/dist/chunks/shutdown-BM92Iyv2.mjs +15 -0
- package/dist/chunks/shutdown-BM92Iyv2.mjs.map +1 -0
- package/dist/chunks/skip-DZG5kpcH.mjs +18 -0
- package/dist/chunks/skip-DZG5kpcH.mjs.map +1 -0
- package/dist/chunks/spec-Bq0P3jVh.mjs +20 -0
- package/dist/chunks/spec-Bq0P3jVh.mjs.map +1 -0
- package/dist/chunks/src-CId6kMsY.mjs +1689 -0
- package/dist/chunks/src-CId6kMsY.mjs.map +1 -0
- package/dist/chunks/src-CfAOde7C.mjs +5403 -0
- package/dist/chunks/src-CfAOde7C.mjs.map +1 -0
- package/dist/chunks/src-Cu3TxNob.mjs +783 -0
- package/dist/chunks/src-Cu3TxNob.mjs.map +1 -0
- package/dist/chunks/src-DcU4IXMe.mjs +117 -0
- package/dist/chunks/src-DcU4IXMe.mjs.map +1 -0
- package/dist/chunks/src-JxawBQBn.mjs +446 -0
- package/dist/chunks/src-JxawBQBn.mjs.map +1 -0
- package/dist/chunks/src-s7YRvk0t.mjs +2116 -0
- package/dist/chunks/src-s7YRvk0t.mjs.map +1 -0
- package/dist/chunks/status-SMjiOMYy.mjs +29 -0
- package/dist/chunks/status-SMjiOMYy.mjs.map +1 -0
- package/dist/chunks/tail-BuhT-2PH.mjs +25 -0
- package/dist/chunks/tail-BuhT-2PH.mjs.map +1 -0
- package/dist/chunks/update-BXIppsbi.mjs +16 -0
- package/dist/chunks/update-BXIppsbi.mjs.map +1 -0
- package/dist/cli.mjs +175 -0
- package/dist/cli.mjs.map +1 -0
- package/package.json +17 -32
- package/CHANGELOG.md +0 -1453
- package/bin/cli.js +0 -812
- package/bin/report.js +0 -141
- package/core/adapter/render.js +0 -389
- package/core/agents/implementer.template.md +0 -142
- package/core/agents/profile-reader.md +0 -50
- package/core/agents/release.md +0 -58
- package/core/agents/review.md +0 -179
- package/core/commands/cohorte-align-ds.md +0 -33
- package/core/commands/cohorte-audit.md +0 -58
- package/core/commands/cohorte-brainstorm.md +0 -70
- package/core/commands/cohorte-build.md +0 -219
- package/core/commands/cohorte-doctor.md +0 -170
- package/core/commands/cohorte-fix.md +0 -80
- package/core/commands/cohorte-fleet.md +0 -103
- package/core/commands/cohorte-init-pipeline.md +0 -31
- package/core/commands/cohorte-intake.md +0 -92
- package/core/commands/cohorte-patch.md +0 -118
- package/core/commands/cohorte-refactor.md +0 -59
- package/core/commands/cohorte-retro.md +0 -85
- package/core/commands/cohorte-review.md +0 -270
- package/core/commands/cohorte-ship.md +0 -122
- package/core/commands/cohorte-spec.md +0 -101
- package/core/commands/cohorte-update-pipeline.md +0 -134
- package/core/hooks/gate.py +0 -580
- package/core/runtimes/claude.json +0 -74
- package/core/runtimes/codex.json +0 -83
- package/core/runtimes/cursor.json +0 -76
- package/core/runtimes/gemini.json +0 -76
- package/core/runtimes/opencode.json +0 -73
- package/core/templates/agent-handoff.md +0 -34
- package/core/templates/brainstorm-return.md +0 -36
- package/core/templates/decisions.template.md +0 -42
- package/core/templates/design-brief.md +0 -44
- package/core/templates/patch.template.md +0 -86
- package/core/templates/pr-body.md +0 -25
- package/core/templates/review-feedback.md +0 -38
- package/core/templates/spec.template.md +0 -87
- package/core/templates/steps/init-pipeline/01-detect-stack.md +0 -44
- package/core/templates/steps/init-pipeline/02-interview-gaps.md +0 -55
- package/core/templates/steps/init-pipeline/03-draft-profile.md +0 -10
- package/core/templates/steps/init-pipeline/04-write-render.md +0 -115
- package/core/templates/steps/init-pipeline/05-report.md +0 -12
- package/core/workflows/audit.js +0 -240
- package/core/workflows/loop.js +0 -641
- package/core/workflows/refactor.js +0 -276
- package/core/workflows/review.js +0 -456
- package/install.ps1 +0 -103
- package/install.sh +0 -114
- package/lib/doctor.js +0 -504
- package/lib/runtime.js +0 -134
- package/lib/versions.js +0 -149
- package/lib/yaml.js +0 -129
- package/profile/PIPELINE.template.md +0 -251
- package/profile/SCHEMA.md +0 -698
- package/profile/cohorte.config.template.yaml +0 -43
- package/scripts/assert-gate-hook.mjs +0 -75
- package/scripts/kanban-move.sh +0 -313
- package/scripts/metrics/collect.mjs +0 -531
- package/scripts/metrics/prices.json +0 -42
- package/scripts/new-feature.sh.template +0 -98
- package/scripts/preflight.sh +0 -124
- package/scripts/remove-feature.sh.template +0 -56
- package/scripts/test-adapter.mjs +0 -368
- package/scripts/test-gate.mjs +0 -428
- package/scripts/test-kanban.mjs +0 -304
- package/scripts/test-lib.mjs +0 -310
- package/scripts/test-metrics.mjs +0 -156
- package/scripts/test-workflows.mjs +0 -758
- package/scripts/validate-core.mjs +0 -320
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
feature_id: <slug>
|
|
3
|
-
title: <Feature title>
|
|
4
|
-
status: draft # draft → frozen → in-progress → in-review → shipped · blocked (see SCHEMA.md §Spec status)
|
|
5
|
-
branch: <feature_branch_prefix><slug>
|
|
6
|
-
created: <YYYY-MM-DD>
|
|
7
|
-
reviewed_base: # merge-base sha at the last SHIP verdict — freshness-gate anchor (written by /cohorte-review)
|
|
8
|
-
reviewed_digest: # sha256 (16 hex) of the reviewed source diff vs reviewed_base, specs excluded — /cohorte-ship re-checks
|
|
9
|
-
design_files: [] # design page links — full URLs of the form https://claude.ai/design/p/<projectId>?file=<file> (each carries its own project + page); blank until designed; omit if no UI
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# <Feature title>
|
|
13
|
-
|
|
14
|
-
## 1. Goal & user story
|
|
15
|
-
|
|
16
|
-
<one paragraph: what · who · why>
|
|
17
|
-
|
|
18
|
-
## 2. Scope
|
|
19
|
-
|
|
20
|
-
**In:** …
|
|
21
|
-
**Out (non-goals):** …
|
|
22
|
-
|
|
23
|
-
## 3. Roles & permissions
|
|
24
|
-
|
|
25
|
-
> Only if `PIPELINE.md` §rbac is enabled — which roles can do what; note the lowest-privilege experience.
|
|
26
|
-
|
|
27
|
-
## 4. Data model & migrations
|
|
28
|
-
|
|
29
|
-
> Additive only (never gate.deny commands). Omit if the feature has no persistence.
|
|
30
|
-
|
|
31
|
-
| table | column | type | null? | default | notes |
|
|
32
|
-
| ----- | ------ | ---- | ----- | ------- | ----- |
|
|
33
|
-
| | | | | | |
|
|
34
|
-
|
|
35
|
-
## 5. CONTRACT (frozen)
|
|
36
|
-
|
|
37
|
-
> The only sync channel. Complete enough to build every surface independently. Uses the profile's
|
|
38
|
-
> `contract.mechanism`. If `contract.enabled` is false, capture the interface precisely in prose here.
|
|
39
|
-
|
|
40
|
-
### `<METHOD> /<path>` · auth: `<middleware/role>`
|
|
41
|
-
|
|
42
|
-
- **Request** — params / query / body: `field` · type · required? · nullable? · validation
|
|
43
|
-
- **Success** — `<status>` · response shape
|
|
44
|
-
- **Errors** — `<status>` · when (422 validation · 401 · 403 · 404 · 409 …)
|
|
45
|
-
|
|
46
|
-
<repeat per endpoint/interface>
|
|
47
|
-
|
|
48
|
-
### Contract types — lead authors `<contract.path>/<slug>.<ext>` before /cohorte-build
|
|
49
|
-
|
|
50
|
-
- `<slug>...Request` / `<slug>...Response` / `<slug>Base` … (sketch the schemas here)
|
|
51
|
-
|
|
52
|
-
## 6. Surface tasks
|
|
53
|
-
|
|
54
|
-
> One `###` subsection per surface in `PIPELINE.md` §surfaces (e.g. backend, frontend), each TDD.
|
|
55
|
-
> Surfaces are subsections of §6, never top-level sections — §8 and §9 are referenced by number
|
|
56
|
-
> across the whole pipeline (`/cohorte-build` implements "§8 flows", `/cohorte-review` checks "§9 acceptance"), so a
|
|
57
|
-
> third surface must not become a §8 of its own.
|
|
58
|
-
|
|
59
|
-
### <surface.key>
|
|
60
|
-
|
|
61
|
-
- …
|
|
62
|
-
|
|
63
|
-
## 8. Design brief (the "spec return")
|
|
64
|
-
|
|
65
|
-
> Only if the project has UI. Standalone; mirrors `<core>/templates/design-brief.md`.
|
|
66
|
-
|
|
67
|
-
- **Screens / views:** …
|
|
68
|
-
- **Flows:** …
|
|
69
|
-
- **Responsive:** mobile (base) → `md:` → `lg:`
|
|
70
|
-
- **States:** empty · loading · error · success
|
|
71
|
-
|
|
72
|
-
## 9. Acceptance criteria / DoD
|
|
73
|
-
|
|
74
|
-
- [ ] Every contract endpoint/interface implemented exactly
|
|
75
|
-
- [ ] Each surface's tests (TDD) green
|
|
76
|
-
- [ ] `PIPELINE.md` commands.lint · typecheck · test green
|
|
77
|
-
- [ ] Mobile-first / responsive verified (if UI)
|
|
78
|
-
- [ ] User-facing copy in `ui_language`
|
|
79
|
-
|
|
80
|
-
## 10. Assumptions & open questions
|
|
81
|
-
|
|
82
|
-
- …
|
|
83
|
-
|
|
84
|
-
## Remediation
|
|
85
|
-
|
|
86
|
-
> Filled by `/cohorte-spec` in review-return mode; empty otherwise. Each item:
|
|
87
|
-
> `[ ] <SEVERITY> · <file:line> · <spec-violation|quality|security> · <concrete fix>`
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# /cohorte-init-pipeline · 01 Detect the stack
|
|
2
|
-
|
|
3
|
-
### Phase 1 — Detect the stack (read-only, no questions yet)
|
|
4
|
-
|
|
5
|
-
Gather evidence, then summarize what you found. Look for:
|
|
6
|
-
|
|
7
|
-
- **Package manager & workspaces:** root `package.json` (`packageManager`, `workspaces`),
|
|
8
|
-
`pnpm-workspace.yaml`, `turbo.json`, `nx.json`, `lerna.json`; lockfiles (`pnpm-lock.yaml`,
|
|
9
|
-
`package-lock.json`, `yarn.lock`, `bun.lockb`); or non-JS: `pyproject.toml`/`requirements.txt`,
|
|
10
|
-
`go.mod`, `Cargo.toml`, `Gemfile`, `composer.json`.
|
|
11
|
-
- **Surfaces (independently-built areas):** `apps/*`, `packages/*`, `services/*`, `cmd/*`, or a single
|
|
12
|
-
root app. For each, detect its framework from its own `package.json`/config:
|
|
13
|
-
backend markers (`adonisrc.ts`, `@adonisjs/*`, `nestjs`, `express`, `fastify`, `django`, `fastapi`,
|
|
14
|
-
`rails`, `.go`), frontend markers (`vite.config.*`, `next.config.*`, `@tanstack/react-router`,
|
|
15
|
-
`angular.json`, `nuxt`, `svelte`), and its test runner (`@japa/*`, `vitest`, `jest`, `playwright`,
|
|
16
|
-
`pytest`, `go test`), linter/formatter (`eslint`, `prettier`, `biome`, `ruff`).
|
|
17
|
-
- **Split candidates (sub-surface boundaries):** inside each surface, look for a clean internal partition
|
|
18
|
-
— feature modules (`src/features/*`, `src/modules/*`, `app/(group)/*`), or independent services
|
|
19
|
-
(`services/*`, domain folders). Note the surface's rough size (module/file count) so a *large* surface
|
|
20
|
-
with a *clean* boundary can be proposed as several specialized surfaces in Phase 2. See SCHEMA.md
|
|
21
|
-
§"Specialization — when to split one surface into more agents" for the heuristic. Don't split yet — just
|
|
22
|
-
flag candidates + their would-be shared-code tree.
|
|
23
|
-
- **Per-surface commands:** derive `test`/`lint`/`format`/`typecheck`/`build` from each surface's
|
|
24
|
-
`package.json` scripts + the workspace filter syntax (e.g. `pnpm --filter <pkg> test`).
|
|
25
|
-
- **Contract mechanism:** a shared types/schema package (`packages/shared-types`, Zod/`z.`),
|
|
26
|
-
an `openapi.*`/`swagger.*` file, `.proto` files, or none.
|
|
27
|
-
- **Release notes / versioning:** a `.changeset/` directory (+ `config.json`, and whether it declares a
|
|
28
|
-
`fixed`/lockstep group — that names the anchor package), `changesets` in devDependencies, or a CI job
|
|
29
|
-
that fails a PR lacking a note (grep `.github/workflows/*` for `changeset`). Also note the root
|
|
30
|
-
`version`: a `0.y.z` product usually forbids `major`. None of these ⇒ `release_notes.enabled: false`.
|
|
31
|
-
- **DB / migrations:** migration tooling (`node ace make:migration`, `knex`, `prisma`, `alembic`,
|
|
32
|
-
`golang-migrate`), a `docker-compose.yml`, DB service.
|
|
33
|
-
- **Design system:** an existing `design-reference/` snapshot, `components/ui`, a DesignSync MCP
|
|
34
|
-
connection, Figma links, or none.
|
|
35
|
-
- **Code retrieval:** is the `serena` CLI resolvable (`command -v serena`)? If not, does
|
|
36
|
-
`~/.local/bin/serena` exist anyway — installed but PATH-broken (see SCHEMA.md §Code retrieval)?
|
|
37
|
-
`graphify`? Is either already registered in this repo's `.mcp.json` or `claude mcp list`? (Feeds
|
|
38
|
-
the retrieval question + Phase 4 wiring — default provider is `serena`.)
|
|
39
|
-
- **VCS:** `git remote -v` → host + `owner/repo`; the default branch (`git symbolic-ref refs/remotes/origin/HEAD` or `git branch`).
|
|
40
|
-
- **Existing `<memory>`** — read it; it may already state stack/conventions to fold in. **Existing
|
|
41
|
-
`PIPELINE.md`** — if present, this is a re-run: load it as the starting draft and only reconcile deltas.
|
|
42
|
-
|
|
43
|
-
Print a compact **Detection Report**: layout, surfaces (with framework + commands), contract, release
|
|
44
|
-
notes, DB, design, vcs. Mark each field `detected` / `guessed` / `unknown`.
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# /cohorte-init-pipeline · 02 Interview the gaps
|
|
2
|
-
|
|
3
|
-
### Phase 2 — Interview the gaps (AskUserQuestion)
|
|
4
|
-
|
|
5
|
-
Ask ONLY what you couldn't confidently detect. Batch related questions. Cover:
|
|
6
|
-
|
|
7
|
-
- **Surfaces & ownership** — confirm the surface list and each one's path + owning agent name. (If a
|
|
8
|
-
single-app repo, one surface.) Confirm the `tools` each agent needs (add `DesignSync` only to a
|
|
9
|
-
surface with `uses_design: true`), and each surface's `model` tier: `sonnet` (Recommended default —
|
|
10
|
-
implementers mostly apply a frozen contract, which Sonnet handles at a fraction of the Opus-lead
|
|
11
|
-
cost), `haiku` for purely mechanical surfaces (scaffolding), `inherit` only for surfaces with real
|
|
12
|
-
design decisions worth running on the lead's model.
|
|
13
|
-
- **Specialization (only if Phase 1 flagged a large + cleanly-separable surface)** — offer to split it
|
|
14
|
-
into specialized sub-surfaces (e.g. `web-checkout`, `web-billing`) so `/cohorte-build` runs them in parallel,
|
|
15
|
-
per SCHEMA.md §Specialization. If the human accepts, apply the rules: **shared code (routing, global
|
|
16
|
-
state, DS kit/tokens) becomes its own single-owner surface**, and cross-slice shapes go through the
|
|
17
|
-
contract. Default to NOT splitting when boundaries are tangled or slices are tiny — coarse is fine.
|
|
18
|
-
- **Quiet commands** (never store a bare `pnpm test` as what agents execute) — for each noisy command
|
|
19
|
-
(tests, lint; per surface AND repo-wide), propose the detected **bridled variant** as the
|
|
20
|
-
Recommended option: dot/failures-only reporter (`--reporter=dot` vitest/playwright, `--silent`
|
|
21
|
-
jest, `-q` pytest, `--quiet` eslint/ruff — whatever the detected runner supports). These land in
|
|
22
|
-
`test_quiet_cmd`/`lint_quiet_cmd` + `commands.test_quiet`/`lint_quiet` and are what agents and the
|
|
23
|
-
`/cohorte-review` pre-flight actually run (SCHEMA.md §Output discipline). If the human declines or
|
|
24
|
-
the runner has no such flag, leave `""` — consumers then fall back to `<cmd> 2>&1 | tail -40`.
|
|
25
|
-
- **Contract** — mechanism (`shared-types-zod` / `openapi` / `protobuf` / `json-schema` / `none`) and
|
|
26
|
-
where feature contracts are authored. If `none`, surfaces sync by the spec prose alone.
|
|
27
|
-
- **Release notes (only if Phase 1 found a versioning tool or a note-enforcing CI job)** — confirm the
|
|
28
|
-
anchor package (a `fixed`/lockstep group means ONE key propagates to all), the note's language, and the
|
|
29
|
-
**bump policy** the lead will apply at `/cohorte-ship`: what counts as major/minor/patch here, and
|
|
30
|
-
whether any level is forbidden (a `0.y.z` product normally forbids `major` — Changesets would jump it
|
|
31
|
-
to `1.0.0` with no human deciding). Policy prose lands in `release_notes.guidance`. Nothing detected ⇒
|
|
32
|
-
don't ask; leave `release_notes.enabled: false`.
|
|
33
|
-
- **UI language** — language of all user-facing copy.
|
|
34
|
-
- **RBAC** — is there a role hierarchy? If yes, list it highest→lowest.
|
|
35
|
-
- **Design system** — enabled? provider (Claude Design / Figma / none) + project ids + kit/token paths.
|
|
36
|
-
- **Code retrieval** — confirm the provider (see SCHEMA.md §Code retrieval): `serena` (Recommended
|
|
37
|
-
default — live LSP symbol navigation, no index to maintain), `graphify` (persistent knowledge graph
|
|
38
|
-
over code + docs — better on very large or mixed code+docs repos, but needs an index step + rescans),
|
|
39
|
-
or `none`. Only demote from `serena` if the human objects or the provider CLI can't be installed.
|
|
40
|
-
- **Isolation** — build features in parallel git worktrees with per-feature DB + ports, or just in the
|
|
41
|
-
main checkout? If worktrees: DB-per-worktree? port bases? compose file?
|
|
42
|
-
- **Gate** — confirm the destructive commands to hard-deny and the ones to confirm-first (seed from the
|
|
43
|
-
detected DB/migration tooling + always git commit/push/merge/rebase/reset).
|
|
44
|
-
- **Personas** — keep the default `/cohorte-brainstorm` panel, or customize members for this domain?
|
|
45
|
-
|
|
46
|
-
Prefer sensible defaults from Phase 1 as the first (Recommended) option in each question.
|
|
47
|
-
|
|
48
|
-
- **Kanban** (optional) — mirror this project's pipeline (`/cohorte-brainstorm`…`/cohorte-ship`) onto an Obsidian
|
|
49
|
-
Kanban board? If the human says yes: confirm the shared vault path (`obsidian.vault_path` in
|
|
50
|
-
`<config>`; ask if empty) and the board's location inside it (default
|
|
51
|
-
`<ProjectName>/Tasks.md`). Phase 4 creates the board and records the link. Default: no.
|
|
52
|
-
|
|
53
|
-
> The **kanban** link is user-scoped (it points at a personal vault, so it never goes in the committed
|
|
54
|
-
> `PIPELINE.md`), but IS wired here because it is per-project — see Phase 4.
|
|
55
|
-
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# /cohorte-init-pipeline · 03 Draft the profile
|
|
2
|
-
|
|
3
|
-
### Phase 3 — Draft the profile (show, don't write yet)
|
|
4
|
-
|
|
5
|
-
Assemble the full `PIPELINE.md` from the installer's `pipeline/PIPELINE.template.md` (resolve
|
|
6
|
-
bundled-vs-global per the note above), filling the `yaml pipeline-profile`
|
|
7
|
-
block and every prose section from Phases 1–2. **Keep it lean**: every stateless agent re-reads this
|
|
8
|
-
file on every dispatch, so its length is a per-dispatch token+latency tax — terse rule-shaped
|
|
9
|
-
conventions, no narration, no facts derivable from the code. **Show the human the drafted
|
|
10
|
-
`PIPELINE.md` in a fenced block and get a go-ahead** before writing.
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
# /cohorte-init-pipeline · 04 Write & render
|
|
2
|
-
|
|
3
|
-
### Phase 4 — Write & render (after go-ahead)
|
|
4
|
-
|
|
5
|
-
1. **Write `PIPELINE.md`** at the repo root (source: the installer's `pipeline/PIPELINE.template.md`).
|
|
6
|
-
2. **Wire it into `<memory>`:** if `<memory>` exists, ensure it references the profile (add a line
|
|
7
|
-
near the top: `> Project profile & pipeline facts: **@PIPELINE.md**`). If not, create a minimal
|
|
8
|
-
`<memory>` with that reference + a one-paragraph project intro.
|
|
9
|
-
3. **Render one agent per surface** — for each surface, follow SCHEMA.md §"Rendering / reconciling a
|
|
10
|
-
surface agent" (steps 2–3): render `<agents>/<agent>.md` from the installer's
|
|
11
|
-
`pipeline/implementer.template.md`, substituting `<SURFACE_AGENT>`, `<SURFACE_LABEL>`, `<SURFACE_PATH>`,
|
|
12
|
-
`<SURFACE_TOOLS>`, `<SURFACE_MODEL>`, `<PROJECT_NAME>`, `<SURFACE_CONVENTIONS>` (the surface's
|
|
13
|
-
baked convention slice — §Shared + its `### Surface:` stanza + its §Testing lines from the
|
|
14
|
-
PIPELINE.md you just wrote), and the surface-specific blocks
|
|
15
|
-
(`<SURFACE_EXTRA_NEVER>`, `<SURFACE_DESIGN_INPUT>`, `<SURFACE_TDD_STEP1>` — fill design-related ones
|
|
16
|
-
only when `uses_design`).
|
|
17
|
-
Leave the fixed agents as-is (generic, shipped by the installer): `review.md`, `release.md`,
|
|
18
|
-
`profile-reader.md`.
|
|
19
|
-
4. **Generate `<state>/gate-config.json`** from the `gate` block — copy all five keys verbatim:
|
|
20
|
-
`{"deny": [...], "ask": [...], "ask_on_default_branch": [...], "default_branch": "<vcs.default_branch>",
|
|
21
|
-
"preflight": {"enabled": <gate.preflight.enabled>, "agents": [...], "max_age_minutes": <n>}}`
|
|
22
|
-
(profile has no `preflight` block ⇒ omit the key — the hook then skips the phase gate).
|
|
23
|
-
<!-- cohorte:if hooks -->
|
|
24
|
-
5. **Write `.claude/settings.json`** permissions (`ask`/`deny` lists mirroring the gate, **plus an
|
|
25
|
-
`allow` list of the project's read-only / verification commands** so agents don't stall on
|
|
26
|
-
permission prompts — including mid-workflow, where nobody is watching a prompt: the detected
|
|
27
|
-
per-surface `test_cmd`/`lint_cmd`/`typecheck_cmd`/`build_cmd` **and their `*_quiet_cmd`
|
|
28
|
-
variants** and repo-wide `commands.*` equivalents as `Bash(<cmd>:*)` rules, plus read-only git —
|
|
29
|
-
`Bash(git status:*)`, `Bash(git diff:*)`, `Bash(git log:*)`, `Bash(git rev-parse:*)` — plus the
|
|
30
|
-
shipped pipeline scripts (`Bash(<core>/pipeline/scripts/:*)` — preflight, kanban-move;
|
|
31
|
-
a prefix rule matches a command *starting* with that path, so a `bash <path>`
|
|
32
|
-
invocation would need its own entry), and the
|
|
33
|
-
retrieval provider's MCP tools when wired (e.g. `mcp__serena`). Never allowlist anything matching
|
|
34
|
-
a `gate.ask`/`gate.deny` pattern. Mention the human can widen it later with
|
|
35
|
-
`/fewer-permission-prompts`) + the hooks, **conditioned on the install mode:**
|
|
36
|
-
- **bundled:** register the PreToolUse hook `<core>/hooks/gate.py` with matcher `Bash|Task`
|
|
37
|
-
(Task is required — the preflight phase gate keys off Task dispatches; a `Bash`-only matcher
|
|
38
|
-
leaves it dead) and the PostToolUse formatter (detected formatter). Before adding, drop any
|
|
39
|
-
existing PreToolUse entry whose command ends in `gate.py` (here AND in `~/.claude/settings.json`
|
|
40
|
-
if one points at this repo's copy) — exactly one registration must survive, or every gated
|
|
41
|
-
command prompts twice.
|
|
42
|
-
- **global:** the PreToolUse gate hook is
|
|
43
|
-
already in `~/.claude/settings.json` and reads this repo's `gate-config.json` — do **not** re-register
|
|
44
|
-
it here (double-registration double-prompts). It no-ops where its config is absent, so one
|
|
45
|
-
registration serves every repo; you only supply this repo's `gate-config.json`. Still write the
|
|
46
|
-
PostToolUse formatter hook + the permissions.
|
|
47
|
-
Preserve any existing custom keys.
|
|
48
|
-
<!-- cohorte:else -->
|
|
49
|
-
5. **No permissions or hook file to write.** This runtime has neither a PreToolUse hook nor a
|
|
50
|
-
settings.json permission model the pipeline can generate, so the gate is enforced by the
|
|
51
|
-
commands calling `<core>/hooks/gate.py --check` at the points the profile marks gated. That
|
|
52
|
-
means `<state>/gate-config.json` (step 4) is not decoration here — it is the ONLY place the
|
|
53
|
-
deny/ask patterns live, so fill it from the profile exactly and do not skip it. If this repo is
|
|
54
|
-
also driven from Claude Code, that install's hook reads the same file; nothing to duplicate.
|
|
55
|
-
<!-- cohorte:endif -->
|
|
56
|
-
6. **Wire the retrieval provider** (skip if `retrieval.provider: none`):
|
|
57
|
-
- **serena:** if the `serena` CLI is missing, have the human install it (`uv tool install -p 3.13
|
|
58
|
-
serena-agent`) — or set the provider to `none` if they decline, and say `/cohorte-update-pipeline` can wire
|
|
59
|
-
it later. If the binary exists (e.g. `~/.local/bin/serena`) but `command -v serena` fails,
|
|
60
|
-
recommend the PATH fix (`uv tool update-shell`, or add `~/.local/bin` to the shell profile) for
|
|
61
|
-
CLI use. Then register at **project scope** (committed `.mcp.json`, portable —
|
|
62
|
-
`--project-from-cwd` resolves the project at server start) using the **PATH-proof launcher**
|
|
63
|
-
from SCHEMA.md §Code retrieval (`sh -c 'exec "$(command -v serena || echo
|
|
64
|
-
"$HOME/.local/bin/serena")" start-mcp-server …'` — a bare `serena` entry dies with ENOENT when
|
|
65
|
-
Claude Code was launched from an environment without `~/.local/bin` on PATH; Windows-native
|
|
66
|
-
teams use the bare form + PATH instead). If `.mcp.json` already has a bare `serena` entry,
|
|
67
|
-
upgrade it to the launcher form rather than skipping. Add `.serena/` to the repo's `.gitignore`
|
|
68
|
-
(per-machine cache/config Serena creates on first launch). On a large repo, offer the one-off
|
|
69
|
-
`serena project index`. Finish with the **health check** from SCHEMA.md §Code retrieval and
|
|
70
|
-
report each result — if the static checks pass but the server isn't connected in this session,
|
|
71
|
-
say a session restart is needed; never report Serena wired on registration alone.
|
|
72
|
-
- **graphify:** have the human install it (`uv tool install graphify` then `graphify install`),
|
|
73
|
-
build the initial graph (`/graphify .`), and note it needs incremental rescans (`--update`) after
|
|
74
|
-
big changes.
|
|
75
|
-
- Either way the rendered agents already carry the provider's MCP tools in their `tools:` list
|
|
76
|
-
(step 3 / SCHEMA §Rendering); remind the human the new MCP server appears after a session restart.
|
|
77
|
-
7. **Render the isolation scripts** (if `isolation.enabled`) from the installer's
|
|
78
|
-
`pipeline/scripts/*.template` to this repo's `scripts/new-feature.sh` and `scripts/remove-feature.sh`,
|
|
79
|
-
substituting the `__TOKENS__` (project
|
|
80
|
-
slug, DB pattern, port bases, compose file, branch prefix, install/dev/migrate commands, per-surface
|
|
81
|
-
env stanzas). `chmod +x` them. If isolation is disabled, skip and note features build in the main checkout.
|
|
82
|
-
8. **Ensure `specs/_template.md`** exists (copy from the installer's `templates/spec.template.md` if missing).
|
|
83
|
-
9. **Write the pointer** `<state>/pipeline.json` (committed — this is how a teammate who clones the repo
|
|
84
|
-
knows which core to install):
|
|
85
|
-
`{ "pipeline": "cohorte", "mode": "<bundled|global>", "core_version": "<contents of the
|
|
86
|
-
installer's pipeline/VERSION>", "install": "<per mode: bundled ⇒ \"npm i -g cohorte && cohorte install\"
|
|
87
|
-
note that the core is committed under .claude/; global ⇒ \"npm i -g cohorte && cohorte install --global\"
|
|
88
|
-
(or, without npm: curl -fsSL https://raw.githubusercontent.com/TheBidouilleAgency/cohorte/main/install.sh | sh -s -- --global;
|
|
89
|
-
Windows: install.ps1 -Global from the same repo)> " }`.
|
|
90
|
-
In **global** mode also add, near the top of `<memory>`, a one-liner:
|
|
91
|
-
`> Pipeline: global core — run the installer above if /cohorte-brainstorm etc. are missing.`
|
|
92
|
-
10. **CI workflow** (if `vcs.host: github` and no existing workflow already runs the profile's
|
|
93
|
-
checks): with the human's go-ahead, generate `.github/workflows/pipeline-ci.yml` — on
|
|
94
|
-
`pull_request` to `<default_branch>`: checkout, set up the `package_manager` toolchain,
|
|
95
|
-
`commands.install`, then `commands.lint` · `commands.typecheck` · `commands.test` (+ per-surface
|
|
96
|
-
`build_cmd`s that are non-empty). Derive the setup steps from the detected stack — mirror what a
|
|
97
|
-
sibling workflow does if one exists. `/cohorte-ship` watches these checks before the merge.
|
|
98
|
-
11. **Metrics sink, report buffer & preflight stamp:** add `<state>/pipeline-metrics.jsonl` to
|
|
99
|
-
`.gitignore` — `/cohorte-build`, `/cohorte-review` and `/cohorte-fix` append per-dispatch evidence
|
|
100
|
-
there (SCHEMA §Specialization reads it).
|
|
101
|
-
Also add `specs/reports/` — `/cohorte-review` stages its last report there so a `/cohorte-fix` (or
|
|
102
|
-
`/cohorte-spec` Mode B) survives a `/clear`; it's a derived buffer, not a versioned artifact.
|
|
103
|
-
And `<state>/preflight.ok` — a local, per-checkout freshness stamp. Versioning it breaks the phase
|
|
104
|
-
gate for good (a committed stamp describes the tree *before* its own commit, and it rides into every
|
|
105
|
-
new worktree as a green nobody earned). If it is already tracked: `git rm --cached <state>/preflight.ok`.
|
|
106
|
-
12. **Design system:** if `design.enabled` with a snapshot dir, note that `/cohorte-align-ds` is active; else the
|
|
107
|
-
`/cohorte-align-ds` command will no-op with a clear message.
|
|
108
|
-
13. **Kanban** (only if the human opted in at Phase 2): wire it per SCHEMA.md §Kanban, writing into the
|
|
109
|
-
**global** `<config>` (never into this repo — the board points at a
|
|
110
|
-
personal vault). Create the file from `pipeline/cohorte.config.template.yaml` if absent; set
|
|
111
|
-
`kanban.enabled: true` and `obsidian.vault_path` if it was empty. Add a `kanban.boards[<PIPELINE
|
|
112
|
-
name>]` entry with `board: <folder>/Tasks.md`. Then **create the board file**
|
|
113
|
-
`<vault>/<folder>/Tasks.md` (per §Kanban) if it doesn't exist — one column per `kanban.columns`
|
|
114
|
-
stage, in pipeline order. Finally run the §Kanban backfill so any spec already in `specs/` lands on
|
|
115
|
-
the board. Report the board path + the `obsidian://` URI. Skip silently if the human declined.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# /cohorte-init-pipeline · 05 Report
|
|
2
|
-
|
|
3
|
-
### Phase 5 — Report
|
|
4
|
-
|
|
5
|
-
Print: the install mode (bundled core under `.claude/` vs global core in `~/.claude/` + the committed
|
|
6
|
-
`<state>/pipeline.json` pointer), the files written/rendered, the surface→agent mapping, and the
|
|
7
|
-
tailored workflow line, e.g.
|
|
8
|
-
`/cohorte-brainstorm → /cohorte-spec → (design) → /cohorte-build <id> → test → /cohorte-review → /cohorte-ship`. Tell the human to sanity-check
|
|
9
|
-
`PIPELINE.md`, commit it, and run `/cohorte-brainstorm` to start a feature. Note that this was the one-time
|
|
10
|
-
setup: from now on `/cohorte-update-pipeline` both refreshes the core AND reconciles the generated files
|
|
11
|
-
(SCHEMA.md §Reconcile), and `/cohorte-build` auto-grows surfaces — re-running `/cohorte-init-pipeline` is only for deep
|
|
12
|
-
stack changes (package manager, contract mechanism, surface overhaul).
|
package/core/workflows/audit.js
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
// cohorte — /cohorte-audit as a deterministic workflow (opt-in; the conversational
|
|
2
|
-
// /cohorte-audit command remains the default path and the fallback).
|
|
3
|
-
//
|
|
4
|
-
// Invoke with args = {target: "<path or domain>"} (optional — default whole repo).
|
|
5
|
-
//
|
|
6
|
-
// Shape (SCHEMA.md §Workflows): profile via profile-reader (phase 0), the
|
|
7
|
-
// mechanical gates staged to disk by one haiku agent, then ONE auditor per
|
|
8
|
-
// domain (each surface + `shared`) running concurrently — the runtime caps
|
|
9
|
-
// concurrency at 20 (CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS since 2026-08), extra
|
|
10
|
-
// domains queue — and a merge phase that writes the
|
|
11
|
-
// prioritized specs/refactor-backlog.md. Only the summary comes back.
|
|
12
|
-
|
|
13
|
-
export const meta = {
|
|
14
|
-
name: 'cohorte-audit',
|
|
15
|
-
description: 'Audit the codebase against PIPELINE.md: mechanical gates, one auditor per domain in parallel, prioritized refactor backlog',
|
|
16
|
-
whenToUse: 'Only when the human explicitly asks for the audit workflow. args = {target: "<path or domain>"} — omit for the whole repo.',
|
|
17
|
-
phases: [
|
|
18
|
-
{ title: 'Profile', detail: 'PIPELINE.md → JSON via profile-reader', model: 'haiku' },
|
|
19
|
-
{ title: 'Gates', detail: 'format/lint/typecheck/tests → specs/reports/audit-gates.txt', model: 'haiku' },
|
|
20
|
-
{ title: 'Audit', detail: 'one review-in-audit-mode agent per domain (concurrent, runtime-capped 20)' },
|
|
21
|
-
{ title: 'Backlog', detail: 'merge + write specs/refactor-backlog.md', model: 'haiku' },
|
|
22
|
-
],
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// The Workflow runtime hands `args` to a script verbatim, so a caller that passes a
|
|
26
|
-
// JSON-ENCODED STRING instead of a real object gets that string back here. The old
|
|
27
|
-
// `typeof args === 'string' ? args.trim()` then took the whole blob as the value — which
|
|
28
|
-
// is how a report landed on disk named `specs/reports/{"feature": "x"}.md`, and how
|
|
29
|
-
// maxRounds/smoke were silently dropped on the same run. Parse it back into the object
|
|
30
|
-
// it was meant to be; a bare slug stays valid shorthand.
|
|
31
|
-
const ARGS = (() => {
|
|
32
|
-
if (typeof args === 'string') {
|
|
33
|
-
const t = args.trim()
|
|
34
|
-
if (t.startsWith('{')) {
|
|
35
|
-
try { const o = JSON.parse(t); if (o && typeof o === 'object' && !Array.isArray(o)) return o } catch {}
|
|
36
|
-
}
|
|
37
|
-
return { feature: t, target: t }
|
|
38
|
-
}
|
|
39
|
-
return args && typeof args === 'object' ? args : {}
|
|
40
|
-
})()
|
|
41
|
-
const target = ARGS.target || ''
|
|
42
|
-
|
|
43
|
-
// The profile-reader returns through a StructuredOutput tool call, and a haiku agent
|
|
44
|
-
// intermittently nests the whole profile as a JSON *string* under a single wrapper field
|
|
45
|
-
// ({"output": "{\"surfaces\": …}"}) instead of putting the profile's keys at the top level.
|
|
46
|
-
// The schema here used to be {type:'object', additionalProperties:true} — no declared
|
|
47
|
-
// properties, no required keys — so that wrapper validated cleanly and every field then read
|
|
48
|
-
// as undefined: `surfaces` fell back to [], parallel([]) dispatched zero agents, the
|
|
49
|
-
// dead-agent guard had no surfaces to find missing, and the run reported a verdict having
|
|
50
|
-
// done nothing. On the surface it is indistinguishable from a clean run with an empty diff.
|
|
51
|
-
// Declaring the shape gives the tool layer something to validate and the agent something to
|
|
52
|
-
// aim at; unwrapProfile() salvages a wrapped return that still gets through; and the
|
|
53
|
-
// zero-surface abort below makes the silent-success path impossible either way.
|
|
54
|
-
// See also the structured-output section of core/agents/profile-reader.md.
|
|
55
|
-
const PROFILE = {
|
|
56
|
-
type: 'object', additionalProperties: true,
|
|
57
|
-
properties: {
|
|
58
|
-
error: { type: 'string', description: 'set ONLY when PIPELINE.md is missing or unparseable' },
|
|
59
|
-
surfaces: {
|
|
60
|
-
type: 'array',
|
|
61
|
-
description: "one entry per surface, at the TOP LEVEL of this object — never a JSON string",
|
|
62
|
-
items: {
|
|
63
|
-
type: 'object', required: ['key'], additionalProperties: true,
|
|
64
|
-
properties: { key: { type: 'string' }, path: { type: 'string' }, agent: { type: 'string' } },
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Salvage a profile handed back as JSON text rather than as an object — either the whole
|
|
71
|
-
// return, or nested under a single wrapper field. Anything already shaped like a profile
|
|
72
|
-
// (has `surfaces`, or is the documented `{error}` failure shape) passes through untouched.
|
|
73
|
-
const unwrapProfile = p => {
|
|
74
|
-
if (typeof p === 'string') { try { return JSON.parse(p) } catch { return null } }
|
|
75
|
-
if (!p || typeof p !== 'object') return null
|
|
76
|
-
if (Array.isArray(p.surfaces) || p.error) return p
|
|
77
|
-
for (const v of Object.values(p)) {
|
|
78
|
-
if (typeof v !== 'string') continue
|
|
79
|
-
try {
|
|
80
|
-
const inner = JSON.parse(v)
|
|
81
|
-
if (inner && typeof inner === 'object' && !Array.isArray(inner)) return inner
|
|
82
|
-
} catch {}
|
|
83
|
-
}
|
|
84
|
-
return p
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const GATES = {
|
|
88
|
-
type: 'object', required: ['failures'], additionalProperties: false,
|
|
89
|
-
properties: {
|
|
90
|
-
failures: {
|
|
91
|
-
type: 'array', maxItems: 40,
|
|
92
|
-
items: {
|
|
93
|
-
type: 'object', required: ['file', 'line', 'kind', 'summary'], additionalProperties: false,
|
|
94
|
-
properties: {
|
|
95
|
-
file: { type: 'string' }, line: { type: 'integer' },
|
|
96
|
-
kind: { enum: ['lint', 'format', 'type', 'test'] },
|
|
97
|
-
summary: { type: 'string', description: 'one line, no output excerpts' },
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
overflow: { type: 'integer', description: 'failures beyond the 40-item cap' },
|
|
102
|
-
},
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const BACKLOG = {
|
|
106
|
-
type: 'object', required: ['items'], additionalProperties: false,
|
|
107
|
-
properties: {
|
|
108
|
-
items: {
|
|
109
|
-
type: 'array', maxItems: 30,
|
|
110
|
-
items: {
|
|
111
|
-
type: 'object', required: ['severity', 'file', 'line', 'kind', 'fix'], additionalProperties: false,
|
|
112
|
-
properties: {
|
|
113
|
-
severity: { enum: ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] },
|
|
114
|
-
file: { type: 'string' }, line: { type: 'integer' },
|
|
115
|
-
kind: { enum: ['rule', 'tdd', 'lint', 'format', 'type', 'security'] },
|
|
116
|
-
fix: { type: 'string', description: 'one concrete change, one line, no code excerpts' },
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
overflow: { type: 'integer' },
|
|
121
|
-
},
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ── Phase 0 — profile ────────────────────────────────────────────────────────
|
|
125
|
-
phase('Profile')
|
|
126
|
-
const profile = unwrapProfile(await agent(
|
|
127
|
-
'Return this project\'s PIPELINE.md `yaml pipeline-profile` block as JSON, per your instructions.',
|
|
128
|
-
{ agentType: 'profile-reader', label: 'profile', schema: PROFILE, effort: 'low' },
|
|
129
|
-
))
|
|
130
|
-
if (!profile || profile.error) {
|
|
131
|
-
return { error: `profile unreadable: ${(profile && profile.error) || 'profile-reader returned nothing'}` }
|
|
132
|
-
}
|
|
133
|
-
const cmds = profile.commands || {}
|
|
134
|
-
const surfaces = Array.isArray(profile.surfaces) ? profile.surfaces : []
|
|
135
|
-
// A profile with no surfaces cannot do this workflow's work, and every later
|
|
136
|
-
// guard compares against `surfaces` — an empty list makes them all vacuously
|
|
137
|
-
// pass. Fail loudly here instead of finishing with nothing done.
|
|
138
|
-
if (!surfaces.length) {
|
|
139
|
-
return { error: 'profile has no surfaces — nothing would be audited. the `yaml pipeline-profile` block in PIPELINE.md is empty or unparseable, or the profile-reader mis-returned; run /cohorte-doctor' }
|
|
140
|
-
}
|
|
141
|
-
const quiet = (q, full) => (q && !String(q).startsWith('<') ? q : full ? `${full} 2>&1 | tail -40` : '')
|
|
142
|
-
const scope = target || 'the whole repo'
|
|
143
|
-
|
|
144
|
-
// ── Phase 1 — mechanical gates, staged to disk ───────────────────────────────
|
|
145
|
-
phase('Gates')
|
|
146
|
-
const gateCmds = [
|
|
147
|
-
cmds.format ? `${cmds.format} --check` : '',
|
|
148
|
-
quiet(cmds.lint_quiet, cmds.lint),
|
|
149
|
-
cmds.typecheck,
|
|
150
|
-
quiet(cmds.test_quiet, cmds.test),
|
|
151
|
-
].filter(c => c && !String(c).startsWith('<'))
|
|
152
|
-
const gates = await agent(
|
|
153
|
-
`Run the cohorte audit's mechanical gates, scoped to ${scope}. Commands (adapt the format one to the ` +
|
|
154
|
-
`project's check mode if --check is wrong): ${gateCmds.map(c => JSON.stringify(c)).join(' · ')}.\n` +
|
|
155
|
-
'Redirect EVERY command\'s output into specs/reports/audit-gates.txt (append, `cmd >> file 2>&1`) — ' +
|
|
156
|
-
'never print it — and keep going after failures (this is an inventory, not a gate to pass). Then grep ' +
|
|
157
|
-
'the file and return each failure as file/line/kind/one-line summary, capped at 40 (set overflow for the rest).',
|
|
158
|
-
{ model: 'haiku', label: 'gates', schema: GATES, effort: 'low' },
|
|
159
|
-
)
|
|
160
|
-
// A dead gates agent is NOT "zero mechanical failures" — silence would read as the
|
|
161
|
-
// cleanest possible inventory from a phase that never ran (SCHEMA.md §Dead agents).
|
|
162
|
-
// The domain auditors still run (they read the staged gates file, which then simply
|
|
163
|
-
// isn't there), but the run must say the mechanical sweep is uncovered.
|
|
164
|
-
const gatesCovered = gates != null
|
|
165
|
-
const mech = (gates && gates.failures) || []
|
|
166
|
-
log(gatesCovered
|
|
167
|
-
? `Mechanical failures: ${mech.length}${gates.overflow ? ` (+${gates.overflow} overflow)` : ''}`
|
|
168
|
-
: 'the gates agent died — mechanical checks NOT covered this run')
|
|
169
|
-
|
|
170
|
-
// ── Phase 2 — one auditor per domain, concurrent ─────────────────────────────
|
|
171
|
-
// Domains = every surface + `shared` (contract package + anything outside the
|
|
172
|
-
// surface trees). The runtime caps concurrent agents at 20 by default; more domains
|
|
173
|
-
// queue. Raising CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS raises the ceiling, it does not
|
|
174
|
-
// change this script — the queue is the runtime's, and a queued domain is not a lost one.
|
|
175
|
-
phase('Audit')
|
|
176
|
-
const domains = surfaces.map(s => ({ key: s.key, path: s.path }))
|
|
177
|
-
.concat([{ key: 'shared', path: (profile.contract && profile.contract.path) || '(everything outside the surface trees)' }])
|
|
178
|
-
.filter(d => !target || target === d.key || String(d.path).startsWith(target) || target.startsWith(String(d.path)))
|
|
179
|
-
if (!domains.length) return { error: `target "${target}" matches no surface/domain` }
|
|
180
|
-
|
|
181
|
-
const audited = await parallel(domains.map(d => () => agent(
|
|
182
|
-
'Audit a target against PIPELINE.md (no spec — audit mode, per your agent instructions). Check ' +
|
|
183
|
-
'conventions for the domain\'s surface, TDD coverage (untested entry points / modules), and — if the ' +
|
|
184
|
-
'profile enables them — mobile-first + design-system usage. Mechanical findings are already staged: ' +
|
|
185
|
-
'read specs/reports/audit-gates.txt and fold the ones in your domain in. Return the prioritized items, ' +
|
|
186
|
-
'capped at 30, one line each, no code excerpts. — Variable slots: domain: ' +
|
|
187
|
-
`${d.key} · tree: ${d.path}${target ? ` · human-requested target: ${target}` : ''}`,
|
|
188
|
-
{ agentType: 'review', label: `audit:${d.key}`, schema: BACKLOG },
|
|
189
|
-
).then(r => r && { key: d.key, items: r.items, overflow: r.overflow || 0 })))
|
|
190
|
-
const perDomain = audited.filter(Boolean)
|
|
191
|
-
// A dead auditor returns null, and a domain with no result is indistinguishable
|
|
192
|
-
// from a domain with nothing to report — the backlog would simply omit it and the
|
|
193
|
-
// human would read that as "clean". Name them instead.
|
|
194
|
-
const deadDomains = domains.filter(d => !perDomain.some(p => p.key === d.key)).map(d => d.key)
|
|
195
|
-
if (deadDomains.length) log(`Auditor died on: ${deadDomains.join(', ')} — those domains are NOT audited`)
|
|
196
|
-
|
|
197
|
-
// ── Phase 3 — merge + write the backlog ──────────────────────────────────────
|
|
198
|
-
phase('Backlog')
|
|
199
|
-
const SEV = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 }
|
|
200
|
-
const body = ['# Refactor backlog', '', `> Generated by the cohorte-audit workflow (scope: ${scope}).`]
|
|
201
|
-
if (deadDomains.length) {
|
|
202
|
-
body.push('', `> ⚠ NOT audited (the auditor died): ${deadDomains.join(', ')} — absence of items below`,
|
|
203
|
-
'> for those domains means "not looked at", not "clean". Re-run the audit for them.')
|
|
204
|
-
}
|
|
205
|
-
if (!gatesCovered) {
|
|
206
|
-
body.push('', '> ⚠ Mechanical gates NOT run (the gates agent died) — lint/typecheck/test failures',
|
|
207
|
-
'> are uncounted below. Re-run the audit for the mechanical sweep.')
|
|
208
|
-
}
|
|
209
|
-
let total = 0
|
|
210
|
-
for (const d of perDomain) {
|
|
211
|
-
const items = [...d.items].sort((a, b) => SEV[a.severity] - SEV[b.severity])
|
|
212
|
-
total += items.length
|
|
213
|
-
body.push('', `## ${d.key}`, '')
|
|
214
|
-
for (const it of items) body.push(`- [ ] ${it.severity} · ${it.file}:${it.line} · ${it.kind} · ${it.fix}`)
|
|
215
|
-
if (d.overflow) body.push(`- [ ] (+${d.overflow} more beyond the cap — re-audit ${d.key} after this pass)`)
|
|
216
|
-
}
|
|
217
|
-
const written = await agent(
|
|
218
|
-
`Write EXACTLY this content to specs/refactor-backlog.md (overwrite), then return the single word done:\n<<<BACKLOG\n${body.join('\n')}\nBACKLOG`,
|
|
219
|
-
{ model: 'haiku', label: 'write-backlog', effort: 'low' },
|
|
220
|
-
)
|
|
221
|
-
// Returning `backlog: <path>` when the writer died points /cohorte-refactor at a file
|
|
222
|
-
// that does not exist (or, worse, at the PREVIOUS run's stale backlog).
|
|
223
|
-
const backlogOk = written != null && /done/i.test(String(written))
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
backlog: backlogOk ? 'specs/refactor-backlog.md' : '(NOT written — the backlog writer died)',
|
|
227
|
-
notAudited: deadDomains, // absence of findings here means "not looked at"
|
|
228
|
-
mechanicalFailures: gatesCovered ? mech.length : null, // null = gates agent died, NOT zero
|
|
229
|
-
gatesCovered,
|
|
230
|
-
domains: Object.fromEntries(perDomain.map(d => [d.key, d.items.length + (d.overflow || 0)])),
|
|
231
|
-
total,
|
|
232
|
-
top: perDomain.flatMap(d => d.items.map(it => ({ ...it, domain: d.key })))
|
|
233
|
-
.sort((a, b) => SEV[a.severity] - SEV[b.severity]).slice(0, 10)
|
|
234
|
-
.map(it => `[${it.severity}] ${it.domain} · ${it.file}:${it.line} — ${it.fix}`),
|
|
235
|
-
next: !backlogOk
|
|
236
|
-
? 'the backlog was NEVER written (writer died) — the counts above are real but nothing is on disk; re-run the audit'
|
|
237
|
-
: deadDomains.length
|
|
238
|
-
? `re-audit ${deadDomains.join(', ')} (auditor died — not covered), then /cohorte-refactor <domain>`
|
|
239
|
-
: 'refactor a domain with /cohorte-refactor <domain> (or the refactor workflow for big domains)',
|
|
240
|
-
}
|