cohorte 2.2.0 → 2.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 +87 -0
- package/README.md +8 -9
- package/bin/cli.js +8 -2
- package/core/adapter/render.js +2 -2
- package/core/agents/implementer.template.md +27 -0
- package/core/agents/release.md +6 -2
- package/core/agents/review.md +35 -4
- package/core/commands/cohorte-audit.md +8 -3
- package/core/commands/cohorte-brainstorm.md +0 -5
- package/core/commands/cohorte-build.md +24 -7
- package/core/commands/cohorte-doctor.md +4 -10
- package/core/commands/cohorte-fix.md +1 -2
- package/core/commands/cohorte-patch.md +113 -0
- package/core/commands/cohorte-review.md +1 -3
- package/core/commands/cohorte-ship.md +8 -9
- package/core/commands/cohorte-spec.md +1 -6
- package/core/commands/cohorte-update-pipeline.md +5 -4
- package/core/templates/patch.template.md +86 -0
- package/core/templates/steps/init-pipeline/02-interview-gaps.md +0 -10
- package/core/templates/steps/init-pipeline/04-write-render.md +2 -2
- package/core/workflows/review.js +3 -5
- package/install.ps1 +3 -1
- package/install.sh +6 -2
- package/package.json +2 -2
- package/profile/PIPELINE.template.md +1 -0
- package/profile/SCHEMA.md +28 -63
- package/profile/cohorte.config.template.yaml +0 -16
- package/scripts/validate-core.mjs +25 -23
- package/scripts/telemetry-send.sh +0 -84
|
@@ -31,7 +31,10 @@ You are the **lead**. Ship feature **$ARGUMENTS**. This is the outward-facing ga
|
|
|
31
31
|
§`Acceptance criteria / DoD`; if any item is still `- [ ]`, list the open ones and ask the human to
|
|
32
32
|
confirm shipping anyway (they may be deferred on purpose — e.g. a UI item on a backend-only feature).
|
|
33
33
|
All `- [x]` ⇒ proceed silently.
|
|
34
|
-
- Show `git status` + `git diff --stat`; confirm the branch is `<
|
|
34
|
+
- Show `git status` + `git diff --stat`; confirm the branch is `<prefix>$ARGUMENTS`, where `<prefix>`
|
|
35
|
+
is `vcs.patch_branch_prefix` (falling back to `fix/` on a profile that predates the key) when the
|
|
36
|
+
spec front-matter carries `kind: patch`, and `vcs.feature_branch_prefix` otherwise. Resolve it once
|
|
37
|
+
here and pass the **literal** branch to §3's dispatch — the release agent must not re-derive it.
|
|
35
38
|
- **Ask the human to confirm** they want to commit, push, and open the PR. Wait for yes.
|
|
36
39
|
- After the yes: `<core>/pipeline/scripts/kanban-move.sh auto $ARGUMENTS ship`. Report what it
|
|
37
40
|
printed — `moved #…` or `kanban: <reason>` — never a guess about which happened.
|
|
@@ -57,6 +60,9 @@ See SCHEMA.md §Release notes.
|
|
|
57
60
|
`release_notes.forbid_levels` (a `0.x` repo forbidding `major` declares the rupture `minor`).
|
|
58
61
|
- **Ambiguous between two defensible levels?** State your reading in one line and **ask the human to
|
|
59
62
|
pick** before writing. A wrong bump becomes a published version number.
|
|
63
|
+
- A `kind: patch` spec is a `patch` bump by default — that is what the level means. It is a default,
|
|
64
|
+
not a rule: a bug fix that changes documented behaviour is still a `minor`, and one that removes it
|
|
65
|
+
is still breaking. Say which you chose when it isn't `patch`.
|
|
60
66
|
- The body describes what changed **for the user**, from the spec §1/§2 — no client names, no internal
|
|
61
67
|
paths, no exploitable attack vector, no file lists.
|
|
62
68
|
- If the feature genuinely must move no version, use `release_notes.empty_cmd` instead. Prefer that to
|
|
@@ -73,7 +79,7 @@ See SCHEMA.md §Release notes.
|
|
|
73
79
|
|
|
74
80
|
Spawn one agent (`subagent_type: release`, or the equivalent dispatch for this runtime):
|
|
75
81
|
"Release feature `$ARGUMENTS` on branch
|
|
76
|
-
`<
|
|
82
|
+
`<the branch §1 resolved>`. Read `PIPELINE.md` §vcs first. Spec: `specs/$ARGUMENTS.md` (already
|
|
77
83
|
`status: shipped` — stage it). Write conventional commit(s), push (no force), open the PR (use `gh` if
|
|
78
84
|
`host: github` + available; else emit the compare URL + drafted PR body from `<core>/templates/pr-body.md`).
|
|
79
85
|
Stage **all** the feature's changes including `specs/$ARGUMENTS.md` and, if `release_notes.enabled`, the
|
|
@@ -101,13 +107,6 @@ with a **grep for `#$ARGUMENTS`** on the board it named (with surrounding headin
|
|
|
101
107
|
`grep -B20 '#$ARGUMENTS' | grep '^##'`, or an offset-limited Read around the match): exactly one card,
|
|
102
108
|
under the `shipped` heading — never re-read the whole board into context.
|
|
103
109
|
|
|
104
|
-
**Telemetry — the usage ping that closes the funnel.** Chain it onto the verify call above
|
|
105
|
-
(`/cohorte-build` §4's shared form, `<phase>` = `ship`, `<seconds>` = `0` — the release agent's duration is
|
|
106
|
-
not the pipeline's, `<results>` = `pr` when a PR was created / `compare` when only a compare URL was
|
|
107
|
-
emitted). Fire it **after** the release agent reports success, never on an aborted ship — a `ship`
|
|
108
|
-
event must mean the feature actually left the pipeline. No board ⇒ still ping, in its own `|| true`
|
|
109
|
-
call. Silent no-op without consent; never ask about consent here.
|
|
110
|
-
|
|
111
110
|
## 5. After the PR — CI gate + teardown
|
|
112
111
|
|
|
113
112
|
- If `host: github` and `gh` is available, watch the PR's checks (`gh pr checks <url> --watch`) and
|
|
@@ -51,12 +51,7 @@ Detect the mode from the pasted content:
|
|
|
51
51
|
it. This is just so the human isn't surprised when `/cohorte-build` proposes a new agent.
|
|
52
52
|
5. When the human validates, **freeze**: write `specs/<id>.md` (`status: frozen`, front-matter filled).
|
|
53
53
|
Create the file — do not ask the human to. **Postcondition:** `grep -q '^status: frozen' specs/<id>.md`
|
|
54
|
-
— if it fails the freeze didn't land; fix it before pointing the human at `/cohorte-build`.
|
|
55
|
-
opt-in usage ping onto the postcondition's Bash call (`/cohorte-build` §4's shared form, `<phase>` =
|
|
56
|
-
`spec`, `<seconds>` = `0` — interactive time, not pipeline wall-clock, `<results>` = `frozen`).
|
|
57
|
-
Ping only on a **landed** freeze, so the funnel counts specs that exist, not attempts. Mode B does
|
|
58
|
-
not ping — it re-enters an already-counted spec, and `/cohorte-fix` covers that loop. Silent no-op without
|
|
59
|
-
consent; never ask about consent here.
|
|
54
|
+
— if it fails the freeze didn't land; fix it before pointing the human at `/cohorte-build`.
|
|
60
55
|
5b. **Record the transverse decisions — the journal, not a summary.** Walk what the interview settled
|
|
61
56
|
and keep ONLY the decisions that **outlive this feature**: a rule the next spec would otherwise
|
|
62
57
|
re-litigate or contradict (auth model, id/naming scheme, where a kind of state lives, an error
|
|
@@ -98,10 +98,11 @@ Four of the §Reconcile steps matter specifically here:
|
|
|
98
98
|
|
|
99
99
|
- **Global config seed** (§Reconcile step 5): if `<config>` is absent, seed it
|
|
100
100
|
from the template so the kanban + shared-vault config has a home. Never clobber an existing filled
|
|
101
|
-
file. Report what was seeded.
|
|
102
|
-
(
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
file. Report what was seeded. Then **scrub the retired `telemetry:` block** if the existing file
|
|
102
|
+
still has one (every install seeded before 2.3.0 does) — one targeted Edit deleting the block and
|
|
103
|
+
its comment header, nothing else touched. That capability was removed in 2.3.0, sender included,
|
|
104
|
+
so the block is dead config: nothing reads it, and an `enabled: true` left sitting in a file the
|
|
105
|
+
human may open reads as though data were still leaving the machine. Say you removed it.
|
|
105
106
|
- **Kanban sync** (§Reconcile step 6): resolve this project's board with
|
|
106
107
|
`<core>/pipeline/scripts/kanban-move.sh --check` — it prints either the board path or the exact
|
|
107
108
|
missing link. **Not linked** → offer to link/create a board (confirm the vault + `<folder>/Tasks.md`,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
feature_id: patch-<slug>
|
|
3
|
+
kind: patch # a bug fix, not a feature — /cohorte-build skips contract authoring, /cohorte-ship commits `fix(…)`
|
|
4
|
+
title: <one line: the bug, from the user's side>
|
|
5
|
+
status: draft # draft → frozen → in-progress → in-review → shipped · blocked (see SCHEMA.md §Spec status)
|
|
6
|
+
severity: <critical | major | minor> # drives nothing mechanical; it is what the human triages on
|
|
7
|
+
branch: <patch_branch_prefix><feature_id>
|
|
8
|
+
created: <YYYY-MM-DD>
|
|
9
|
+
reviewed_base: # merge-base sha at the last SHIP verdict — freshness-gate anchor (written by /cohorte-review)
|
|
10
|
+
reviewed_digest: # sha256 (16 hex) of the reviewed source diff vs reviewed_base, specs excluded — /cohorte-ship re-checks
|
|
11
|
+
design_files: [] # omit unless the fix is itself visual
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# <one line: the bug, from the user's side>
|
|
15
|
+
|
|
16
|
+
## 1. Symptom & repro
|
|
17
|
+
|
|
18
|
+
> What the user sees, and the shortest deterministic path to see it. A repro nobody can run is a
|
|
19
|
+
> hypothesis, not a bug — say so here rather than pretending otherwise.
|
|
20
|
+
|
|
21
|
+
- **Observed:** …
|
|
22
|
+
- **Expected:** …
|
|
23
|
+
- **Repro:** 1. … 2. … 3. …
|
|
24
|
+
- **Since / trigger:** <version, commit, or "unknown">
|
|
25
|
+
|
|
26
|
+
## 2. Impact
|
|
27
|
+
|
|
28
|
+
> Who is affected, how often, and whether there is a workaround. This is what justifies the patch
|
|
29
|
+
> jumping the feature queue — one or two lines.
|
|
30
|
+
|
|
31
|
+
## 3. Cause
|
|
32
|
+
|
|
33
|
+
> Confirmed or hypothesis — label which. Name the `file:line` if you have it. If the cause is
|
|
34
|
+
> unknown at freeze, say so: the implementer's first job is then to find it, and the review will
|
|
35
|
+
> check that the stated cause matches the diff.
|
|
36
|
+
|
|
37
|
+
## 4. Regression test (red first)
|
|
38
|
+
|
|
39
|
+
> The heart of a patch spec — it replaces §5 CONTRACT as the thing the diff is checked against.
|
|
40
|
+
> Name the test file and what it asserts. It must fail on the current code and pass after the fix.
|
|
41
|
+
|
|
42
|
+
- **Test:** `<path/to/test>` · asserts: …
|
|
43
|
+
- **Fails today because:** …
|
|
44
|
+
|
|
45
|
+
## 5. Contract delta
|
|
46
|
+
|
|
47
|
+
> Usually `none`. A fix that *changes* an existing contract entry describes the change here and
|
|
48
|
+
> `/cohorte-build` propagates it to every surface that names it. A fix that needs **new** contract
|
|
49
|
+
> surface area is not a patch — stop and run `/cohorte-spec` instead.
|
|
50
|
+
|
|
51
|
+
`none`
|
|
52
|
+
|
|
53
|
+
## 6. Surface tasks
|
|
54
|
+
|
|
55
|
+
> One `###` subsection per surface the fix touches — a patch may legitimately span several (an
|
|
56
|
+
> unvalidated field on the API *and* the form that sends it). Each TDD: the §4 test first.
|
|
57
|
+
|
|
58
|
+
### <surface.key>
|
|
59
|
+
|
|
60
|
+
- …
|
|
61
|
+
|
|
62
|
+
## 7. Out of scope
|
|
63
|
+
|
|
64
|
+
> The adjacent things this patch deliberately does NOT fix. This is the section that keeps a bug fix
|
|
65
|
+
> from turning into a refactor — the reviewer reads it before flagging what you left alone. Real but
|
|
66
|
+
> out-of-scope findings belong in `specs/refactor-backlog.md`, not in this diff.
|
|
67
|
+
|
|
68
|
+
- …
|
|
69
|
+
|
|
70
|
+
## 9. Acceptance criteria / DoD
|
|
71
|
+
|
|
72
|
+
> §9, not §8 — the number is load-bearing, so don't renumber it. `review.md` and
|
|
73
|
+
> `implementer.template.md` both name "contract §5, acceptance §9" verbatim, so a patch spec pins
|
|
74
|
+
> those two sections to the feature spec's numbers and just has no §8 (the feature spec has no §7 —
|
|
75
|
+
> a gap in the sequence is normal here).
|
|
76
|
+
|
|
77
|
+
- [ ] The §4 regression test exists, and failed before the fix
|
|
78
|
+
- [ ] Each touched surface's tests (TDD) green
|
|
79
|
+
- [ ] `PIPELINE.md` commands.lint · typecheck · test green
|
|
80
|
+
- [ ] The repro in §1 no longer reproduces
|
|
81
|
+
- [ ] Nothing outside §6's surfaces changed
|
|
82
|
+
|
|
83
|
+
## Remediation
|
|
84
|
+
|
|
85
|
+
> Filled by `/cohorte-fix` from a REVIEW REPORT; empty otherwise. Each item:
|
|
86
|
+
> `[ ] <SEVERITY> · <file:line> · <spec-violation|quality|security> · <concrete fix>`
|
|
@@ -53,13 +53,3 @@ Prefer sensible defaults from Phase 1 as the first (Recommended) option in each
|
|
|
53
53
|
> The **kanban** link is user-scoped (it points at a personal vault, so it never goes in the committed
|
|
54
54
|
> `PIPELINE.md`), but IS wired here because it is per-project — see Phase 4.
|
|
55
55
|
|
|
56
|
-
- **Telemetry** (optional, machine-scoped — SKIP entirely if `<config>` already
|
|
57
|
-
has a `telemetry:` block with a `consent_date`, i.e. the human already answered on this machine).
|
|
58
|
-
Ask ONE opt-in question, stating exactly: _"Share anonymous usage stats with the cohorte project?
|
|
59
|
-
One ping per pipeline phase, `/cohorte-brainstorm` through `/cohorte-ship`: core version, OS, phase name, duration,
|
|
60
|
-
per-surface result counts, and a hash of the feature id — never repo names, paths, code, or IPs.
|
|
61
|
-
Setup and maintenance commands never ping. Off by default; withdraw anytime
|
|
62
|
-
(`telemetry.enabled: false`); erase your history anytime (SCHEMA.md §Telemetry). Default: No."_
|
|
63
|
-
On **yes**: in the global config set `telemetry.enabled: true`, mint `install_id` (`uuidgen`,
|
|
64
|
-
lowercase), set `consent_date` (ISO date). On **no**: set `enabled: false` + `consent_date` (so
|
|
65
|
-
no future command re-asks). Never touch `endpoint` — it ships with the template.
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
per-surface `test_cmd`/`lint_cmd`/`typecheck_cmd`/`build_cmd` **and their `*_quiet_cmd`
|
|
28
28
|
variants** and repo-wide `commands.*` equivalents as `Bash(<cmd>:*)` rules, plus read-only git —
|
|
29
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
|
-
|
|
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
32
|
invocation would need its own entry), and the
|
|
33
33
|
retrieval provider's MCP tools when wired (e.g. `mcp__serena`). Never allowlist anything matching
|
|
34
34
|
a `gate.ask`/`gate.deny` pattern. Mention the human can widen it later with
|
package/core/workflows/review.js
CHANGED
|
@@ -125,7 +125,7 @@ const FINDING = {
|
|
|
125
125
|
properties: {
|
|
126
126
|
severity: { enum: ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] },
|
|
127
127
|
file: { type: 'string' }, line: { type: 'integer' },
|
|
128
|
-
kind: { enum: ['spec-violation', 'quality', 'security'] },
|
|
128
|
+
kind: { enum: ['spec-violation', 'quality', 'security', 'complexity'] },
|
|
129
129
|
problem: { type: 'string', description: 'one line, no code excerpts' },
|
|
130
130
|
fix: { type: 'string', description: 'one concrete change, one line' },
|
|
131
131
|
},
|
|
@@ -141,7 +141,7 @@ const DEFERRED = {
|
|
|
141
141
|
properties: {
|
|
142
142
|
severity: { enum: ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] },
|
|
143
143
|
file: { type: 'string' }, line: { type: 'integer' },
|
|
144
|
-
kind: { enum: ['quality', 'security', 'rule'] },
|
|
144
|
+
kind: { enum: ['quality', 'security', 'rule', 'complexity'] },
|
|
145
145
|
problem: { type: 'string', description: 'one line, no code excerpts' },
|
|
146
146
|
fix: { type: 'string', description: 'one concrete change, one line' },
|
|
147
147
|
outOfScope: { type: 'string', description: 'one line: why this feature does not own it' },
|
|
@@ -323,12 +323,10 @@ const staging = await agent(
|
|
|
323
323
|
`1. Write EXACTLY this content to specs/reports/${feature}.md (overwrite):\n<<<REPORT\n${reportBody}\nREPORT\n` +
|
|
324
324
|
`2. Append one line to $(dirname "$(git rev-parse --git-common-dir)")/.claude/pipeline-metrics.jsonl: ` +
|
|
325
325
|
`{"ts":"<ISO now>","feature":"${feature}","phase":"review","seconds":0,"surfaces":{${results.map(r => `"${r.key}":"${verdict}:${r.kept.length}"`).join(',')}}}\n` +
|
|
326
|
-
`3. Chain the opt-in usage ping: <core>/pipeline/scripts/telemetry-send.sh review "${feature}" 0 "${verdict}:${kept.length}" || true ` +
|
|
327
|
-
'(<core> = .claude if .claude/pipeline/scripts/telemetry-send.sh exists, else ~/.claude; script on neither ⇒ skip the ping).\n' +
|
|
328
326
|
// Deferred findings must land in the backlog on EVERY verdict — parked only on a
|
|
329
327
|
// SHIP is parked nowhere the rest of the time, which is the leak this closes.
|
|
330
328
|
(deferredAll.length
|
|
331
|
-
? `
|
|
329
|
+
? `3. Route the deferred findings to specs/refactor-backlog.md (create it if absent): for each line below, ` +
|
|
332
330
|
`append it under the \`## <domain>\` heading named in its prefix (create that heading if absent) — with \`>>\`, ` +
|
|
333
331
|
`never by rewriting the file, and skip any whose file path + first words already appear there (grep -F first, ` +
|
|
334
332
|
`they may be left from a prior round or an /cohorte-audit):\n` +
|
package/install.ps1
CHANGED
|
@@ -172,8 +172,10 @@ cohorte needs Node >= 18 to install.
|
|
|
172
172
|
Copy-Item (Join-Path $src 'profile\cohorte.config.template.yaml') (Join-Path $dest 'pipeline') -Force
|
|
173
173
|
Copy-Item (Join-Path $src 'scripts\*.template') (Join-Path $dest 'pipeline\scripts') -Force
|
|
174
174
|
Copy-Item (Join-Path $src 'scripts\kanban-move.sh') (Join-Path $dest 'pipeline\scripts') -Force
|
|
175
|
-
Copy-Item (Join-Path $src 'scripts\telemetry-send.sh') (Join-Path $dest 'pipeline\scripts') -Force
|
|
176
175
|
Copy-Item (Join-Path $src 'scripts\preflight.sh') (Join-Path $dest 'pipeline\scripts') -Force
|
|
176
|
+
# 2.3.0 removed telemetry; copy-over never deletes, so scrub the sender from existing
|
|
177
|
+
# installs. The dead `telemetry:` config block is deleted by /cohorte-update-pipeline.
|
|
178
|
+
Remove-Item (Join-Path $dest 'pipeline\scripts\telemetry-send.sh') -Force -ErrorAction SilentlyContinue
|
|
177
179
|
Copy-Item (Join-Path $src 'core\agents\implementer.template.md') (Join-Path $dest 'pipeline') -Force
|
|
178
180
|
if (Test-Path (Join-Path $src 'CHANGELOG.md')) { Copy-Item (Join-Path $src 'CHANGELOG.md') (Join-Path $dest 'pipeline') -Force }
|
|
179
181
|
[System.IO.File]::WriteAllText((Join-Path $dest 'pipeline\VERSION'), "$ver`n", [System.Text.UTF8Encoding]::new($false))
|
package/install.sh
CHANGED
|
@@ -126,10 +126,14 @@ copy_core() {
|
|
|
126
126
|
cp "$src/profile/cohorte.config.template.yaml" "$dest/pipeline/"
|
|
127
127
|
cp "$src"/scripts/*.template "$dest/pipeline/scripts/"
|
|
128
128
|
cp "$src/scripts/kanban-move.sh" "$dest/pipeline/scripts/"
|
|
129
|
-
cp "$src/scripts/telemetry-send.sh" "$dest/pipeline/scripts/"
|
|
130
129
|
cp "$src/scripts/preflight.sh" "$dest/pipeline/scripts/"
|
|
131
|
-
chmod +x "$dest/pipeline/scripts/kanban-move.sh"
|
|
130
|
+
chmod +x "$dest/pipeline/scripts/kanban-move.sh" \
|
|
132
131
|
"$dest/pipeline/scripts/preflight.sh" 2>/dev/null || true
|
|
132
|
+
# 2.3.0 removed telemetry. Copy-over never deletes, so an existing install would keep an
|
|
133
|
+
# executable that still POSTs to the collector — scrub the script itself. The dead
|
|
134
|
+
# `telemetry:` block in the user's config is not this installer's to parse; the interactive
|
|
135
|
+
# /cohorte-update-pipeline deletes it (SCHEMA.md §Reconcile step 5).
|
|
136
|
+
rm -f "$dest/pipeline/scripts/telemetry-send.sh"
|
|
133
137
|
cp "$src/core/agents/implementer.template.md" "$dest/pipeline/"
|
|
134
138
|
[ -f "$src/CHANGELOG.md" ] && cp "$src/CHANGELOG.md" "$dest/pipeline/"
|
|
135
139
|
printf '%s\n' "$ver" > "$dest/pipeline/VERSION"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cohorte",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Portable, stack-agnostic multi-agent development pipeline for Claude Code, Codex CLI, Cursor, Gemini CLI and OpenCode
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "Portable, stack-agnostic multi-agent development pipeline for Claude Code, Codex CLI, Cursor, Gemini CLI and OpenCode — install the core, run /cohorte-init-pipeline, and it adapts to your project's stack.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cohorte": "bin/cli.js"
|
|
7
7
|
},
|
|
@@ -25,6 +25,7 @@ vcs:
|
|
|
25
25
|
remote: <owner/repo> # e.g. acme/webapp
|
|
26
26
|
default_branch: <main>
|
|
27
27
|
feature_branch_prefix: feature/ # branch = <prefix><feature_id>
|
|
28
|
+
patch_branch_prefix: fix/ # same, for /cohorte-patch specs (kind: patch)
|
|
28
29
|
|
|
29
30
|
# ── repo shape ──────────────────────────────────────────────────────────────
|
|
30
31
|
repo:
|
package/profile/SCHEMA.md
CHANGED
|
@@ -16,6 +16,7 @@ generic pipeline uses it, so a stateless agent can read/regenerate the profile c
|
|
|
16
16
|
| `vcs.remote` | string | release | `owner/repo` for the PR/compare URL. |
|
|
17
17
|
| `vcs.default_branch` | string | build, review, release | Base branch for diffs + PRs. |
|
|
18
18
|
| `vcs.feature_branch_prefix` | string | ship, isolation script | `feature/` → branch `feature/<id>`. |
|
|
19
|
+
| `vcs.patch_branch_prefix` | string | ship | Same, for a `kind: patch` spec: `fix/` → branch `fix/patch-<slug>`. Optional — a profile that predates it falls back to `fix/`. |
|
|
19
20
|
| `repo.layout` | enum | build, audit | `monorepo` (many surfaces) or `single`. |
|
|
20
21
|
| `repo.workspace_tool` | enum | audit | `turborepo`/`nx`/`none`. |
|
|
21
22
|
| `retrieval.provider` | enum | init, update-pipeline, implementer | `serena` (default) / `graphify` / `none` — see §Code retrieval. |
|
|
@@ -190,11 +191,6 @@ to log it. For what's EXPENSIVE, use Claude Code's own accounting:
|
|
|
190
191
|
over the last 24 h / 7 d (e.g. _"Top subagents: frontend 7 %, backend 4 % · Top skills: /cohorte-build 1 %,
|
|
191
192
|
/cohorte-review 1 %"_). That IS the per-phase ledger — approximate (share-of-total, machine-local, not exact
|
|
192
193
|
tokens). Read it to see which surface/command actually dominates the bill before you tune a `model` tier.
|
|
193
|
-
- **OpenTelemetry** (exact numbers + dashboards) — add an `env` block to `this runtime's settings file`:
|
|
194
|
-
`{"env":{"CLAUDE_CODE_ENABLE_TELEMETRY":"1","OTEL_METRICS_EXPORTER":"otlp","OTEL_EXPORTER_OTLP_PROTOCOL":"http/protobuf","OTEL_EXPORTER_OTLP_ENDPOINT":"http://localhost:4318"}}`
|
|
195
|
-
and point it at a collector. Metrics `claude_code.token.usage` + `claude_code.cost.usage` carry
|
|
196
|
-
`session.id` + model + type (input/output/cacheRead). Subagent tokens roll into the session total;
|
|
197
|
-
per-subagent attribution needs traces (`CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1`, beta).
|
|
198
194
|
|
|
199
195
|
**Lead context discipline — the silent bill.** The lead session's conversation history is re-sent as
|
|
200
196
|
input on EVERY turn; a session that spans spec→build→review→fix without clearing re-pays the
|
|
@@ -246,6 +242,25 @@ built-in autonomous driver (`/cohorte-loop`) was retired in 2.2.0, and the human
|
|
|
246
242
|
them, and because anything automating the cycle from outside needs somewhere to record "a round is
|
|
247
243
|
under way" and "a round gave up". Every reader still routes on them; nothing produces them.
|
|
248
244
|
|
|
245
|
+
**`kind` — feature (default) or `patch`.** Orthogonal to `status`, and the only other front-matter
|
|
246
|
+
field commands route on. `/cohorte-patch` freezes `specs/patch-<slug>.md` with `kind: patch` from
|
|
247
|
+
`templates/patch.template.md`: a ~60-line bug spec whose §4 **regression test** replaces §5 CONTRACT
|
|
248
|
+
as the thing the diff is checked against. It moves through the same states and the same commands —
|
|
249
|
+
`/cohorte-build` → `/cohorte-review` → `/cohorte-fix`* → `/cohorte-ship` — which is the whole design:
|
|
250
|
+
a patch is a spec, so nothing downstream is special-cased beyond three lines.
|
|
251
|
+
|
|
252
|
+
| what reads `kind: patch` | what it does differently |
|
|
253
|
+
| --- | --- |
|
|
254
|
+
| `/cohorte-build` §1.6 | judges §1 repro + §4 regression test instead of contract completeness; gap check `repro` |
|
|
255
|
+
| `/cohorte-build` §2 | authors no contract when §5 Contract delta is `none` (the usual case) |
|
|
256
|
+
| `/cohorte-ship` §1/§2b/§3 | branches off `vcs.patch_branch_prefix`; a `patch` bump by default; `fix(<scope>)` commit |
|
|
257
|
+
|
|
258
|
+
A patch may span **several surfaces** — one bug, one repro, one spec. What it may never do is add
|
|
259
|
+
**new** contract surface area: two surfaces agreeing on a shape that doesn't exist yet is what §5 is
|
|
260
|
+
for, so `/cohorte-patch` routes that to `/cohorte-spec` instead. Changing an *existing* contract entry
|
|
261
|
+
is a legitimate delta. The patch template keeps contract on **§5** and acceptance on **§9** — the two
|
|
262
|
+
numbers `review.md` and `implementer.template.md` name verbatim — and simply has no §8.
|
|
263
|
+
|
|
249
264
|
Corollaries worth knowing:
|
|
250
265
|
|
|
251
266
|
- A spec with no front-matter makes every stamp a **silent no-op** — the state is bookkeeping, and the
|
|
@@ -477,7 +492,13 @@ files automatically. It works because every generated artifact is a **determinis
|
|
|
477
492
|
rots (PATH changes, uninstalls, hand-edits) — and repair whatever fails.
|
|
478
493
|
5. **Global config seed.** If `<config>` is absent, seed it from the template
|
|
479
494
|
(`profile/cohorte.config.template.yaml`) so the kanban + shared-vault config has a home. Never
|
|
480
|
-
clobber an existing filled file; report what was seeded.
|
|
495
|
+
clobber an existing filled file; report what was seeded. Then **scrub the retired `telemetry:`
|
|
496
|
+
block** if the file still carries one (every install seeded before 2.3.0 does): delete the block
|
|
497
|
+
and its comment header, leaving the rest byte-identical. It is dead config — the sender is gone
|
|
498
|
+
and nothing reads it — but a `telemetry.enabled: true` sitting in a file the human may open reads
|
|
499
|
+
as "this is still sending", which is the one thing it must not imply. This is the single
|
|
500
|
+
exception to "never rewrite the config": a targeted deletion of a block the core no longer
|
|
501
|
+
defines, never a re-seed.
|
|
481
502
|
6. **Kanban sync.** Run the §Kanban reconcile: link/create the project's board if configured, verify
|
|
482
503
|
its columns, and backfill/sync cards from `specs/*.md`. See §Kanban.
|
|
483
504
|
7. **Spec-template top-up.** `specs/_template.md` is seeded once at install and then **never**
|
|
@@ -621,6 +642,7 @@ Ideas — so `/cohorte-brainstorm` appends the tag to the picked line before its
|
|
|
621
642
|
| `/cohorte-brainstorm` picks it up | `brainstorm` |
|
|
622
643
|
| `/cohorte-spec` opens (draft) | `spec` |
|
|
623
644
|
| `/cohorte-spec` freezes (`status: frozen`) | `ready` |
|
|
645
|
+
| `/cohorte-patch` triages / freezes | `spec` → `ready` (card titled `[patch] <title>`, tag `#patch-<slug>`) |
|
|
624
646
|
| `/cohorte-build` | `building` |
|
|
625
647
|
| `/cohorte-review` | `review` |
|
|
626
648
|
| `/cohorte-fix` | `fix` |
|
|
@@ -642,60 +664,3 @@ added vs. moved vs. already-correct.
|
|
|
642
664
|
per configured column in pipeline order, and the closing `%% kanban:settings %%` block
|
|
643
665
|
(`{"kanban-plugin":"board","list-collapse":[false,…]}` with one `false` per column).
|
|
644
666
|
|
|
645
|
-
## Telemetry — anonymous usage stats, strictly opt-in (GDPR-first)
|
|
646
|
-
|
|
647
|
-
Cohorte can send the maintainers anonymous usage pings so the pipeline improves where it's actually
|
|
648
|
-
slow. **Nothing is ever sent without explicit consent**: `/cohorte-init-pipeline` (and `/cohorte-update-pipeline` on
|
|
649
|
-
pre-telemetry installs) ask ONE question, once per machine, default **No**, and record the answer in
|
|
650
|
-
`<config>` §`telemetry` (`enabled`, `install_id`, `consent_date`). The sender —
|
|
651
|
-
`pipeline/scripts/telemetry-send.sh` — is a silent no-op unless `enabled: true` AND `install_id` AND
|
|
652
|
-
`endpoint` are all set, times out at 2s, and never fails the pipeline. Callers chain it with
|
|
653
|
-
`|| true`, so a **missing** script is equally silent: `/cohorte-doctor` check 1 verifies `pipeline/scripts/`
|
|
654
|
-
is fully populated.
|
|
655
|
-
|
|
656
|
-
**Which commands ping** — the six that make up the feature funnel, and only those. The point is to
|
|
657
|
-
see where features stall, so every stage of `idea → PR` reports and nothing else does:
|
|
658
|
-
|
|
659
|
-
| phase | fired when | `seconds` | `results` |
|
|
660
|
-
| --- | --- | --- | --- |
|
|
661
|
-
| `brainstorm` | the return is staged | `0` | — |
|
|
662
|
-
| `spec` | a freeze lands (Mode A only) | `0` | `frozen` |
|
|
663
|
-
| `build` | after the batch metrics line | wall-clock | `ok,ok` / `error` |
|
|
664
|
-
| `review` | after the merged verdict | wall-clock | `<verdict>:<count>` |
|
|
665
|
-
| `fix` | after the batch metrics line | wall-clock | `<fixed>/<found>` |
|
|
666
|
-
| `ship` | the release agent succeeded | `0` | `pr` / `compare` |
|
|
667
|
-
|
|
668
|
-
> Workflow-variant runs (`review.js`) report `seconds: 0` for their phases — only the
|
|
669
|
-
> conversational commands measure wall-clock. `results` is a free-text summary field, so both
|
|
670
|
-
> forms are valid — but read the `fix` column knowing which path produced it.
|
|
671
|
-
|
|
672
|
-
`seconds: 0` marks a phase whose duration is human thinking time, not pipeline wall-clock — the
|
|
673
|
-
funnel signal there is the event, not how long it took. `/cohorte-doctor`, `/cohorte-audit`, `/cohorte-refactor`,
|
|
674
|
-
`/cohorte-align-ds`, `/cohorte-init-pipeline` and `/cohorte-update-pipeline` **never** ping: they sit outside the funnel, and
|
|
675
|
-
keeping them out is what holds the collected set to what the consent text describes.
|
|
676
|
-
|
|
677
|
-
**What one event contains** (strict allowlist, ~200 bytes):
|
|
678
|
-
|
|
679
|
-
```json
|
|
680
|
-
{"v":1,"install_id":"<random uuid>","ts":"<ISO>","core_version":"1.2.0","os":"Darwin",
|
|
681
|
-
"event":"phase","phase":"build","feature_hash":"<sha256[..12] of the feature id>",
|
|
682
|
-
"seconds":412,"results":"ok,ok"}
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
**What is NEVER sent:** repo/project names, file paths, code, spec content, prompts, emails,
|
|
686
|
-
usernames, IP handling client-side. The feature id is hashed (12 hex chars) so cross-feature counts
|
|
687
|
-
work without revealing what is being built.
|
|
688
|
-
|
|
689
|
-
**GDPR rights, concretely:**
|
|
690
|
-
|
|
691
|
-
- **Consent** — opt-in only, recorded with a date; "No" is also recorded so nothing re-asks.
|
|
692
|
-
- **Withdrawal** — set `telemetry.enabled: false` in `<config>`; effective on
|
|
693
|
-
the next phase, no restart.
|
|
694
|
-
- **Erasure** — `/cohorte-doctor` prints your `install_id`; send
|
|
695
|
-
`curl -X DELETE <endpoint-origin>/v1/install/<install_id>` and the collector drops every event
|
|
696
|
-
for that id (the deployed collector implements this and stores no IPs).
|
|
697
|
-
- **Access/portability** — events are keyed by your `install_id`; ask the operator for an export.
|
|
698
|
-
|
|
699
|
-
**Collector contract** (any implementation must honor it):
|
|
700
|
-
`POST /v1/events` (one JSON event, allowlisted fields) · `DELETE /v1/install/<id>` (erasure) ·
|
|
701
|
-
`GET /healthz`. Operators must not retain IP-bearing access logs for the ingest vhost.
|
|
@@ -10,22 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
enabled: true # cfg:enabled — master switch; false disables every capability below
|
|
12
12
|
|
|
13
|
-
# ── telemetry (anonymous usage stats — OPT-IN, GDPR-first) ──────────────────
|
|
14
|
-
# Strictly opt-in: nothing is ever sent unless YOU said yes to the consent question
|
|
15
|
-
# (/cohorte-init-pipeline or /cohorte-update-pipeline ask once per machine; they record your answer here).
|
|
16
|
-
# What is sent when enabled: core version, OS, phase name, wall-clock seconds, per-surface
|
|
17
|
-
# result counts, and a SHA-256 HASH of the feature id — never repo names, paths, code, spec
|
|
18
|
-
# content, emails, or IPs. One ping per pipeline phase, /cohorte-brainstorm through /cohorte-ship; setup and
|
|
19
|
-
# maintenance commands (/cohorte-doctor, /cohorte-init-pipeline, /cohorte-update-pipeline, /cohorte-audit, /cohorte-refactor,
|
|
20
|
-
# /cohorte-align-ds) never ping. Sent fire-and-forget (2s timeout, silent on failure) by
|
|
21
|
-
# pipeline/scripts/telemetry-send.sh. Withdraw anytime: set enabled: false. Erase your history:
|
|
22
|
-
# see SCHEMA.md §Telemetry (DELETE by install_id).
|
|
23
|
-
telemetry:
|
|
24
|
-
enabled: false # cfg:telemetry_enabled — true ONLY via explicit consent
|
|
25
|
-
install_id: "" # cfg:telemetry_install_id — random UUID minted at consent; the erasure key
|
|
26
|
-
consent_date: "" # cfg:telemetry_consent_date — ISO date the human said yes
|
|
27
|
-
endpoint: "https://telemetry.cohorte.thebidouille.fr/v1/events" # cfg:telemetry_endpoint — collector URL; empty = off even if enabled
|
|
28
|
-
|
|
29
13
|
# ── obsidian ──────────────────────────────────────────────────────────────────
|
|
30
14
|
# Local vault used by the kanban mirror — defined here ONCE so no capability ever duplicates
|
|
31
15
|
# the path. Absolute path to the folder you open in Obsidian.
|
|
@@ -25,7 +25,7 @@ const frontmatter = (text) => {
|
|
|
25
25
|
const PINNED = ["cohorte-build", "cohorte-review", "cohorte-fix", "cohorte-ship",
|
|
26
26
|
"cohorte-audit", "cohorte-refactor", "cohorte-doctor", "cohorte-align-ds",
|
|
27
27
|
"cohorte-update-pipeline"];
|
|
28
|
-
const UNPINNED = ["cohorte-brainstorm", "cohorte-spec", "cohorte-init-pipeline"];
|
|
28
|
+
const UNPINNED = ["cohorte-brainstorm", "cohorte-spec", "cohorte-init-pipeline", "cohorte-patch"];
|
|
29
29
|
|
|
30
30
|
// Every command must carry the `cohorte-` prefix. This replaces the old RESERVED
|
|
31
31
|
// blocklist, which chased collisions one name at a time and always lagged: a command
|
|
@@ -129,26 +129,28 @@ const steps = join(root, "core/templates/steps/init-pipeline");
|
|
|
129
129
|
if (!existsSync(steps) || readdirSync(steps).length === 0)
|
|
130
130
|
fail("core/templates/steps/init-pipeline", "router step files missing/empty");
|
|
131
131
|
|
|
132
|
-
// ── telemetry
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
for (const
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
132
|
+
// ── no telemetry ────────────────────────────────────────────────────────────
|
|
133
|
+
// Telemetry was removed wholesale in 2.3.0: the shipped `telemetry-send.sh`, the
|
|
134
|
+
// per-phase pings, the consent question, the `telemetry:` config block, the collector
|
|
135
|
+
// endpoint. This check is the ratchet — it fails if any of it creeps back into the
|
|
136
|
+
// core, which is easy to do by copying an old command file that still chains a ping.
|
|
137
|
+
// Deliberately broad: the whole point is that there is nothing left to send with.
|
|
138
|
+
const NO_TELEMETRY = /telemetry|usage ping/i;
|
|
139
|
+
// One exemption, and it is the opposite of a regression: /cohorte-update-pipeline is what
|
|
140
|
+
// DELETES the leftover `telemetry:` block from configs seeded before 2.3.0, so it is the one
|
|
141
|
+
// file that must still name the thing. Narrow on purpose — a filename, not a pattern.
|
|
142
|
+
const TELEMETRY_SCRUBBER = "core/commands/cohorte-update-pipeline.md";
|
|
143
|
+
const walk = (dir) => readdirSync(join(root, dir), { withFileTypes: true }).flatMap((e) =>
|
|
144
|
+
e.isDirectory() ? walk(`${dir}/${e.name}`) : [`${dir}/${e.name}`]);
|
|
145
|
+
for (const dir of ["core/commands", "core/agents", "core/templates", "core/workflows"])
|
|
146
|
+
for (const rel of walk(dir)) {
|
|
147
|
+
if (!/\.(md|js)$/.test(rel) || rel === TELEMETRY_SCRUBBER) continue;
|
|
148
|
+
if (NO_TELEMETRY.test(read(rel)))
|
|
149
|
+
fail(rel, "mentions telemetry — it was removed in 2.3.0; nothing may ping or ask for consent");
|
|
150
|
+
}
|
|
151
|
+
// …and the exempt file may only REMOVE it: naming a send/ping/consent path there is still a bug.
|
|
152
|
+
if (/usage ping|telemetry-send|consent/i.test(read(TELEMETRY_SCRUBBER)))
|
|
153
|
+
fail(TELEMETRY_SCRUBBER, "may reference the retired telemetry block only to delete it — no ping, sender or consent flow");
|
|
152
154
|
|
|
153
155
|
// ── kanban call sites ───────────────────────────────────────────────────────
|
|
154
156
|
// Every pipeline stage moves a card, and a stage that only *describes* the move
|
|
@@ -158,7 +160,7 @@ for (const f of readdirSync(join(root, "core/commands"))) {
|
|
|
158
160
|
// having opened neither the config nor PIPELINE.md, and a merged feature's card
|
|
159
161
|
// stayed in "Ready to build". `kanban-move.sh auto` moved resolution into the
|
|
160
162
|
// script; this keeps it there. Prose is not a call site — the literal invocation is.
|
|
161
|
-
const KANBAN_STAGES = ["brainstorm", "spec", "build", "review", "fix", "ship"];
|
|
163
|
+
const KANBAN_STAGES = ["brainstorm", "spec", "build", "review", "fix", "ship", "patch"];
|
|
162
164
|
for (const c of KANBAN_STAGES) {
|
|
163
165
|
const path = `core/commands/${PREFIX}${c}.md`;
|
|
164
166
|
const text = read(path);
|
|
@@ -174,7 +176,7 @@ for (const c of KANBAN_STAGES) {
|
|
|
174
176
|
// ── shipped scripts ─────────────────────────────────────────────────────────
|
|
175
177
|
// Every scripts/*.sh must be copied by BOTH shell installers. Callers chain these
|
|
176
178
|
// with `|| true`, so one an installer forgets is a silent no-op forever — no kanban
|
|
177
|
-
// card moves, no
|
|
179
|
+
// card moves, no error. CI is the only place this is loud.
|
|
178
180
|
// The third installer, bin/cli.js (what `npx cohorte` runs), copies by rule rather
|
|
179
181
|
// than by name, so grepping for filenames can't see it — ci.yml dry-runs it into a
|
|
180
182
|
// scratch HOME and asserts the same postconditions instead. Both are needed: this
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# telemetry-send.sh — fire-and-forget anonymous usage ping (SCHEMA.md §Telemetry).
|
|
3
|
-
#
|
|
4
|
-
# telemetry-send.sh <phase> <feature_id> <seconds> [results]
|
|
5
|
-
# phase brainstorm|spec|build|review|fix|ship — the feature funnel, and only it.
|
|
6
|
-
# Setup/maintenance commands never ping (SCHEMA.md §Telemetry).
|
|
7
|
-
# feature the feature id — NEVER sent raw; SHA-256-hashed to 12 hex chars
|
|
8
|
-
# seconds batch wall-clock
|
|
9
|
-
# results optional compact summary, e.g. "ok,ok" or "REVISE:3"
|
|
10
|
-
#
|
|
11
|
-
# GDPR posture (documented in SCHEMA.md §Telemetry):
|
|
12
|
-
# - STRICTLY opt-in: exits silently unless ~/.claude/cohorte.config.yaml has
|
|
13
|
-
# telemetry.enabled: true AND a non-empty endpoint AND an install_id
|
|
14
|
-
# (all three written only by the explicit consent flow).
|
|
15
|
-
# - Data minimization: no repo names, no paths, no code, no IP handling client-side.
|
|
16
|
-
# - Never blocks or fails the pipeline: 2s timeout, all errors swallowed, exit 0 always.
|
|
17
|
-
set -u
|
|
18
|
-
|
|
19
|
-
# Same two-location probe as kanban-move.sh: consent is recorded once per human, in
|
|
20
|
-
# whichever config the runtime that installed the pipeline created.
|
|
21
|
-
cfg=""
|
|
22
|
-
for c in "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/cohorte.config.yaml" \
|
|
23
|
-
"$HOME/.cohorte/cohorte.config.yaml"; do
|
|
24
|
-
[ -f "$c" ] && { cfg="$c"; break; }
|
|
25
|
-
done
|
|
26
|
-
[ -n "$cfg" ] || exit 0
|
|
27
|
-
|
|
28
|
-
# read keys scoped to the `telemetry:` block only
|
|
29
|
-
tval() {
|
|
30
|
-
awk -v key="$1" '
|
|
31
|
-
/^telemetry:/ { in_t=1; next }
|
|
32
|
-
/^[a-zA-Z]/ { in_t=0 }
|
|
33
|
-
in_t && $1 == key":" {
|
|
34
|
-
v=$2; gsub(/^"|"$|#.*/,"",v); gsub(/"/,"",v); print v; exit
|
|
35
|
-
}' "$cfg"
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
[ "$(tval enabled)" = "true" ] || exit 0
|
|
39
|
-
endpoint="$(tval endpoint)"; [ -n "$endpoint" ] || exit 0
|
|
40
|
-
install_id="$(tval install_id)"; [ -n "$install_id" ] || exit 0
|
|
41
|
-
|
|
42
|
-
phase="${1:-}"; feature="${2:-}"; seconds="${3:-0}"; results="${4:-}"
|
|
43
|
-
[ -n "$phase" ] || exit 0
|
|
44
|
-
|
|
45
|
-
# Allowlist the phase here — the collector accepts any string, so a typo in a command
|
|
46
|
-
# file would silently pollute the dataset with a phantom phase nobody notices.
|
|
47
|
-
case "$phase" in
|
|
48
|
-
brainstorm|spec|build|review|fix|ship) ;;
|
|
49
|
-
# `smoke` is a RETIRED phase (removed in 1.5.0) — still accepted so a stale install
|
|
50
|
-
# pinging it lands in its own bucket instead of being silently dropped.
|
|
51
|
-
smoke) ;;
|
|
52
|
-
*) exit 0 ;;
|
|
53
|
-
esac
|
|
54
|
-
|
|
55
|
-
# The payload is hand-built JSON: a non-numeric seconds or a quote in results
|
|
56
|
-
# would silently produce an invalid document the collector drops.
|
|
57
|
-
case "$seconds" in ''|*[!0-9]*) seconds=0 ;; esac
|
|
58
|
-
results=$(printf '%s' "$results" | tr -d '"\\\n\r' | cut -c1-80)
|
|
59
|
-
|
|
60
|
-
if command -v shasum >/dev/null 2>&1; then
|
|
61
|
-
fhash=$(printf '%s' "$feature" | shasum -a 256 | cut -c1-12)
|
|
62
|
-
else
|
|
63
|
-
fhash=$(printf '%s' "$feature" | sha256sum | cut -c1-12)
|
|
64
|
-
fi
|
|
65
|
-
# The core that shipped THIS script is the one whose version we report. Bundled
|
|
66
|
-
# installs live at <project>/.claude/pipeline/scripts/, where the global VERSION is
|
|
67
|
-
# either absent or a DIFFERENT core — reading only the global path made every
|
|
68
|
-
# bundled repo report an empty core_version.
|
|
69
|
-
here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd 2>/dev/null) || here=""
|
|
70
|
-
ver=""
|
|
71
|
-
for v in "$here/../VERSION" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/pipeline/VERSION" \
|
|
72
|
-
"$HOME/.cohorte/pipeline/VERSION"; do
|
|
73
|
-
[ -n "$ver" ] && break
|
|
74
|
-
ver=$(head -1 "$v" 2>/dev/null | tr -d '"\\')
|
|
75
|
-
done
|
|
76
|
-
os=$(uname -s 2>/dev/null || echo unknown)
|
|
77
|
-
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
78
|
-
|
|
79
|
-
payload=$(printf '{"v":1,"install_id":"%s","ts":"%s","core_version":"%s","os":"%s","event":"phase","phase":"%s","feature_hash":"%s","seconds":%s,"results":"%s"}' \
|
|
80
|
-
"$install_id" "$ts" "$ver" "$os" "$phase" "$fhash" "${seconds:-0}" "$results")
|
|
81
|
-
|
|
82
|
-
curl -s -m 2 -X POST -H 'content-type: application/json' \
|
|
83
|
-
-d "$payload" "$endpoint" >/dev/null 2>&1 || true
|
|
84
|
-
exit 0
|