cohorte 1.2.2 → 1.2.4
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 +46 -0
- package/bin/cli.js +21 -3
- package/core/commands/brainstorm.md +5 -0
- package/core/commands/build.md +7 -4
- package/core/commands/doctor.md +6 -1
- package/core/commands/fix.md +4 -1
- package/core/commands/review.md +2 -0
- package/core/commands/ship.md +7 -0
- package/core/commands/smoke.md +2 -1
- package/core/commands/spec.md +6 -1
- package/core/templates/steps/init-pipeline/02-interview-gaps.md +3 -2
- package/package.json +3 -1
- package/profile/SCHEMA.md +22 -3
- package/profile/cohorte.config.template.yaml +3 -1
- package/scripts/telemetry-send.sh +9 -1
- package/scripts/validate-core.mjs +38 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,52 @@
|
|
|
3
3
|
Entries are shown by `/update-pipeline` ("What's new") after a core refresh. Keep them short,
|
|
4
4
|
user-facing, most recent first. One `## <version> — <YYYY-MM-DD>` section per release.
|
|
5
5
|
|
|
6
|
+
## 1.2.4 — 2026-07-29
|
|
7
|
+
|
|
8
|
+
> **If you installed with `npx cohorte`, this is the release that makes 1.2.3 actually
|
|
9
|
+
> reach you.** Re-run `npx cohorte@latest update --global` (or `update` for a bundled core).
|
|
10
|
+
|
|
11
|
+
- **`npx cohorte install/update` shipped a core missing two scripts.** `bin/cli.js` — the
|
|
12
|
+
port of `install.sh` that `npx` actually runs — copied only `scripts/*.template`, never
|
|
13
|
+
`kanban-move.sh` or `telemetry-send.sh`. Since every caller chains them with `|| true`,
|
|
14
|
+
the result was silent on every npx-installed machine: no kanban card moves, no telemetry
|
|
15
|
+
pings, no error anywhere. The shell installers named both files explicitly and this port
|
|
16
|
+
drifted from them. It now copies by a rule that needs no list to keep in sync.
|
|
17
|
+
- The same port never copied `CHANGELOG.md` into the core either, so `/doctor` and
|
|
18
|
+
`/update-pipeline`'s "What's new" had nothing to read on npx installs. Fixed.
|
|
19
|
+
- CI now dry-runs `bin/cli.js` into a scratch dir and asserts the same postconditions as
|
|
20
|
+
the `install.sh` dry-run. 1.2.3's guard only grepped the two shell installers — it would
|
|
21
|
+
have passed this bug, because the port copies by rule rather than by name.
|
|
22
|
+
|
|
23
|
+
## 1.2.3 — 2026-07-29
|
|
24
|
+
|
|
25
|
+
- **Telemetry now covers the whole funnel.** Only `/build` was actually pinging; `/smoke`,
|
|
26
|
+
`/review` and `/fix` wrote their metrics line but never sent one, so consenting installs
|
|
27
|
+
reported a quarter of their pipeline. Those three are fixed, and `/brainstorm`, `/spec`
|
|
28
|
+
(on a landed freeze) and `/ship` join them — the seven stages of `idea → PR` now report,
|
|
29
|
+
so it's finally possible to see *where* features stall. Setup and maintenance commands
|
|
30
|
+
(`/doctor`, `/init-pipeline`, `/update-pipeline`, `/audit`, `/refactor`, `/align-ds`)
|
|
31
|
+
deliberately never ping: the collected set stays inside what the consent text describes.
|
|
32
|
+
Same data categories as before, same purpose — nothing new about you is sent, so your
|
|
33
|
+
existing consent stands and nothing re-asks. The full table is in SCHEMA.md §Telemetry.
|
|
34
|
+
- `telemetry-send.sh` now allowlists the phase name client-side — a typo in a command file
|
|
35
|
+
used to sail through and land a phantom phase in the dataset.
|
|
36
|
+
- `/fix` never defined a wall-clock start, so the `seconds` in its metrics line was
|
|
37
|
+
undefined. It now notes the epoch like `/build` and `/review` do.
|
|
38
|
+
- **`/doctor` catches a half-copied core.** New check: `pipeline/scripts/` must hold every
|
|
39
|
+
shipped script, and `VERSION` must not be newer than its siblings. Callers chain these
|
|
40
|
+
scripts with `|| true`, so a missing one was invisible — no kanban move, no telemetry
|
|
41
|
+
ping, no error. If you saw either go quiet, this is why: re-run the installer.
|
|
42
|
+
- CI now fails if an installer forgets to copy a `scripts/*.sh`, and the dry-run install
|
|
43
|
+
asserts the scripts land executable — the root cause above, caught before release
|
|
44
|
+
rather than on someone's machine.
|
|
45
|
+
- The npm tarball no longer ships `scripts/new-feature.sh` + `scripts/remove-feature.sh`
|
|
46
|
+
— cohorte's *own* rendered isolation scripts, with this repo's ports and paths baked
|
|
47
|
+
in. They claimed in their header to be excluded but never were (an explicit `files`
|
|
48
|
+
whitelist wins over `.npmignore`). Only the `*.sh.template` files ship, as intended.
|
|
49
|
+
- Fixed `validate-core.mjs` crashing on Windows (`C:\C:\…` path), so the guard above
|
|
50
|
+
actually runs locally too.
|
|
51
|
+
|
|
6
52
|
## 1.2.2 — 2026-07-29
|
|
7
53
|
|
|
8
54
|
- The reference collector moved to its own (private) deployment repo; the public repo keeps
|
package/bin/cli.js
CHANGED
|
@@ -102,13 +102,31 @@ function copyCore() {
|
|
|
102
102
|
for (const f of ['PIPELINE.template.md', 'SCHEMA.md', 'cohorte.config.template.yaml']) {
|
|
103
103
|
fs.copyFileSync(path.join(src, 'profile', f), path.join(pipelineDir, f));
|
|
104
104
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
// Copy the *.template files AND the shipped executables (kanban-move.sh,
|
|
106
|
+
// telemetry-send.sh). Until 1.2.4 this loop took only `.template`, so every
|
|
107
|
+
// `npx cohorte install/update` produced a core missing both scripts — and since
|
|
108
|
+
// every caller chains them with `|| true`, the result was silent: no kanban card
|
|
109
|
+
// moves, no telemetry pings, no error. The shell installers named them explicitly
|
|
110
|
+
// and this port drifted. The rule below needs no list to keep in sync: a `<x>.sh`
|
|
111
|
+
// with an `<x>.sh.template` sibling is rendered per-project by /init-pipeline, so
|
|
112
|
+
// only the template ships; every other `.sh` is a shipped executable.
|
|
113
|
+
const scriptFiles = fs.readdirSync(path.join(src, 'scripts'));
|
|
114
|
+
for (const f of scriptFiles) {
|
|
115
|
+
const isTemplate = f.endsWith('.template');
|
|
116
|
+
const isShipped = f.endsWith('.sh') && !scriptFiles.includes(`${f}.template`);
|
|
117
|
+
if (!isTemplate && !isShipped) continue;
|
|
118
|
+
const target = path.join(pipelineDir, 'scripts', f);
|
|
119
|
+
fs.copyFileSync(path.join(src, 'scripts', f), target);
|
|
120
|
+
if (isShipped && process.platform !== 'win32') {
|
|
121
|
+
try { fs.chmodSync(target, 0o755); } catch { /* optional */ }
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
fs.copyFileSync(path.join(src, 'core', 'agents', 'implementer.template.md'),
|
|
111
125
|
path.join(pipelineDir, 'implementer.template.md'));
|
|
126
|
+
// /doctor reads this to tell the human what they're missing; the shell installers
|
|
127
|
+
// have always copied it, this port never did.
|
|
128
|
+
const changelog = path.join(src, 'CHANGELOG.md');
|
|
129
|
+
if (fs.existsSync(changelog)) fs.copyFileSync(changelog, path.join(pipelineDir, 'CHANGELOG.md'));
|
|
112
130
|
fs.writeFileSync(path.join(pipelineDir, 'VERSION'), VERSION + '\n');
|
|
113
131
|
if (process.platform !== 'win32') {
|
|
114
132
|
try { fs.chmodSync(path.join(dest, 'hooks', 'gate.py'), 0o755); } catch { /* optional */ }
|
|
@@ -49,3 +49,8 @@ when invoked with no paste). In chat print only a 3-line summary + the path. Tel
|
|
|
49
49
|
downstream. If a board is configured, **move** the card into the **Brainstorm** column tagged
|
|
50
50
|
`#<feature_id>` (per §Kanban): the picked Ideas card if the human chose one, else a new card. No-op if
|
|
51
51
|
no board.
|
|
52
|
+
|
|
53
|
+
**Telemetry:** chain the opt-in usage ping onto that same Bash call — `/build` §4's shared form,
|
|
54
|
+
`<phase>` = `brainstorm`, `<seconds>` = `0` (this phase is human thinking time, not pipeline
|
|
55
|
+
wall-clock — the funnel signal is the event, not its duration), `<results>` = `""`. No board ⇒ still
|
|
56
|
+
ping, in its own `|| true` call. Silent no-op without consent; never ask about consent here.
|
package/core/commands/build.md
CHANGED
|
@@ -101,10 +101,13 @@ Compute the elapsed time in the same Bash call
|
|
|
101
101
|
(`echo "{...\"seconds\":$(($(date +%s)-<start epoch from §2>)),...}" >> …`):
|
|
102
102
|
`{"ts":"<ISO date>","feature":"$ARGUMENTS","phase":"build","seconds":<wall-clock>,"surfaces":{"<key>":"ok|error",…}}`
|
|
103
103
|
— this is the evidence SCHEMA.md §Specialization asks for before splitting a surface. In the same
|
|
104
|
-
Bash call, chain the opt-in usage ping
|
|
105
|
-
`<core>/pipeline/scripts/telemetry-send.sh
|
|
106
|
-
(`<core>` = `~/.claude` global / `.claude` bundled
|
|
107
|
-
consented (SCHEMA.md §Telemetry);
|
|
104
|
+
Bash call, chain the opt-in usage ping — **the shared form every phase command reuses**:
|
|
105
|
+
`<core>/pipeline/scripts/telemetry-send.sh <phase> "$ARGUMENTS" <seconds> "<results>" || true`
|
|
106
|
+
(`<core>` = `~/.claude` global / `.claude` bundled; here `<phase>` = `build`, `<results>` =
|
|
107
|
+
`<ok,ok|error,…>`) — a silent no-op unless the human explicitly consented (SCHEMA.md §Telemetry);
|
|
108
|
+
never ask about consent here. `/review`, `/fix` and `/smoke` chain the same line with their own
|
|
109
|
+
phase + results. The `|| true` swallows a **missing** script too, so a half-copied core goes
|
|
110
|
+
silent rather than loud — `/doctor` check 1 is what catches that.
|
|
108
111
|
Then tell the human: run `/smoke $ARGUMENTS` to exercise the feature end-to-end (or test by hand),
|
|
109
112
|
then `/review $ARGUMENTS`. Do not run the app or migrations yourself here — `/smoke` is the
|
|
110
113
|
sanctioned path for that. **Recommend a `/clear` now** — the spec, contract and diff are all on
|
package/core/commands/doctor.md
CHANGED
|
@@ -18,7 +18,12 @@ fix only with the human's go-ahead (or hand them the command).
|
|
|
18
18
|
suggest `/update-pipeline`. Read `pipeline/CHANGELOG.md` for what they're missing. The router
|
|
19
19
|
commands' step files are present — `templates/steps/init-pipeline/` non-empty (a router whose
|
|
20
20
|
`templates/steps/<cmd>/` dir is missing is a partial/stale install ⇒
|
|
21
|
-
re-run install/update).
|
|
21
|
+
re-run install/update). **Shipped scripts present and executable** in `<core>/pipeline/scripts/`:
|
|
22
|
+
`kanban-move.sh`, `telemetry-send.sh`, `new-feature.sh.template`, `remove-feature.sh.template`
|
|
23
|
+
— ❌ any missing one. Every caller chains these with `|| true`, so an absent script is a **silent**
|
|
24
|
+
no-op (no kanban card moves, no telemetry ping, no error anywhere) — this check is the only thing
|
|
25
|
+
that sees it. Also flag ❌ a `VERSION` **newer than** the other `pipeline/` files (compare mtimes):
|
|
26
|
+
a version bumped without a full re-copy is a half-done update ⇒ re-run install/update.
|
|
22
27
|
2. **Profile.** `PIPELINE.md` exists and its `yaml pipeline-profile` block parses. Every
|
|
23
28
|
`surfaces[].agent` has its `.claude/agents/<agent>.md` and every agent file has its `surfaces[]`
|
|
24
29
|
entry — **no orphans either way** (SCHEMA.md rule). Each rendered agent's frontmatter `tools`
|
package/core/commands/fix.md
CHANGED
|
@@ -29,6 +29,8 @@ that change the *contract*; `/fix` is for everything else.
|
|
|
29
29
|
re-author the contract file yourself now (lead-only, per `/build` §2) — agents never edit it. If
|
|
30
30
|
the contract change ripples into surfaces *without* findings, fall back to full `/build` instead
|
|
31
31
|
and say so.
|
|
32
|
+
- **Note the epoch** (`date +%s`) in the first Bash call you make here — §3's metrics line and usage
|
|
33
|
+
ping both carry `seconds`, and there is no separate timing call.
|
|
32
34
|
|
|
33
35
|
## 2. Scope the re-dispatch — only surfaces with findings
|
|
34
36
|
|
|
@@ -61,7 +63,8 @@ When the agents return:
|
|
|
61
63
|
needs those checkboxes).
|
|
62
64
|
- Print one status line per surface (`<key> · items fixed <n>/<m> · tests pass/fail`) — do not restate
|
|
63
65
|
handoff content — and append ONE metrics line for the batch to `pipeline-metrics.jsonl`
|
|
64
|
-
(see `/build` §4, `phase: "fix"`)
|
|
66
|
+
(see `/build` §4, `phase: "fix"`), chaining the opt-in usage ping in the same Bash call
|
|
67
|
+
(results = items fixed over items found across surfaces, e.g. `"5/6"`).
|
|
65
68
|
- Tell the human: re-run `/smoke` if the failures were runtime ones, and `/review $ARGUMENTS` for the
|
|
66
69
|
re-verdict — the re-review is what *verifies* the ticked items actually hold (a regression simply
|
|
67
70
|
reappears as a new finding in the next round). **Recommend a `/clear`** — all state (spec,
|
package/core/commands/review.md
CHANGED
|
@@ -58,6 +58,8 @@ Merge the returned reports into **one** REVIEW REPORT (same template): findings
|
|
|
58
58
|
re-ordered by severity, counts summed, duplicates collapsed, verdict = the worst returned
|
|
59
59
|
(`BLOCK` > `REVISE` > `SHIP`). Append ONE metrics line for the batch to `pipeline-metrics.jsonl`
|
|
60
60
|
(main-checkout path + rules in `/build` §4): `{"ts":"<ISO>","feature":"$ARGUMENTS","phase":"review","seconds":<wall-clock>,"surfaces":{"<key>":"<verdict>:<finding count>",…}}`.
|
|
61
|
+
In the same Bash call, chain the opt-in usage ping (`/build` §4, `phase: "review"`, results = the
|
|
62
|
+
merged verdict + total finding count, e.g. `"REVISE:3"`).
|
|
61
63
|
**Stage the full report to `specs/reports/$ARGUMENTS.md`** (overwrite) — a gitignored buffer so a
|
|
62
64
|
`/fix` after a `/clear` can still read the findings; the `specs/reports/` subfolder is skipped by the
|
|
63
65
|
non-recursive `specs/*.md` glob, so it's never mistaken for a spec (no phantom card, no bogus stage).
|
package/core/commands/ship.md
CHANGED
|
@@ -60,6 +60,13 @@ compare URL was emitted (no PR yet), move the card without a number. Then verify
|
|
|
60
60
|
or an offset-limited Read around the match): exactly one card, under the `shipped` heading — never
|
|
61
61
|
re-read the whole board into context. No board ⇒ skip silently.
|
|
62
62
|
|
|
63
|
+
**Telemetry — the usage ping that closes the funnel.** Chain it onto the verify call above
|
|
64
|
+
(`/build` §4's shared form, `<phase>` = `ship`, `<seconds>` = `0` — the release agent's duration is
|
|
65
|
+
not the pipeline's, `<results>` = `pr` when a PR was created / `compare` when only a compare URL was
|
|
66
|
+
emitted). Fire it **after** the release agent reports success, never on an aborted ship — a `ship`
|
|
67
|
+
event must mean the feature actually left the pipeline. No board ⇒ still ping, in its own `|| true`
|
|
68
|
+
call. Silent no-op without consent; never ask about consent here.
|
|
69
|
+
|
|
63
70
|
## 5. After the PR — CI gate + teardown
|
|
64
71
|
|
|
65
72
|
- If `host: github` and `gh` is available, watch the PR's checks (`gh pr checks <url> --watch`) and
|
package/core/commands/smoke.md
CHANGED
|
@@ -34,7 +34,8 @@ human; that's expected.
|
|
|
34
34
|
|
|
35
35
|
- Print the agent's return as-is (verdict + ❌ lines + report path) — it is already minimal.
|
|
36
36
|
- Append ONE metrics line to `pipeline-metrics.jsonl` (main-checkout path + rules in `/build` §4,
|
|
37
|
-
`phase: "smoke"`)
|
|
37
|
+
`phase: "smoke"`), chaining the opt-in usage ping in the same Bash call (results = `PASS` or
|
|
38
|
+
`FAIL:<n>` failing flows).
|
|
38
39
|
- **PASS** → tell the human to run `/review $ARGUMENTS`. **FAIL** → the failures are findings: feed
|
|
39
40
|
them to `/fix $ARGUMENTS`, re-run `/smoke` after. Either way the report is on disk —
|
|
40
41
|
**recommend a `/clear`** before the next command.
|
package/core/commands/spec.md
CHANGED
|
@@ -47,7 +47,12 @@ Detect the mode from the pasted content:
|
|
|
47
47
|
it. This is just so the human isn't surprised when `/build` proposes a new agent.
|
|
48
48
|
5. When the human validates, **freeze**: write `specs/<id>.md` (`status: frozen`, front-matter filled).
|
|
49
49
|
Create the file — do not ask the human to. **Postcondition:** `grep -q '^status: frozen' specs/<id>.md`
|
|
50
|
-
— if it fails the freeze didn't land; fix it before pointing the human at `/build`.
|
|
50
|
+
— if it fails the freeze didn't land; fix it before pointing the human at `/build`. Chain the
|
|
51
|
+
opt-in usage ping onto the postcondition's Bash call (`/build` §4's shared form, `<phase>` =
|
|
52
|
+
`spec`, `<seconds>` = `0` — interactive time, not pipeline wall-clock, `<results>` = `frozen`).
|
|
53
|
+
Ping only on a **landed** freeze, so the funnel counts specs that exist, not attempts. Mode B does
|
|
54
|
+
not ping — it re-enters an already-counted spec, and `/fix` covers that loop. Silent no-op without
|
|
55
|
+
consent; never ask about consent here.
|
|
51
56
|
6. Author the **design brief** — `specs/design/<id>.md`, rendered via
|
|
52
57
|
`.claude/templates/design-brief.md` (resolves to `~/.claude/templates/…` on a global install).
|
|
53
58
|
_Only if `design.enabled` / the feature has UI; skip entirely for a backend-only feature._
|
|
@@ -43,8 +43,9 @@ Prefer sensible defaults from Phase 1 as the first (Recommended) option in each
|
|
|
43
43
|
- **Telemetry** (optional, machine-scoped — SKIP entirely if `~/.claude/cohorte.config.yaml` already
|
|
44
44
|
has a `telemetry:` block with a `consent_date`, i.e. the human already answered on this machine).
|
|
45
45
|
Ask ONE opt-in question, stating exactly: _"Share anonymous usage stats with the cohorte project?
|
|
46
|
-
|
|
47
|
-
hash of the feature id — never repo names, paths, code, or IPs.
|
|
46
|
+
One ping per pipeline phase, `/brainstorm` through `/ship`: core version, OS, phase name, duration,
|
|
47
|
+
per-surface result counts, and a hash of the feature id — never repo names, paths, code, or IPs.
|
|
48
|
+
Setup and maintenance commands never ping. Off by default; withdraw anytime
|
|
48
49
|
(`telemetry.enabled: false`); erase your history anytime (SCHEMA.md §Telemetry). Default: No."_
|
|
49
50
|
On **yes**: in the global config set `telemetry.enabled: true`, mint `install_id` (`uuidgen`,
|
|
50
51
|
lowercase), set `consent_date` (ISO date). On **no**: set `enabled: false` + `consent_date` (so
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cohorte",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Portable, stack-agnostic multi-agent development pipeline for Claude Code — install the core, run /init-pipeline, and it adapts to your project's stack.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cohorte": "bin/cli.js"
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"core",
|
|
14
14
|
"profile",
|
|
15
15
|
"scripts",
|
|
16
|
+
"!scripts/new-feature.sh",
|
|
17
|
+
"!scripts/remove-feature.sh",
|
|
16
18
|
"dashboard/server",
|
|
17
19
|
"dashboard/dist",
|
|
18
20
|
"dashboard/README.md",
|
package/profile/SCHEMA.md
CHANGED
|
@@ -325,9 +325,28 @@ Cohorte can send the maintainers anonymous usage pings so the pipeline improves
|
|
|
325
325
|
slow. **Nothing is ever sent without explicit consent**: `/init-pipeline` (and `/update-pipeline` on
|
|
326
326
|
pre-telemetry installs) ask ONE question, once per machine, default **No**, and record the answer in
|
|
327
327
|
`~/.claude/cohorte.config.yaml` §`telemetry` (`enabled`, `install_id`, `consent_date`). The sender —
|
|
328
|
-
`pipeline/scripts/telemetry-send.sh
|
|
329
|
-
|
|
330
|
-
|
|
328
|
+
`pipeline/scripts/telemetry-send.sh` — is a silent no-op unless `enabled: true` AND `install_id` AND
|
|
329
|
+
`endpoint` are all set, times out at 2s, and never fails the pipeline. Callers chain it with
|
|
330
|
+
`|| true`, so a **missing** script is equally silent: `/doctor` check 1 verifies `pipeline/scripts/`
|
|
331
|
+
is fully populated.
|
|
332
|
+
|
|
333
|
+
**Which commands ping** — the seven that make up the feature funnel, and only those. The point is to
|
|
334
|
+
see where features stall, so every stage of `idea → PR` reports and nothing else does:
|
|
335
|
+
|
|
336
|
+
| phase | fired when | `seconds` | `results` |
|
|
337
|
+
| --- | --- | --- | --- |
|
|
338
|
+
| `brainstorm` | the return is staged | `0` | — |
|
|
339
|
+
| `spec` | a freeze lands (Mode A only) | `0` | `frozen` |
|
|
340
|
+
| `build` | after the batch metrics line | wall-clock | `ok,ok` / `error` |
|
|
341
|
+
| `smoke` | after the verdict | wall-clock | `PASS` / `FAIL:<n>` |
|
|
342
|
+
| `review` | after the merged verdict | wall-clock | `<verdict>:<count>` |
|
|
343
|
+
| `fix` | after the batch metrics line | wall-clock | `<fixed>/<found>` |
|
|
344
|
+
| `ship` | the release agent succeeded | `0` | `pr` / `compare` |
|
|
345
|
+
|
|
346
|
+
`seconds: 0` marks a phase whose duration is human thinking time, not pipeline wall-clock — the
|
|
347
|
+
funnel signal there is the event, not how long it took. `/doctor`, `/audit`, `/refactor`,
|
|
348
|
+
`/align-ds`, `/init-pipeline` and `/update-pipeline` **never** ping: they sit outside the funnel, and
|
|
349
|
+
keeping them out is what holds the collected set to what the consent text describes.
|
|
331
350
|
|
|
332
351
|
**What one event contains** (strict allowlist, ~200 bytes):
|
|
333
352
|
|
|
@@ -15,7 +15,9 @@ enabled: true # cfg:enabled — master switch; false disables ev
|
|
|
15
15
|
# (/init-pipeline or /update-pipeline ask once per machine; they record your answer here).
|
|
16
16
|
# What is sent when enabled: core version, OS, phase name, wall-clock seconds, per-surface
|
|
17
17
|
# result counts, and a SHA-256 HASH of the feature id — never repo names, paths, code, spec
|
|
18
|
-
# content, emails, or IPs.
|
|
18
|
+
# content, emails, or IPs. One ping per pipeline phase, /brainstorm through /ship; setup and
|
|
19
|
+
# maintenance commands (/doctor, /init-pipeline, /update-pipeline, /audit, /refactor,
|
|
20
|
+
# /align-ds) never ping. Sent fire-and-forget (2s timeout, silent on failure) by
|
|
19
21
|
# pipeline/scripts/telemetry-send.sh. Withdraw anytime: set enabled: false. Erase your history:
|
|
20
22
|
# see SCHEMA.md §Telemetry (DELETE by install_id).
|
|
21
23
|
telemetry:
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
# telemetry-send.sh — fire-and-forget anonymous usage ping (SCHEMA.md §Telemetry).
|
|
3
3
|
#
|
|
4
4
|
# telemetry-send.sh <phase> <feature_id> <seconds> [results]
|
|
5
|
-
# phase build|review|fix|
|
|
5
|
+
# phase brainstorm|spec|build|smoke|review|fix|ship — the feature funnel, and only it.
|
|
6
|
+
# Setup/maintenance commands never ping (SCHEMA.md §Telemetry).
|
|
6
7
|
# feature the feature id — NEVER sent raw; SHA-256-hashed to 12 hex chars
|
|
7
8
|
# seconds batch wall-clock
|
|
8
9
|
# results optional compact summary, e.g. "ok,ok" or "REVISE:3"
|
|
@@ -35,6 +36,13 @@ install_id="$(tval install_id)"; [ -n "$install_id" ] || exit 0
|
|
|
35
36
|
phase="${1:-}"; feature="${2:-}"; seconds="${3:-0}"; results="${4:-}"
|
|
36
37
|
[ -n "$phase" ] || exit 0
|
|
37
38
|
|
|
39
|
+
# Allowlist the phase here — the collector accepts any string, so a typo in a command
|
|
40
|
+
# file would silently pollute the dataset with a phantom phase nobody notices.
|
|
41
|
+
case "$phase" in
|
|
42
|
+
brainstorm|spec|build|smoke|review|fix|ship) ;;
|
|
43
|
+
*) exit 0 ;;
|
|
44
|
+
esac
|
|
45
|
+
|
|
38
46
|
if command -v shasum >/dev/null 2>&1; then
|
|
39
47
|
fhash=$(printf '%s' "$feature" | shasum -a 256 | cut -c1-12)
|
|
40
48
|
else
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
// locally with `node scripts/validate-core.mjs`.
|
|
5
5
|
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
// fileURLToPath, not .pathname — on Windows the latter yields "/C:/…", which
|
|
10
|
+
// join() then resolves against the cwd drive ("C:\C:\…") and every read ENOENTs.
|
|
11
|
+
const root = fileURLToPath(new URL("..", import.meta.url));
|
|
9
12
|
const errors = [];
|
|
10
13
|
const fail = (file, msg) => errors.push(`${file}: ${msg}`);
|
|
11
14
|
|
|
@@ -109,6 +112,40 @@ const steps = join(root, "core/templates/steps/init-pipeline");
|
|
|
109
112
|
if (!existsSync(steps) || readdirSync(steps).length === 0)
|
|
110
113
|
fail("core/templates/steps/init-pipeline", "router step files missing/empty");
|
|
111
114
|
|
|
115
|
+
// ── telemetry coverage ──────────────────────────────────────────────────────
|
|
116
|
+
// The funnel is only readable if every one of its stages pings — a single missing
|
|
117
|
+
// one silently truncates it (that is how /smoke, /review and /fix went unreported
|
|
118
|
+
// until 1.2.3). The phase list here must match SCHEMA.md §Telemetry's table.
|
|
119
|
+
const FUNNEL = ["brainstorm", "spec", "build", "smoke", "review", "fix", "ship"];
|
|
120
|
+
for (const c of FUNNEL)
|
|
121
|
+
if (!/usage ping/i.test(read(`core/commands/${c}.md`)))
|
|
122
|
+
fail(`core/commands/${c}.md`, "funnel command with no usage ping — breaks the telemetry funnel");
|
|
123
|
+
// …and nothing outside the funnel may ping (consent text scopes it to the funnel).
|
|
124
|
+
for (const f of readdirSync(join(root, "core/commands"))) {
|
|
125
|
+
const c = f.replace(/\.md$/, "");
|
|
126
|
+
// `telemetry-send.sh` + an argument = a call site; the bare filename (e.g. /doctor
|
|
127
|
+
// listing the scripts it checks for) is a mention, not a ping.
|
|
128
|
+
if (!FUNNEL.includes(c) && /telemetry-send\.sh +\S|usage ping/i.test(read(`core/commands/${f}`)))
|
|
129
|
+
fail(`core/commands/${f}`, "non-funnel command pings telemetry — outside the consented scope");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── shipped scripts ─────────────────────────────────────────────────────────
|
|
133
|
+
// Every scripts/*.sh must be copied by BOTH shell installers. Callers chain these
|
|
134
|
+
// with `|| true`, so one an installer forgets is a silent no-op forever — no kanban
|
|
135
|
+
// card moves, no telemetry ping, no error. CI is the only place this is loud.
|
|
136
|
+
// The third installer, bin/cli.js (what `npx cohorte` runs), copies by rule rather
|
|
137
|
+
// than by name, so grepping for filenames can't see it — ci.yml dry-runs it into a
|
|
138
|
+
// scratch HOME and asserts the same postconditions instead. Both are needed: this
|
|
139
|
+
// check catches a forgotten name, that one catches a drifted rule.
|
|
140
|
+
// A `<name>.sh` with a `<name>.sh.template` sibling is a locally-rendered artifact
|
|
141
|
+
// (this repo dogfoods its own /init-pipeline), not a core asset — skip those.
|
|
142
|
+
const installers = { "install.sh": read("install.sh"), "install.ps1": read("install.ps1") };
|
|
143
|
+
const shipped = readdirSync(join(root, "scripts"));
|
|
144
|
+
for (const f of shipped.filter((f) => f.endsWith(".sh") && !shipped.includes(`${f}.template`)))
|
|
145
|
+
for (const [name, src] of Object.entries(installers))
|
|
146
|
+
if (!src.includes(`scripts/${f}`) && !src.includes(`scripts\\${f}`))
|
|
147
|
+
fail(name, `never copies scripts/${f} into pipeline/scripts/ (silent no-op at runtime)`);
|
|
148
|
+
|
|
112
149
|
// ── report ──────────────────────────────────────────────────────────────────
|
|
113
150
|
if (errors.length) {
|
|
114
151
|
console.error(`validate-core: ${errors.length} error(s)\n`);
|