cohorte 1.2.1 → 1.2.2

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 CHANGED
@@ -3,6 +3,11 @@
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.2 — 2026-07-29
7
+
8
+ - The reference collector moved to its own (private) deployment repo; the public repo keeps
9
+ the collector API contract in SCHEMA.md §Telemetry. No behavior change for users.
10
+
6
11
  ## 1.2.1 — 2026-07-29
7
12
 
8
13
  - Telemetry collector URL shipped as the config-template default
package/README.md CHANGED
@@ -277,7 +277,7 @@ result counts, and a *hash* of the feature id — never repo names, paths, code,
277
277
  recorded so you're never re-asked. Withdraw anytime (`telemetry.enabled: false` in
278
278
  `~/.claude/cohorte.config.yaml`); erase your history anytime (`/doctor` prints your `install_id`,
279
279
  the collector's `DELETE /v1/install/<id>` drops it). Full spec + GDPR details:
280
- `profile/SCHEMA.md` §Telemetry; reference collector in `telemetry/`.
280
+ `profile/SCHEMA.md` §Telemetry (including the collector API contract).
281
281
 
282
282
  ## License
283
283
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cohorte",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
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"
@@ -16,7 +16,6 @@
16
16
  "dashboard/server",
17
17
  "dashboard/dist",
18
18
  "dashboard/README.md",
19
- "telemetry",
20
19
  "install.sh",
21
20
  "install.ps1",
22
21
  "CHANGELOG.md"
package/profile/SCHEMA.md CHANGED
@@ -348,9 +348,9 @@ work without revealing what is being built.
348
348
  the next phase, no restart.
349
349
  - **Erasure** — `/doctor` prints your `install_id`; send
350
350
  `curl -X DELETE <endpoint-origin>/v1/install/<install_id>` and the collector drops every event
351
- for that id (the reference collector in `telemetry/` implements this and stores no IPs).
351
+ for that id (the deployed collector implements this and stores no IPs).
352
352
  - **Access/portability** — events are keyed by your `install_id`; ask the operator for an export.
353
353
 
354
- **Collector contract** (implement your own, or deploy `telemetry/collector.mjs`):
354
+ **Collector contract** (any implementation must honor it):
355
355
  `POST /v1/events` (one JSON event, allowlisted fields) · `DELETE /v1/install/<id>` (erasure) ·
356
356
  `GET /healthz`. Operators must not retain IP-bearing access logs for the ingest vhost.
@@ -1,49 +0,0 @@
1
- # cohorte telemetry collector (reference)
2
-
3
- Zero-dependency ingest endpoint for cohorte's **opt-in** anonymous usage pings
4
- (see the repo README §Privacy and `profile/SCHEMA.md` §Telemetry for what is —
5
- and is not — collected).
6
-
7
- ## Deploy (any VPS, Node ≥ 18)
8
-
9
- ```sh
10
- TELEMETRY_DATA=/var/lib/cohorte-telemetry/events.ndjson \
11
- TELEMETRY_PORT=8787 node collector.mjs
12
- ```
13
-
14
- Put it behind HTTPS (Caddy example):
15
-
16
- ```
17
- telemetry.yourdomain.tld {
18
- reverse_proxy 127.0.0.1:8787
19
- log { output discard } # GDPR: do NOT keep access logs with IPs for this vhost
20
- }
21
- ```
22
-
23
- Then set the public URL as the `endpoint:` default in
24
- `profile/cohorte.config.template.yaml` (anchor `cfg:telemetry_endpoint`) and release —
25
- consenting installs start sending to it.
26
-
27
- ## API
28
-
29
- | Method | Path | Purpose |
30
- |---|---|---|
31
- | `POST` | `/v1/events` | Ingest one event (strict field allowlist, 4 KB max) |
32
- | `DELETE` | `/v1/install/<install_id>` | **Right to erasure** — drops every event for that id |
33
- | `GET` | `/healthz` | Liveness |
34
-
35
- Storage is an append-only NDJSON file — point your dashboard (or a cron that loads
36
- into SQLite/Postgres) at it. One JSON object per line:
37
-
38
- ```json
39
- {"v":1,"install_id":"…","ts":"…","core_version":"1.2.0","os":"Darwin","event":"phase","phase":"build","feature_hash":"a1b2c3d4e5f6","seconds":412,"results":"ok,ok","received_at":"…"}
40
- ```
41
-
42
- ## GDPR checklist (operator side)
43
-
44
- - [x] Opt-in only — the client never sends without recorded consent
45
- - [x] No IPs stored (collector ignores them; disable proxy access logs)
46
- - [x] Data minimization — hashed feature ids, no repo names/paths/code
47
- - [x] Erasure — `DELETE /v1/install/<id>`; the user finds their id via `/doctor`
48
- - [x] Withdrawal — `telemetry.enabled: false` in `~/.claude/cohorte.config.yaml`
49
- - [ ] Your privacy notice — link the repo README §Privacy from wherever you present cohorte
@@ -1,79 +0,0 @@
1
- #!/usr/bin/env node
2
- // Reference telemetry collector for cohorte — zero-dependency, GDPR-first.
3
- // Deploy on any VPS behind HTTPS (Caddy/nginx). Storage: NDJSON append-only file,
4
- // one event per line — read it from your own dashboard however you like.
5
- //
6
- // TELEMETRY_DATA=/var/lib/cohorte-telemetry/events.ndjson \
7
- // TELEMETRY_PORT=8787 node collector.mjs
8
- //
9
- // GDPR notes:
10
- // - IPs are NEVER stored (this process doesn't read them; make sure your reverse
11
- // proxy's access log is disabled or anonymized for this vhost).
12
- // - DELETE /v1/install/<install_id> erases every event for that id (right to erasure —
13
- // the install_id printed by /doctor is the user's erasure key).
14
- // - Events are validated against a strict allowlist of fields; anything else is dropped.
15
- import { createServer } from "node:http";
16
- import { appendFileSync, existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
17
- import { dirname } from "node:path";
18
-
19
- const DATA = process.env.TELEMETRY_DATA || "./events.ndjson";
20
- const PORT = Number(process.env.TELEMETRY_PORT || 8787);
21
- const MAX_BODY = 4096;
22
-
23
- mkdirSync(dirname(DATA), { recursive: true });
24
-
25
- const FIELDS = ["v", "install_id", "ts", "core_version", "os", "event",
26
- "phase", "feature_hash", "seconds", "results"];
27
- const ID_RE = /^[A-Za-z0-9-]{8,64}$/;
28
-
29
- function sanitize(raw) {
30
- const e = {};
31
- for (const k of FIELDS) if (k in raw) e[k] = raw[k];
32
- if (e.v !== 1) return null;
33
- if (typeof e.install_id !== "string" || !ID_RE.test(e.install_id)) return null;
34
- if (typeof e.event !== "string" || e.event.length > 32) return null;
35
- for (const k of ["ts", "core_version", "os", "phase", "feature_hash", "results"])
36
- if (k in e && (typeof e[k] !== "string" || e[k].length > 64)) return null;
37
- if ("seconds" in e && typeof e.seconds !== "number") return null;
38
- e.received_at = new Date().toISOString();
39
- return e;
40
- }
41
-
42
- const server = createServer((req, res) => {
43
- const done = (code, body) => { res.writeHead(code, { "content-type": "application/json" }); res.end(JSON.stringify(body)); };
44
-
45
- if (req.method === "GET" && req.url === "/healthz") return done(200, { ok: true });
46
-
47
- if (req.method === "POST" && req.url === "/v1/events") {
48
- let body = "";
49
- req.on("data", (c) => { body += c; if (body.length > MAX_BODY) req.destroy(); });
50
- req.on("end", () => {
51
- try {
52
- const e = sanitize(JSON.parse(body));
53
- if (!e) return done(400, { error: "invalid event" });
54
- appendFileSync(DATA, JSON.stringify(e) + "\n");
55
- done(204, {});
56
- } catch { done(400, { error: "bad json" }); }
57
- });
58
- return;
59
- }
60
-
61
- // Right to erasure: drop every event carrying this install_id.
62
- const del = req.url?.match(/^\/v1\/install\/([A-Za-z0-9-]{8,64})$/);
63
- if (req.method === "DELETE" && del) {
64
- const id = del[1];
65
- let kept = [], dropped = 0;
66
- if (existsSync(DATA))
67
- for (const line of readFileSync(DATA, "utf8").split("\n")) {
68
- if (!line.trim()) continue;
69
- if (line.includes(`"install_id":"${id}"`)) { dropped++; continue; }
70
- kept.push(line);
71
- }
72
- writeFileSync(DATA, kept.length ? kept.join("\n") + "\n" : "");
73
- return done(200, { erased: dropped });
74
- }
75
-
76
- done(404, { error: "not found" });
77
- });
78
-
79
- server.listen(PORT, () => console.log(`cohorte telemetry collector on :${PORT} → ${DATA}`));