murmur8 4.5.1 → 4.6.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/.blueprint/features/feature_pipeline-telemetry/FEATURE_SPEC.md +297 -0
- package/.blueprint/features/feature_pipeline-telemetry/IMPLEMENTATION_PLAN.md +34 -0
- package/.blueprint/features/feature_pipeline-telemetry/handoff-alex.md +21 -0
- package/.blueprint/features/feature_pipeline-telemetry/handoff-cass.md +25 -0
- package/.blueprint/features/feature_pipeline-telemetry/handoff-nigel.md +20 -0
- package/.blueprint/features/feature_pipeline-telemetry/story-failed-queue-retry.md +53 -0
- package/.blueprint/features/feature_pipeline-telemetry/story-identifiers.md +47 -0
- package/.blueprint/features/feature_pipeline-telemetry/story-init-integration.md +48 -0
- package/.blueprint/features/feature_pipeline-telemetry/story-payload-send.md +54 -0
- package/.blueprint/features/feature_pipeline-telemetry/story-telemetry-activation.md +54 -0
- package/.blueprint/features/feature_pipeline-telemetry/story-telemetry-config-command.md +52 -0
- package/README.md +54 -0
- package/package.json +1 -1
- package/src/commands/telemetry-config.js +16 -0
- package/src/index.js +14 -0
- package/src/init.js +5 -0
- package/src/telemetry.js +198 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
---
|
|
2
|
+
featureId: a3f8c2d1-7e54-4b09-8f16-23a1e5d94c72
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Feature Specification — Pipeline Telemetry
|
|
6
|
+
|
|
7
|
+
## 1. Feature Intent
|
|
8
|
+
|
|
9
|
+
**Why this feature exists.**
|
|
10
|
+
|
|
11
|
+
- **Problem being addressed:** Teams using murmur8 have no visibility into pipeline usage across projects or over time at an aggregate level. Individual projects may have local history, but there is no mechanism to send structured execution data to a central observability endpoint for fleet-wide analysis, cost attribution, or quality tracking.
|
|
12
|
+
- **User need:** Platform teams and administrators want to understand how murmur8 is being used — which features succeed or fail, how long stages take, and whether quality is improving. This is opt-in by configuration, not mandated.
|
|
13
|
+
- **System purpose alignment:** Per SYSTEM_SPEC.md:Section 8 (Cross-Cutting Concerns:Observability), the system aims for observability. This feature extends that observability to an external, configurable telemetry layer without modifying the core pipeline flow.
|
|
14
|
+
|
|
15
|
+
> This feature reinforces the system's observability goals and does not alter pipeline behaviour. It is completely silent when no endpoint is configured.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 2. Scope
|
|
20
|
+
|
|
21
|
+
### In Scope
|
|
22
|
+
|
|
23
|
+
- Sending a structured JSON event payload via HTTP POST to a configurable telemetry endpoint at the end of each pipeline run
|
|
24
|
+
- Reading endpoint URL and API key from `.env` file and real environment variables (env vars take precedence)
|
|
25
|
+
- Non-blocking send: failures never interrupt the pipeline
|
|
26
|
+
- Queuing failed sends to `.claude/telemetry-failed.json`; retrying queued sends at the start of the next pipeline run
|
|
27
|
+
- Compressing artifact content (FEATURE_SPEC.md and story files) with zlib gzip + base64 before inclusion in payload
|
|
28
|
+
- Generating a `runId` (UUID v4) at pipeline start for correlating events
|
|
29
|
+
- Writing a `featureId` (UUID v4) into FEATURE_SPEC.md YAML frontmatter by Alex on first creation; stable across retries
|
|
30
|
+
- New CLI command `murmur8 telemetry-config` to display current telemetry configuration
|
|
31
|
+
- `init` command creating/appending a commented-out telemetry template to `.env` and ensuring `.env` is in `.gitignore`
|
|
32
|
+
- Exporting telemetry functions from `src/index.js`
|
|
33
|
+
- Storing `runId` in the history entry
|
|
34
|
+
|
|
35
|
+
### Out of Scope
|
|
36
|
+
|
|
37
|
+
- An opt-out flag (presence of URL = opt-in; absence = opt-out)
|
|
38
|
+
- Real-time or streaming telemetry (end-of-run POST only)
|
|
39
|
+
- Telemetry for non-pipeline CLI commands (e.g., `history`, `queue`)
|
|
40
|
+
- Encryption of payload beyond HTTPS transport
|
|
41
|
+
- Telemetry dashboard or receiver implementation
|
|
42
|
+
- dotenv or any third-party `.env` parsing dependency (manual parsing only)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 3. Actors Involved
|
|
47
|
+
|
|
48
|
+
### Human User / Administrator
|
|
49
|
+
|
|
50
|
+
- **Can do:** Configure telemetry endpoint and key via `.env`; view telemetry config via `murmur8 telemetry-config`; remove config to disable
|
|
51
|
+
- **Cannot do:** Trigger telemetry manually; view the contents of failed send queue via CLI (file inspection only)
|
|
52
|
+
|
|
53
|
+
### Pipeline Orchestrator (internal)
|
|
54
|
+
|
|
55
|
+
- **Can do:** Generate `runId` at pipeline start; call telemetry send at pipeline end; enqueue failed sends; retry failed sends at start of next run
|
|
56
|
+
- **Cannot do:** Block pipeline on telemetry failure; modify payload after send attempt
|
|
57
|
+
|
|
58
|
+
### Alex Agent (internal)
|
|
59
|
+
|
|
60
|
+
- **Can do:** Write `featureId` UUID into FEATURE_SPEC.md YAML frontmatter on first creation; preserve existing `featureId` on re-runs
|
|
61
|
+
- **Cannot do:** Regenerate `featureId` if one already exists
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 4. Behaviour Overview
|
|
66
|
+
|
|
67
|
+
### Happy-path behaviour
|
|
68
|
+
|
|
69
|
+
1. User configures `MURMUR8_TELEMETRY_URL` (and optionally `MURMUR8_TELEMETRY_KEY`) in `.env` or as real environment variables
|
|
70
|
+
2. At pipeline start (Step 5 of SKILL.md), orchestrator generates a `runId` UUID v4 and stores it in working context
|
|
71
|
+
3. Alex writes a `featureId` UUID into FEATURE_SPEC.md YAML frontmatter (if not already present)
|
|
72
|
+
4. Pipeline executes normally; timings, stage statuses, and feedback are collected as usual
|
|
73
|
+
5. At pipeline end (Step 12 of SKILL.md), `src/telemetry.js` builds the payload, compresses artifacts, and POSTs to the configured URL
|
|
74
|
+
6. On success, no user-visible output occurs (silent)
|
|
75
|
+
7. On failure, the payload is appended to `.claude/telemetry-failed.json` silently
|
|
76
|
+
|
|
77
|
+
### Key alternatives or branches
|
|
78
|
+
|
|
79
|
+
- **No URL configured:** Telemetry module does nothing; pipeline continues as normal
|
|
80
|
+
- **Send failure (network error, non-2xx response):** Payload written to failed queue; no pipeline interruption
|
|
81
|
+
- **Retry at next run start:** Any entries in `.claude/telemetry-failed.json` are attempted before the new run proceeds; failures remain in queue
|
|
82
|
+
- **`--no-feedback` flag used:** `feedback` block omitted from payload
|
|
83
|
+
- **`featureId` already present in FEATURE_SPEC.md:** Alex preserves existing value; does not regenerate
|
|
84
|
+
|
|
85
|
+
### User-visible outcomes
|
|
86
|
+
|
|
87
|
+
- No output when telemetry is working correctly (fully silent)
|
|
88
|
+
- `murmur8 telemetry-config` shows configured URL (masked key), retry queue depth
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 5. State & Lifecycle Interactions
|
|
93
|
+
|
|
94
|
+
### States entered
|
|
95
|
+
|
|
96
|
+
- **telemetry_pending:** `runId` generated at pipeline start; stored in working context
|
|
97
|
+
- **telemetry_sent:** Payload successfully POSTed at pipeline end
|
|
98
|
+
- **telemetry_queued:** Send failed; payload appended to `.claude/telemetry-failed.json`
|
|
99
|
+
|
|
100
|
+
### States modified
|
|
101
|
+
|
|
102
|
+
- `featureId` written into FEATURE_SPEC.md frontmatter (by Alex, on first run only)
|
|
103
|
+
- History entry extended with `runId` field
|
|
104
|
+
- `.claude/telemetry-failed.json` appended to on send failure; entries removed on successful retry
|
|
105
|
+
|
|
106
|
+
### This feature is:
|
|
107
|
+
|
|
108
|
+
- **State-creating:** Creates `runId` per run; may create `telemetry-failed.json`
|
|
109
|
+
- **State-transitioning:** Moves failed payloads from queued → sent on retry
|
|
110
|
+
- **Not state-constraining:** Does not block any pipeline operations
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 6. Rules & Decision Logic
|
|
115
|
+
|
|
116
|
+
### Rule: Telemetry Activation
|
|
117
|
+
|
|
118
|
+
- **Description:** Telemetry is active if and only if `MURMUR8_TELEMETRY_URL` is set (non-empty) in `.env` or real env
|
|
119
|
+
- **Inputs:** Presence and value of `MURMUR8_TELEMETRY_URL`
|
|
120
|
+
- **Outputs:** Active or inactive module
|
|
121
|
+
- **Deterministic:** Yes
|
|
122
|
+
|
|
123
|
+
### Rule: Environment Variable Precedence
|
|
124
|
+
|
|
125
|
+
- **Description:** Real environment variables take precedence over `.env` file values for both `MURMUR8_TELEMETRY_URL` and `MURMUR8_TELEMETRY_KEY`
|
|
126
|
+
- **Inputs:** `process.env` values, `.env` file parse result
|
|
127
|
+
- **Outputs:** Resolved URL and key
|
|
128
|
+
- **Deterministic:** Yes
|
|
129
|
+
|
|
130
|
+
### Rule: runId Generation
|
|
131
|
+
|
|
132
|
+
- **Description:** A new UUID v4 `runId` is generated at Step 5 of SKILL.md for every pipeline run, regardless of whether telemetry is active
|
|
133
|
+
- **Inputs:** None (random UUID)
|
|
134
|
+
- **Outputs:** UUID v4 string stored in working context and history entry
|
|
135
|
+
- **Deterministic:** No (random)
|
|
136
|
+
|
|
137
|
+
### Rule: featureId Stability
|
|
138
|
+
|
|
139
|
+
- **Description:** `featureId` is written by Alex into FEATURE_SPEC.md frontmatter on first creation. If FEATURE_SPEC.md already contains a `featureId`, it must not be changed.
|
|
140
|
+
- **Inputs:** Existing FEATURE_SPEC.md content
|
|
141
|
+
- **Outputs:** Frontmatter with stable `featureId`
|
|
142
|
+
- **Deterministic:** Yes (preserves existing); No for initial generation (random UUID)
|
|
143
|
+
|
|
144
|
+
### Rule: Non-blocking Send
|
|
145
|
+
|
|
146
|
+
- **Description:** The HTTP POST to the telemetry endpoint must not block or throw into the pipeline. Any network or HTTP error results in silent queue, not pipeline abort.
|
|
147
|
+
- **Inputs:** HTTP response code, network errors
|
|
148
|
+
- **Outputs:** Success (silent) or enqueue to failed queue (silent)
|
|
149
|
+
- **Deterministic:** Yes
|
|
150
|
+
|
|
151
|
+
### Rule: Failed Queue Retry
|
|
152
|
+
|
|
153
|
+
- **Description:** At the start of each pipeline run (before Step 1 processing), any entries in `.claude/telemetry-failed.json` are attempted. Successfully sent entries are removed; failures remain.
|
|
154
|
+
- **Inputs:** Contents of `.claude/telemetry-failed.json`
|
|
155
|
+
- **Outputs:** Updated queue file (or file removed if empty)
|
|
156
|
+
- **Deterministic:** Yes
|
|
157
|
+
|
|
158
|
+
### Rule: Artifact Compression
|
|
159
|
+
|
|
160
|
+
- **Description:** FEATURE_SPEC.md and any `story-*.md` files are gzip-compressed (zlib) and base64-encoded before inclusion in the `artifacts` block. Each file is keyed by its filename.
|
|
161
|
+
- **Inputs:** File contents
|
|
162
|
+
- **Outputs:** `{ "filename": "<base64-gzip>" }` per file
|
|
163
|
+
- **Deterministic:** Yes (given same input)
|
|
164
|
+
|
|
165
|
+
### Rule: Authorization Header
|
|
166
|
+
|
|
167
|
+
- **Description:** If `MURMUR8_TELEMETRY_KEY` is set, it is sent as `Authorization: Bearer <key>` header. If not set, the header is omitted.
|
|
168
|
+
- **Inputs:** Key value
|
|
169
|
+
- **Outputs:** HTTP Authorization header presence
|
|
170
|
+
- **Deterministic:** Yes
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 7. Dependencies
|
|
175
|
+
|
|
176
|
+
### System components
|
|
177
|
+
|
|
178
|
+
- `src/history.js` — Must pass `runId` to the history entry write
|
|
179
|
+
- `SKILL.md` Step 5 — Must generate `runId`; Step 12 — Must call telemetry send
|
|
180
|
+
- `src/init.js` — Must create/append `.env` template and update `.gitignore`
|
|
181
|
+
- `bin/cli.js` — Must register `telemetry-config` command
|
|
182
|
+
- `src/index.js` — Must export telemetry functions
|
|
183
|
+
|
|
184
|
+
### External systems
|
|
185
|
+
|
|
186
|
+
- Configurable HTTP/HTTPS endpoint (operator-supplied); must accept POST with JSON body
|
|
187
|
+
- zlib (Node.js built-in) — for gzip compression
|
|
188
|
+
- Node.js `crypto` module (built-in) — for UUID v4 generation
|
|
189
|
+
- Node.js `https`/`http` modules (built-in) — for HTTP POST; no external HTTP library
|
|
190
|
+
|
|
191
|
+
### Operational dependencies
|
|
192
|
+
|
|
193
|
+
- `.env` file at project root (optional; telemetry inactive if absent or URL not set)
|
|
194
|
+
- File system access to `.claude/` directory for failed queue
|
|
195
|
+
- Network access to configured endpoint (no hard dependency — failures are tolerated)
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 8. Non-Functional Considerations
|
|
200
|
+
|
|
201
|
+
### Performance sensitivity
|
|
202
|
+
|
|
203
|
+
- Telemetry send is fire-and-forget (async, non-blocking); it must not add measurable latency to the user-visible pipeline completion
|
|
204
|
+
- Artifact compression is performed in-process; acceptable for typical spec file sizes (<50 KB combined)
|
|
205
|
+
|
|
206
|
+
### Audit/logging needs
|
|
207
|
+
|
|
208
|
+
- The telemetry payload itself constitutes a structured audit record
|
|
209
|
+
- `runId` links telemetry events to local history entries for cross-referencing
|
|
210
|
+
- `featureId` provides stable identity across retries of the same feature
|
|
211
|
+
|
|
212
|
+
### Error tolerance
|
|
213
|
+
|
|
214
|
+
- Send failures must be fully silent to the user (no output, no pipeline interruption)
|
|
215
|
+
- Failed queue must not grow unboundedly — implementations should consider a maximum retry depth (e.g., 50 entries), dropping oldest on overflow; exact limit is an implementation decision
|
|
216
|
+
|
|
217
|
+
### Security implications
|
|
218
|
+
|
|
219
|
+
- `MURMUR8_TELEMETRY_KEY` must never be logged or displayed in plaintext; `telemetry-config` command must mask the key (e.g., `sk-****1234`)
|
|
220
|
+
- `.env` file must be added to `.gitignore` by the `init` command to prevent accidental credential commit
|
|
221
|
+
- `gitEmail` and `repoUrl` are included in the identity block; operators must be aware these are transmitted to the configured endpoint
|
|
222
|
+
- Payload transmitted over HTTPS (URL is operator-configured; framework does not enforce HTTPS but should warn if `http://` is used)
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 9. Assumptions & Open Questions
|
|
227
|
+
|
|
228
|
+
### Assumptions
|
|
229
|
+
|
|
230
|
+
- ASSUMPTION: Node.js built-in `zlib`, `crypto`, `https`/`http` modules are sufficient; no external dependencies needed
|
|
231
|
+
- ASSUMPTION: `git config user.name`, `git config user.email`, and `git remote get-url origin` are available and executable in the pipeline environment
|
|
232
|
+
- ASSUMPTION: Artifact files (FEATURE_SPEC.md, story files) are small enough that synchronous gzip compression is non-blocking in practice
|
|
233
|
+
- ASSUMPTION: The telemetry endpoint is operator-managed; murmur8 does not validate the endpoint URL beyond basic format
|
|
234
|
+
- ASSUMPTION: UUID v4 can be generated using `crypto.randomUUID()` (Node.js 15.6+; within Node.js 18+ requirement)
|
|
235
|
+
|
|
236
|
+
### Open Questions
|
|
237
|
+
|
|
238
|
+
- Should the failed queue have a configurable maximum depth, or a fixed cap (e.g., 50)?
|
|
239
|
+
- Should `telemetry-config` provide a `--test` flag to send a synthetic ping to verify connectivity?
|
|
240
|
+
- Should the `init` command emit a user-visible notice that `.env` was created (to aid discoverability)?
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 10. Impact on System Specification
|
|
245
|
+
|
|
246
|
+
### Alignment assessment
|
|
247
|
+
|
|
248
|
+
This feature **reinforces existing system assumptions** and introduces a minor extension:
|
|
249
|
+
|
|
250
|
+
- Per SYSTEM_SPEC.md:Section 8 (Observability), the system already exposes queue status and completion summaries. This feature adds an optional external observability channel.
|
|
251
|
+
- SYSTEM_SPEC.md:Section 3 (Out of Scope) excludes CI/CD integration, but telemetry to an operator endpoint is a distinct concern (usage analytics, not CI automation). No contradiction.
|
|
252
|
+
- SYSTEM_SPEC.md:Section 9 (Non-Functional:Reliability) states the system must not be blocked by side-effects. This feature's non-blocking, silent-failure design directly upholds that invariant.
|
|
253
|
+
|
|
254
|
+
### Minor extension to system spec warranted
|
|
255
|
+
|
|
256
|
+
The following additions to SYSTEM_SPEC.md are flagged for consideration (not applied here):
|
|
257
|
+
|
|
258
|
+
1. **Section 5 (Core Domain Concepts):** Add entry for `Run Identifier (runId)` — a UUID v4 generated per pipeline invocation for telemetry correlation and history linkage.
|
|
259
|
+
2. **Section 5:** Add entry for `Feature Identifier (featureId)` — a UUID v4 written into FEATURE_SPEC.md frontmatter by Alex on first creation; stable across retries.
|
|
260
|
+
3. **Section 8 (Cross-Cutting Concerns):** Add sub-section for external telemetry noting the opt-in model.
|
|
261
|
+
|
|
262
|
+
These are **non-breaking extensions** flagged for Alex/human decision.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## 11. Handover to BA (Cass)
|
|
267
|
+
|
|
268
|
+
### Story themes
|
|
269
|
+
|
|
270
|
+
1. **Telemetry activation** — Configuring the endpoint and key; understanding opt-in model
|
|
271
|
+
2. **runId and featureId generation** — Identifier lifecycle at pipeline start and spec creation
|
|
272
|
+
3. **Payload construction and send** — Building and POSTing the event at pipeline end
|
|
273
|
+
4. **Failed send queue** — Queuing failed sends and retrying at next run start
|
|
274
|
+
5. **init integration** — `.env` template creation and `.gitignore` protection
|
|
275
|
+
6. **telemetry-config command** — Viewing current configuration with masked key
|
|
276
|
+
|
|
277
|
+
### Expected story boundaries
|
|
278
|
+
|
|
279
|
+
- Activation/config story should be separate from the payload/send story
|
|
280
|
+
- `featureId` (Alex behaviour) and `runId` (orchestrator behaviour) may be combined into one story or split by agent concern
|
|
281
|
+
- Failed queue retry is a distinct story (edge case behaviour, important for reliability)
|
|
282
|
+
- `init` changes are a small story but have a user-visible outcome (discoverability of the feature)
|
|
283
|
+
|
|
284
|
+
### Areas needing careful story framing
|
|
285
|
+
|
|
286
|
+
- The "completely silent when no endpoint configured" behaviour needs an explicit AC (absence of output is the expected outcome)
|
|
287
|
+
- The precedence rule (real env vars override `.env`) needs a dedicated AC
|
|
288
|
+
- The `featureId` preservation rule (no regeneration on re-run) needs a dedicated AC
|
|
289
|
+
- Security: key masking in `telemetry-config` output needs explicit AC
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 12. Change Log (Feature-Level)
|
|
294
|
+
|
|
295
|
+
| Date | Change | Reason | Raised By |
|
|
296
|
+
|------------|----------------------------------------|--------------------------------|-----------|
|
|
297
|
+
| 2026-05-19 | Initial feature specification created | New feature: telemetry layer | Alex |
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Implementation Plan — pipeline-telemetry
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Create `src/telemetry.js` as the core module exporting all telemetry primitives (config loading, UUID generation, payload building, gzip compression, queue management, init helpers, and config formatting). Add `src/commands/telemetry-config.js` as a thin CLI wrapper, then wire the new module into `src/index.js`, `src/init.js`, and `bin/cli.js`. No external dependencies are required; Node.js built-ins (`crypto`, `zlib`, `https`, `fs`) cover all needs.
|
|
6
|
+
|
|
7
|
+
## Files to Create / Modify
|
|
8
|
+
|
|
9
|
+
| File | Action | Purpose |
|
|
10
|
+
|------|--------|---------|
|
|
11
|
+
| `src/telemetry.js` | **Create** | All exported telemetry functions |
|
|
12
|
+
| `src/commands/telemetry-config.js` | **Create** | CLI command handler for `telemetry-config` |
|
|
13
|
+
| `src/index.js` | **Modify** | Export telemetry functions |
|
|
14
|
+
| `src/init.js` | **Modify** | Call `ensureDotenv` and `ensureGitignore` during init |
|
|
15
|
+
| `bin/cli.js` | **No change needed** | Dynamic command loading already handles new commands |
|
|
16
|
+
|
|
17
|
+
## Numbered Steps
|
|
18
|
+
|
|
19
|
+
1. `src/telemetry.js` CREATE `loadConfig(dotenvPath)` — manual `.env` parse; env vars win; returns `{ url, key }` (null if absent) | Tests: T-TA-1 to T-TA-6
|
|
20
|
+
2. `src/telemetry.js` ADD `generateRunId()` — returns `crypto.randomUUID()` | Tests: T-ID-1, T-ID-2, T-ID-5
|
|
21
|
+
3. `src/telemetry.js` ADD `ensureFeatureId(specPath)` — reads file, extracts YAML frontmatter `featureId`; generates UUID v4 and prepends/updates frontmatter if absent | Tests: T-ID-3, T-ID-4, T-ID-5
|
|
22
|
+
4. `src/telemetry.js` ADD `buildPayload(runData)` — assembles `{ runId, run: { featureId, slug, status, startedAt, completedAt, totalDurationMs, stages } }`; omits `feedback` key when value is falsy/empty; passes `artifacts` through unchanged | Tests: T-PS-1 to T-PS-4, T-PS-6
|
|
23
|
+
5. `src/telemetry.js` ADD `compressArtifact(content)` — synchronous `zlib.gzipSync(content)` then `.toString('base64')` | Tests: T-PS-3, T-PS-5
|
|
24
|
+
6. `src/telemetry.js` ADD `enqueueFailure(payload, queuePath)` — reads queue (or `[]` on missing/corrupt); appends payload; trims to last 50; writes JSON | Tests: T-FQ-1, T-FQ-6
|
|
25
|
+
7. `src/telemetry.js` ADD `retryQueue(queuePath, sendFn)` — reads queue; calls `sendFn(payload)` for each entry; keeps entries where `sendFn` returns falsy; writes remaining (or `[]`) back to file | Tests: T-FQ-2 to T-FQ-5
|
|
26
|
+
8. `src/telemetry.js` ADD `ensureDotenv(targetDir)` and `ensureGitignore(targetDir)` — create/append `.env` template (idempotent on `MURMUR8_TELEMETRY_URL` presence); add `.env` line to `.gitignore` if absent (create if needed) | Tests: T-II-1 to T-II-5
|
|
27
|
+
9. `src/telemetry.js` ADD `formatTelemetryConfig(config, queuePath)` — returns string with URL, masked key (`****last4` or "not set"), active/inactive status, queue depth (reads file or 0) | Tests: T-TC-1 to T-TC-6
|
|
28
|
+
10. `src/commands/telemetry-config.js` CREATE `run(args)` — loads config via `loadConfig`, reads queue path, calls `formatTelemetryConfig`, prints result; `src/index.js` ADD telemetry exports; `src/init.js` CALL `ensureDotenv`+`ensureGitignore` | Tests: T-TC-1 to T-TC-6, T-II-1 to T-II-5
|
|
29
|
+
|
|
30
|
+
## Risks
|
|
31
|
+
|
|
32
|
+
- `ensureFeatureId` must handle three frontmatter states: no frontmatter, frontmatter without `featureId`, and frontmatter with existing `featureId`. YAML is parsed with regex/string logic (no yaml dep); edge cases like multi-document or trailing dashes need careful handling.
|
|
33
|
+
- `retryQueue` contract: `sendFn` returns truthy on success (synchronous). If the real `sendTelemetry` is async, callers must `await` and pass a resolved boolean — document this clearly in comments.
|
|
34
|
+
- Queue overflow logic drops the **oldest** entries (index 0), keeping the newest. Verify slice direction: `queue.slice(-49)` then push gives correct oldest-dropped behaviour.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## Handoff Summary
|
|
2
|
+
**For:** Cass
|
|
3
|
+
**Feature:** pipeline-telemetry
|
|
4
|
+
|
|
5
|
+
### Key Decisions
|
|
6
|
+
- Telemetry is opt-in by configuration: setting `MURMUR8_TELEMETRY_URL` activates it; absence = fully silent (no flag needed to disable)
|
|
7
|
+
- Non-blocking by design: HTTP send failures enqueue to `.claude/telemetry-failed.json` silently; retry happens at next pipeline start — pipeline is never interrupted
|
|
8
|
+
- Two stable identifiers: `runId` (UUID v4, per run, generated at Step 5) and `featureId` (UUID v4, per feature spec, written by Alex into frontmatter on first creation, never regenerated)
|
|
9
|
+
- No external dependencies: uses Node.js built-ins only (`zlib`, `crypto`, `https`/`http`) — no dotenv, no HTTP client library
|
|
10
|
+
- `.env` parsed manually with real env vars taking precedence; `init` command adds commented template and ensures `.gitignore` protection
|
|
11
|
+
|
|
12
|
+
### Files Created
|
|
13
|
+
- .blueprint/features/feature_pipeline-telemetry/FEATURE_SPEC.md
|
|
14
|
+
|
|
15
|
+
### Open Questions
|
|
16
|
+
- Should the failed-send queue have a configurable max depth or a fixed cap (e.g., 50 entries)?
|
|
17
|
+
- Should `telemetry-config` offer a `--test` flag to ping the endpoint for connectivity verification?
|
|
18
|
+
- Should `init` emit a visible notice that `.env` was created, to improve discoverability?
|
|
19
|
+
|
|
20
|
+
### Critical Context
|
|
21
|
+
This feature is infrastructure-level and cross-cutting: it touches SKILL.md (Step 5 for runId, Step 12 for send), `src/init.js` (`.env` + `.gitignore`), `src/history.js` (store runId), and introduces two new files (`src/telemetry.js`, `src/commands/telemetry-config.js`). The core principle is **silent by default** — no output when working, no errors surfaced to user on failure. Cass should write stories that make the "absence of output is correct behaviour" expectation explicit in acceptance criteria, as this is counter-intuitive but intentional. The `featureId` preservation rule (Alex must not regenerate an existing featureId) is a critical correctness invariant that needs a precise AC.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## Handoff Summary
|
|
2
|
+
**For:** Nigel
|
|
3
|
+
**Feature:** pipeline-telemetry
|
|
4
|
+
|
|
5
|
+
### Key Decisions
|
|
6
|
+
- Telemetry is fully silent in both directions: no output when working correctly, no error output on failure — "absence of output is the correct behaviour" is an explicit AC in story-payload-send and story-failed-queue-retry
|
|
7
|
+
- Six stories map directly to Alex's six story themes; each is independently testable
|
|
8
|
+
- `featureId` preservation (AC-4 of story-identifiers) is a critical invariant: Alex must never regenerate a `featureId` that already exists in FEATURE_SPEC.md frontmatter
|
|
9
|
+
- Environment variable precedence (process env overrides `.env`) is a dedicated AC in story-telemetry-activation (AC-3 and AC-6)
|
|
10
|
+
- API key masking in `telemetry-config` output is an explicit security AC (story-telemetry-config-command AC-2)
|
|
11
|
+
|
|
12
|
+
### Files Created
|
|
13
|
+
- .blueprint/features/feature_pipeline-telemetry/story-telemetry-activation.md
|
|
14
|
+
- .blueprint/features/feature_pipeline-telemetry/story-identifiers.md
|
|
15
|
+
- .blueprint/features/feature_pipeline-telemetry/story-payload-send.md
|
|
16
|
+
- .blueprint/features/feature_pipeline-telemetry/story-failed-queue-retry.md
|
|
17
|
+
- .blueprint/features/feature_pipeline-telemetry/story-init-integration.md
|
|
18
|
+
- .blueprint/features/feature_pipeline-telemetry/story-telemetry-config-command.md
|
|
19
|
+
|
|
20
|
+
### Open Questions
|
|
21
|
+
- Should the failed queue cap be configurable or hard-coded? (left as implementation-defined in story-failed-queue-retry AC-6)
|
|
22
|
+
- Should `init` emit a visible notice when `.env` is created? (scoped out of story-init-integration per Alex's silent-by-default principle)
|
|
23
|
+
|
|
24
|
+
### Critical Context
|
|
25
|
+
This feature is infrastructure-level and cross-cutting. Tests must cover: (1) complete silence on success and failure paths, (2) the `featureId` no-regeneration invariant, (3) env var precedence over `.env`, (4) non-blocking behaviour (pipeline state unaffected by telemetry errors), and (5) key masking security property. All new code lives in `src/telemetry.js` and `src/commands/telemetry-config.js`; existing files touched are SKILL.md (Steps 5 and 12), `src/history.js`, `src/init.js`, `bin/cli.js`, and `src/index.js`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## Handoff Summary
|
|
2
|
+
**For:** Codey
|
|
3
|
+
**Feature:** pipeline-telemetry
|
|
4
|
+
|
|
5
|
+
### Key Decisions
|
|
6
|
+
- Tests are self-contained: all helper functions (loadConfig, buildPayload, compressArtifact, etc.) are inlined in the test file — Codey must match these contracts exactly in src/telemetry.js
|
|
7
|
+
- HTTP is never called in tests; send behaviour is validated via contract (headers, payload shape, silence on success)
|
|
8
|
+
- Queue capped at 50 entries; overflow drops oldest
|
|
9
|
+
- Queue cleanup on full success: write `[]` (not delete file)
|
|
10
|
+
- All 7 story ambiguities resolved in test-spec.md assumptions
|
|
11
|
+
|
|
12
|
+
### Files Created
|
|
13
|
+
- test/artifacts/feature_pipeline-telemetry/test-spec.md
|
|
14
|
+
- test/feature_pipeline-telemetry.test.js
|
|
15
|
+
|
|
16
|
+
### Open Questions
|
|
17
|
+
- None
|
|
18
|
+
|
|
19
|
+
### Critical Context
|
|
20
|
+
Tests define the contracts Codey must implement. Key exported functions expected from src/telemetry.js: `loadConfig(dotenvPath)`, `generateRunId()`, `ensureFeatureId(specPath)`, `buildPayload(runData)`, `compressArtifact(content)`, `sendTelemetry(payload, config)`, `retryQueue(queuePath, sendFn)`. The telemetry-config command logic is also inlined in tests — match the masking format (`****last4`) and output format exactly.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Story — Failed Send Queue and Retry
|
|
2
|
+
|
|
3
|
+
### User story
|
|
4
|
+
As a platform administrator, I want failed telemetry sends to be queued silently and retried at the start of the next pipeline run so that transient network issues do not cause data loss and never interrupt pipeline execution.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
### Context / scope
|
|
9
|
+
- Failed payloads are appended to `.claude/telemetry-failed.json`
|
|
10
|
+
- Retry happens at pipeline start (before Step 1 processing) on subsequent runs
|
|
11
|
+
- All failure handling is silent: no output to user on queue write or retry
|
|
12
|
+
- The queue has a maximum depth (implementation-defined, e.g. 50 entries); oldest entries are dropped on overflow
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
### Acceptance criteria
|
|
17
|
+
|
|
18
|
+
**AC-1 — Failed send is silently queued; pipeline continues**
|
|
19
|
+
- Given telemetry is active and the HTTP POST fails (network error or non-2xx response),
|
|
20
|
+
- When the send attempt completes,
|
|
21
|
+
- Then the payload is appended to `.claude/telemetry-failed.json`, no error or warning is output to the user, and the pipeline continues normally.
|
|
22
|
+
|
|
23
|
+
**AC-2 — Queued payloads are retried at the start of the next run**
|
|
24
|
+
- Given `.claude/telemetry-failed.json` contains one or more queued payloads,
|
|
25
|
+
- When the next pipeline run starts (before Step 1),
|
|
26
|
+
- Then `src/telemetry.js` attempts to send each queued payload to the configured endpoint.
|
|
27
|
+
|
|
28
|
+
**AC-3 — Successfully retried entries are removed from the queue**
|
|
29
|
+
- Given a queued payload is retried and the endpoint returns a 2xx response,
|
|
30
|
+
- When the retry completes,
|
|
31
|
+
- Then that entry is removed from `.claude/telemetry-failed.json`; remaining failed entries stay in the file.
|
|
32
|
+
|
|
33
|
+
**AC-4 — Queue file is removed when all entries are successfully sent**
|
|
34
|
+
- Given `.claude/telemetry-failed.json` exists with entries that are all successfully retried,
|
|
35
|
+
- When the last entry is sent successfully,
|
|
36
|
+
- Then `.claude/telemetry-failed.json` is deleted (or written as an empty array — implementation choice, file absence is acceptable).
|
|
37
|
+
|
|
38
|
+
**AC-5 — Retry failures remain in queue silently**
|
|
39
|
+
- Given a queued payload is retried and the send fails again,
|
|
40
|
+
- When the retry completes,
|
|
41
|
+
- Then the entry remains in `.claude/telemetry-failed.json` and no output is produced.
|
|
42
|
+
|
|
43
|
+
**AC-6 — Queue does not grow unboundedly**
|
|
44
|
+
- Given `.claude/telemetry-failed.json` already contains the maximum allowed entries,
|
|
45
|
+
- When a new send failure occurs,
|
|
46
|
+
- Then the oldest entry is dropped and the new payload is appended, keeping the queue at the maximum depth.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### Out of scope
|
|
51
|
+
- CLI command to inspect or clear the failed queue (file inspection only)
|
|
52
|
+
- Configurable retry delay or backoff (retry happens at next pipeline start, no timer)
|
|
53
|
+
- Retry of payloads when telemetry URL is unconfigured
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Story — Run and Feature Identifiers
|
|
2
|
+
|
|
3
|
+
### User story
|
|
4
|
+
As a platform administrator, I want each pipeline run to carry a unique `runId` and each feature spec to carry a stable `featureId` so that I can correlate telemetry events with local history and track the same feature across multiple retries.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
### Context / scope
|
|
9
|
+
- `runId`: UUID v4, generated at pipeline start (Step 5 of SKILL.md), stored in working context and in the history entry
|
|
10
|
+
- `featureId`: UUID v4, written into FEATURE_SPEC.md YAML frontmatter by Alex on first creation only
|
|
11
|
+
- Both identifiers are generated using Node.js built-in `crypto.randomUUID()` — no external library
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### Acceptance criteria
|
|
16
|
+
|
|
17
|
+
**AC-1 — runId generated at pipeline start for every run**
|
|
18
|
+
- Given a pipeline run is initiated,
|
|
19
|
+
- When the pipeline reaches Step 5 of SKILL.md,
|
|
20
|
+
- Then a UUID v4 `runId` is generated and stored in the working context for use throughout that run.
|
|
21
|
+
|
|
22
|
+
**AC-2 — runId is stored in the history entry**
|
|
23
|
+
- Given a pipeline run completes (successfully or with failure),
|
|
24
|
+
- When the history entry is written to `.claude/pipeline-history.json`,
|
|
25
|
+
- Then the entry includes the `runId` generated at pipeline start.
|
|
26
|
+
|
|
27
|
+
**AC-3 — featureId written into FEATURE_SPEC.md frontmatter on first creation**
|
|
28
|
+
- Given Alex creates a new FEATURE_SPEC.md that has no YAML frontmatter `featureId`,
|
|
29
|
+
- When Alex writes the file,
|
|
30
|
+
- Then a UUID v4 `featureId` is added to the YAML frontmatter of FEATURE_SPEC.md.
|
|
31
|
+
|
|
32
|
+
**AC-4 — featureId is never regenerated if already present**
|
|
33
|
+
- Given FEATURE_SPEC.md already contains a `featureId` in its YAML frontmatter,
|
|
34
|
+
- When Alex processes the feature spec (on a re-run or update),
|
|
35
|
+
- Then the existing `featureId` value is preserved unchanged.
|
|
36
|
+
|
|
37
|
+
**AC-5 — runId is unique per run even for the same feature**
|
|
38
|
+
- Given the same feature is run twice (e.g., after a failure and retry),
|
|
39
|
+
- When both pipeline runs complete,
|
|
40
|
+
- Then each run's history entry contains a different `runId`, while both FEATURE_SPEC.md entries retain the same `featureId`.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### Out of scope
|
|
45
|
+
- Exposing `runId` in any user-visible CLI output
|
|
46
|
+
- User-configurable or manually set `runId` or `featureId`
|
|
47
|
+
- `featureId` for artifacts other than FEATURE_SPEC.md
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Story — Init Command Integration (.env Template and .gitignore)
|
|
2
|
+
|
|
3
|
+
### User story
|
|
4
|
+
As a developer initialising a new murmur8 project, I want the `init` command to create a commented-out telemetry template in `.env` and ensure `.env` is protected by `.gitignore` so that I know telemetry exists and cannot accidentally commit credentials.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
### Context / scope
|
|
9
|
+
- `murmur8 init` runs `src/init.js` which copies framework files to the target project
|
|
10
|
+
- `.env` file is at the project root; it is optional — telemetry is inactive if absent
|
|
11
|
+
- `.gitignore` must include `.env` to prevent accidental credential commits
|
|
12
|
+
- If `.env` already exists, the template block is appended (not overwritten) only if not already present
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
### Acceptance criteria
|
|
17
|
+
|
|
18
|
+
**AC-1 — `.env` created with commented telemetry template when absent**
|
|
19
|
+
- Given the target project has no `.env` file,
|
|
20
|
+
- When `murmur8 init` is run,
|
|
21
|
+
- Then a `.env` file is created at the project root containing a commented-out block documenting `MURMUR8_TELEMETRY_URL` and `MURMUR8_TELEMETRY_KEY`.
|
|
22
|
+
|
|
23
|
+
**AC-2 — `.env` template appended when file already exists**
|
|
24
|
+
- Given the target project already has a `.env` file that does not contain the murmur8 telemetry template block,
|
|
25
|
+
- When `murmur8 init` is run,
|
|
26
|
+
- Then the commented-out telemetry template block is appended to the existing `.env` file without modifying existing content.
|
|
27
|
+
|
|
28
|
+
**AC-3 — `.env` not modified if template already present**
|
|
29
|
+
- Given the target project has a `.env` file that already contains the murmur8 telemetry template block,
|
|
30
|
+
- When `murmur8 init` is run,
|
|
31
|
+
- Then the `.env` file is not modified (no duplicate block appended).
|
|
32
|
+
|
|
33
|
+
**AC-4 — `.env` added to `.gitignore` when absent from it**
|
|
34
|
+
- Given the target project has a `.gitignore` file that does not contain `.env`,
|
|
35
|
+
- When `murmur8 init` is run,
|
|
36
|
+
- Then `.env` is appended to `.gitignore`.
|
|
37
|
+
|
|
38
|
+
**AC-5 — `.gitignore` not modified if `.env` already listed**
|
|
39
|
+
- Given the target project's `.gitignore` already contains `.env`,
|
|
40
|
+
- When `murmur8 init` is run,
|
|
41
|
+
- Then `.gitignore` is not modified (no duplicate entry added).
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### Out of scope
|
|
46
|
+
- Emitting a user-visible notice that `.env` was created (silent operation, matching overall telemetry design)
|
|
47
|
+
- Validating that existing `.env` values are correct
|
|
48
|
+
- Creating or modifying `.gitignore` if neither `.gitignore` nor `.env` exist (no `.gitignore` needed without a `.env`)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Story — Telemetry Payload Construction and Send
|
|
2
|
+
|
|
3
|
+
### User story
|
|
4
|
+
As a platform administrator, I want a structured JSON event payload to be POST-ed to my telemetry endpoint at the end of each pipeline run so that I have complete execution data including identifiers, timings, stage statuses, and compressed artifacts.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
### Context / scope
|
|
9
|
+
- Send occurs at pipeline end (Step 12 of SKILL.md) via `src/telemetry.js`
|
|
10
|
+
- Payload is a JSON body sent via HTTP POST using Node.js built-in `https`/`http` modules — no external HTTP library
|
|
11
|
+
- Artifacts (FEATURE_SPEC.md and `story-*.md` files) are gzip-compressed with `zlib` and base64-encoded before inclusion
|
|
12
|
+
- Send is fire-and-forget: the pipeline does not wait for confirmation beyond a reasonable timeout
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
### Acceptance criteria
|
|
17
|
+
|
|
18
|
+
**AC-1 — Payload contains required identity and run fields**
|
|
19
|
+
- Given telemetry is active and a pipeline run completes,
|
|
20
|
+
- When the payload is constructed,
|
|
21
|
+
- Then it includes: `runId`, `featureId`, `featureSlug`, `status` (success/failure), `startedAt`, `completedAt`, and `durationMs`.
|
|
22
|
+
|
|
23
|
+
**AC-2 — Payload includes per-stage timing and status**
|
|
24
|
+
- Given a pipeline run completes with one or more stages executed,
|
|
25
|
+
- When the payload is constructed,
|
|
26
|
+
- Then the `stages` block includes each executed stage with its `name`, `status`, and `durationMs`.
|
|
27
|
+
|
|
28
|
+
**AC-3 — Artifacts are gzip-compressed and base64-encoded**
|
|
29
|
+
- Given FEATURE_SPEC.md and any `story-*.md` files exist for the feature,
|
|
30
|
+
- When the payload is constructed,
|
|
31
|
+
- Then the `artifacts` block contains each file keyed by filename, with its content gzip-compressed (zlib) and base64-encoded.
|
|
32
|
+
|
|
33
|
+
**AC-4 — Feedback block omitted when `--no-feedback` is used**
|
|
34
|
+
- Given the pipeline is run with the `--no-feedback` flag,
|
|
35
|
+
- When the payload is constructed,
|
|
36
|
+
- Then the `feedback` block is absent from the payload.
|
|
37
|
+
|
|
38
|
+
**AC-5 — Successful send produces no user-visible output**
|
|
39
|
+
- Given telemetry is active and the HTTP POST returns a 2xx response,
|
|
40
|
+
- When the send completes,
|
|
41
|
+
- Then no message, log line, or output is written to stdout or stderr.
|
|
42
|
+
|
|
43
|
+
**AC-6 — Payload sent with correct Content-Type header**
|
|
44
|
+
- Given telemetry is active,
|
|
45
|
+
- When the POST request is made,
|
|
46
|
+
- Then the request includes the header `Content-Type: application/json`.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### Out of scope
|
|
51
|
+
- Real-time or streaming telemetry (end-of-run POST only)
|
|
52
|
+
- Telemetry for non-pipeline commands (`history`, `queue`, `validate`, etc.)
|
|
53
|
+
- Encryption of payload beyond HTTPS transport
|
|
54
|
+
- Validating or enforcing HTTPS on the configured URL (though a warning may be emitted for `http://`)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Story — Telemetry Activation & Configuration
|
|
2
|
+
|
|
3
|
+
### User story
|
|
4
|
+
As a platform administrator, I want to activate pipeline telemetry by setting a URL in `.env` so that murmur8 sends structured execution data to my observability endpoint without requiring code changes.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
### Context / scope
|
|
9
|
+
- Configuration via `.env` file at project root and/or real environment variables
|
|
10
|
+
- Activation is determined solely by the presence of `MURMUR8_TELEMETRY_URL`
|
|
11
|
+
- Absence of the URL means telemetry is fully inactive — no output, no side effects
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### Acceptance criteria
|
|
16
|
+
|
|
17
|
+
**AC-1 — Telemetry inactive when URL is absent**
|
|
18
|
+
- Given `MURMUR8_TELEMETRY_URL` is not set in `.env` or in the process environment,
|
|
19
|
+
- When a pipeline run completes,
|
|
20
|
+
- Then no HTTP request is made, no queue file is created, and no output is produced.
|
|
21
|
+
|
|
22
|
+
**AC-2 — Telemetry active when URL is set in `.env`**
|
|
23
|
+
- Given `MURMUR8_TELEMETRY_URL` is set to a valid URL in `.env`,
|
|
24
|
+
- When a pipeline run completes,
|
|
25
|
+
- Then `src/telemetry.js` POSTs the payload to that URL.
|
|
26
|
+
|
|
27
|
+
**AC-3 — Real environment variable takes precedence over `.env`**
|
|
28
|
+
- Given `MURMUR8_TELEMETRY_URL` is set to `https://env-value.example.com` in the process environment,
|
|
29
|
+
- And `.env` contains `MURMUR8_TELEMETRY_URL=https://dotenv-value.example.com`,
|
|
30
|
+
- When telemetry resolves the endpoint,
|
|
31
|
+
- Then the request is sent to `https://env-value.example.com` (the process env value).
|
|
32
|
+
|
|
33
|
+
**AC-4 — API key sent as Authorization header when set**
|
|
34
|
+
- Given `MURMUR8_TELEMETRY_KEY` is set (in `.env` or environment),
|
|
35
|
+
- When the telemetry POST is made,
|
|
36
|
+
- Then the request includes the header `Authorization: Bearer <key>`.
|
|
37
|
+
|
|
38
|
+
**AC-5 — Authorization header omitted when key is absent**
|
|
39
|
+
- Given `MURMUR8_TELEMETRY_KEY` is not set,
|
|
40
|
+
- When the telemetry POST is made,
|
|
41
|
+
- Then no `Authorization` header is included in the request.
|
|
42
|
+
|
|
43
|
+
**AC-6 — Same precedence rule applies to API key**
|
|
44
|
+
- Given `MURMUR8_TELEMETRY_KEY` is set in both the process environment and `.env` with different values,
|
|
45
|
+
- When telemetry resolves the key,
|
|
46
|
+
- Then the process environment value is used.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### Out of scope
|
|
51
|
+
- Opt-out flag (absence of URL is the opt-out mechanism)
|
|
52
|
+
- Validating or enforcing HTTPS on the configured URL
|
|
53
|
+
- Telemetry for non-pipeline CLI commands (`history`, `queue`, etc.)
|
|
54
|
+
- Any third-party `.env` parsing library (manual parsing only)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Story — telemetry-config CLI Command
|
|
2
|
+
|
|
3
|
+
### User story
|
|
4
|
+
As a platform administrator, I want to run `murmur8 telemetry-config` to view the current telemetry configuration so that I can confirm the endpoint is configured correctly and check the failed-send queue depth — without exposing the API key in plaintext.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
### Context / scope
|
|
9
|
+
- New CLI command: `murmur8 telemetry-config` (registered in `bin/cli.js`, handled by `src/commands/telemetry-config.js`)
|
|
10
|
+
- Reads configuration from `.env` and process environment (same precedence rules as telemetry module)
|
|
11
|
+
- Displays configured URL; masks the API key; shows failed queue depth
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### Acceptance criteria
|
|
16
|
+
|
|
17
|
+
**AC-1 — Command displays configured URL**
|
|
18
|
+
- Given `MURMUR8_TELEMETRY_URL` is set,
|
|
19
|
+
- When `murmur8 telemetry-config` is run,
|
|
20
|
+
- Then the output includes the full configured URL.
|
|
21
|
+
|
|
22
|
+
**AC-2 — API key is masked in output**
|
|
23
|
+
- Given `MURMUR8_TELEMETRY_KEY` is set to a non-empty value,
|
|
24
|
+
- When `murmur8 telemetry-config` is run,
|
|
25
|
+
- Then the output displays the key in masked form (e.g. `sk-****1234` showing only the last 4 characters) and never the full plaintext value.
|
|
26
|
+
|
|
27
|
+
**AC-3 — Key shown as "not set" when absent**
|
|
28
|
+
- Given `MURMUR8_TELEMETRY_KEY` is not configured,
|
|
29
|
+
- When `murmur8 telemetry-config` is run,
|
|
30
|
+
- Then the output indicates no key is configured (e.g. `not set`).
|
|
31
|
+
|
|
32
|
+
**AC-4 — Command shows telemetry as inactive when URL is absent**
|
|
33
|
+
- Given `MURMUR8_TELEMETRY_URL` is not set,
|
|
34
|
+
- When `murmur8 telemetry-config` is run,
|
|
35
|
+
- Then the output clearly indicates telemetry is inactive (e.g. `status: inactive`).
|
|
36
|
+
|
|
37
|
+
**AC-5 — Failed queue depth is displayed**
|
|
38
|
+
- Given `.claude/telemetry-failed.json` exists with N entries,
|
|
39
|
+
- When `murmur8 telemetry-config` is run,
|
|
40
|
+
- Then the output includes the number of queued failed sends (e.g. `failed queue: 3 entries`).
|
|
41
|
+
|
|
42
|
+
**AC-6 — Failed queue shown as zero when file is absent**
|
|
43
|
+
- Given `.claude/telemetry-failed.json` does not exist,
|
|
44
|
+
- When `murmur8 telemetry-config` is run,
|
|
45
|
+
- Then the output shows a failed queue depth of 0.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### Out of scope
|
|
50
|
+
- A `--test` flag to send a synthetic ping to the endpoint
|
|
51
|
+
- Editing or clearing configuration via this command (read-only display)
|
|
52
|
+
- Displaying the full contents of the failed send queue
|
package/README.md
CHANGED
|
@@ -148,6 +148,7 @@ This updates `.blueprint/agents/`, `.blueprint/templates/`, `.blueprint/ways_of_
|
|
|
148
148
|
| `npx murmur8 feedback-config set <key> <value>` | Modify feedback settings |
|
|
149
149
|
| `npx murmur8 murm-config` | View murmuration pipeline configuration |
|
|
150
150
|
| `npx murmur8 murm-config set <key> <value>` | Modify murmuration settings |
|
|
151
|
+
| `npx murmur8 telemetry-config` | View telemetry configuration and failed queue depth |
|
|
151
152
|
|
|
152
153
|
## Skill usage
|
|
153
154
|
|
|
@@ -298,6 +299,7 @@ murmur8 includes these built-in modules for observability and self-improvement:
|
|
|
298
299
|
| **stack** | Configurable tech stack detection and configuration |
|
|
299
300
|
| **cost** | Token usage tracking and cost estimation per stage |
|
|
300
301
|
| **diff-preview** | Pre-commit change review with user confirmation |
|
|
302
|
+
| **telemetry** | Opt-in audit layer — POSTs structured run data to a configured endpoint for corpus building and enterprise usage monitoring |
|
|
301
303
|
|
|
302
304
|
### How They Work Together
|
|
303
305
|
|
|
@@ -515,6 +517,58 @@ npx murmur8 cost-config reset
|
|
|
515
517
|
|
|
516
518
|
Cost data is stored in `pipeline-history.json` alongside timing and feedback data.
|
|
517
519
|
|
|
520
|
+
## Pipeline Telemetry
|
|
521
|
+
|
|
522
|
+
An opt-in audit and data collection layer. When a telemetry endpoint is configured, each completed pipeline run POSTs a structured event payload to that endpoint. If no endpoint is configured, the feature is completely silent — no output, no side effects.
|
|
523
|
+
|
|
524
|
+
### Activation
|
|
525
|
+
|
|
526
|
+
The `murmur8 init` command creates a `.env` file at your project root with a commented-out telemetry template:
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
# Remove # to enable
|
|
530
|
+
MURMUR8_TELEMETRY_URL=https://your-ingest-endpoint.com/events
|
|
531
|
+
MURMUR8_TELEMETRY_KEY=your-api-key # optional — sent as Authorization: Bearer
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Real environment variables take precedence over `.env`, making it straightforward to configure in CI/CD without storing credentials in files. `.env` is automatically added to `.gitignore` during init.
|
|
535
|
+
|
|
536
|
+
### What gets sent
|
|
537
|
+
|
|
538
|
+
| Field | Description |
|
|
539
|
+
|-------|-------------|
|
|
540
|
+
| `runId` | UUID v4 unique to this pipeline execution |
|
|
541
|
+
| `featureId` | UUID stable across retries — written into FEATURE_SPEC.md frontmatter once by Alex |
|
|
542
|
+
| `identity` | Git user name/email, repo remote URL, org ID, murmur8 version |
|
|
543
|
+
| `run` | Slug, status, start/end timestamps, per-stage timings and statuses, feedback ratings |
|
|
544
|
+
| `artifacts` | Feature spec and story files, gzip + base64 encoded |
|
|
545
|
+
|
|
546
|
+
`featureId` enables cross-run correlation: all retries of the same feature share one `featureId` while each execution gets a unique `runId`. This lets you query "all runs ever attempted for this feature" and trace evolution over time.
|
|
547
|
+
|
|
548
|
+
### Reliability
|
|
549
|
+
|
|
550
|
+
Sends are non-blocking and never interrupt the pipeline. On failure (network error, timeout, non-2xx), the payload is silently queued to `.claude/telemetry-failed.json` and retried at the start of the next run.
|
|
551
|
+
|
|
552
|
+
### Viewing configuration
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
npx murmur8 telemetry-config
|
|
556
|
+
|
|
557
|
+
# Example output:
|
|
558
|
+
# status: active
|
|
559
|
+
# url: https://your-endpoint.com/events
|
|
560
|
+
# api key: ****1234
|
|
561
|
+
# failed queue: 0 entries
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Enterprise use
|
|
565
|
+
|
|
566
|
+
murmur8 telemetry is designed for self-hosted enterprise endpoints — there is no central collection service. Each organisation configures its own ingest URL. This enables:
|
|
567
|
+
|
|
568
|
+
- **Usage monitoring** — who ran what pipeline, on which repo, when
|
|
569
|
+
- **Audit trail** — `runId` + `commitHash` + `gitUser` provides an immutable trace of AI-assisted code changes
|
|
570
|
+
- **Corpus building** — aggregate feature specs and stories across teams to analyse and improve pipeline quality
|
|
571
|
+
|
|
518
572
|
## Murmuration
|
|
519
573
|
|
|
520
574
|
Run multiple feature pipelines simultaneously using git worktrees for isolation. Each feature gets its own worktree and branch, allowing true parallel development without conflicts.
|
package/package.json
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* telemetry-config command - View telemetry configuration and queue status
|
|
3
|
+
*/
|
|
4
|
+
const { loadConfig, formatTelemetryConfig } = require('../telemetry');
|
|
5
|
+
|
|
6
|
+
const description = 'View telemetry configuration and failed-send queue status';
|
|
7
|
+
|
|
8
|
+
const QUEUE_PATH = '.claude/telemetry-failed.json';
|
|
9
|
+
|
|
10
|
+
async function run(_args) {
|
|
11
|
+
const config = loadConfig('.env');
|
|
12
|
+
const output = formatTelemetryConfig(config, QUEUE_PATH);
|
|
13
|
+
console.log(output);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { run, description };
|
package/src/index.js
CHANGED
|
@@ -94,6 +94,9 @@ const {
|
|
|
94
94
|
markWorktreeAborted,
|
|
95
95
|
getPromptText
|
|
96
96
|
} = require('./diff-preview');
|
|
97
|
+
const { loadConfig, generateRunId, ensureFeatureId, buildPayload, compressArtifact,
|
|
98
|
+
enqueueFailure, retryQueue, ensureDotenv, ensureGitignore, formatTelemetryConfig
|
|
99
|
+
} = require('./telemetry');
|
|
97
100
|
const tools = require('./tools');
|
|
98
101
|
const theme = require('./theme');
|
|
99
102
|
|
|
@@ -159,6 +162,17 @@ module.exports = {
|
|
|
159
162
|
setStackConfigValue,
|
|
160
163
|
detectStackConfig,
|
|
161
164
|
displayStackConfig,
|
|
165
|
+
// Telemetry module exports
|
|
166
|
+
loadConfig,
|
|
167
|
+
generateRunId,
|
|
168
|
+
ensureFeatureId,
|
|
169
|
+
buildPayload,
|
|
170
|
+
compressArtifact,
|
|
171
|
+
enqueueFailure,
|
|
172
|
+
retryQueue,
|
|
173
|
+
ensureDotenv,
|
|
174
|
+
ensureGitignore,
|
|
175
|
+
formatTelemetryConfig,
|
|
162
176
|
// Tools module (model native features)
|
|
163
177
|
tools,
|
|
164
178
|
// Theme module (murmuration visual theming)
|
package/src/init.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
|
|
4
4
|
const { detectStackConfig, writeStackConfig, CONFIG_FILE: STACK_CONFIG_FILE } = require('./stack');
|
|
5
5
|
const { prompt } = require('./utils');
|
|
6
|
+
const { ensureDotenv, ensureGitignore } = require('./telemetry');
|
|
6
7
|
|
|
7
8
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
8
9
|
const TARGET_DIR = process.cwd();
|
|
@@ -176,6 +177,10 @@ async function init() {
|
|
|
176
177
|
console.log('Stack config already exists, skipping detection');
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
// Ensure .env template and .gitignore entry for telemetry
|
|
181
|
+
ensureDotenv(TARGET_DIR);
|
|
182
|
+
ensureGitignore(TARGET_DIR);
|
|
183
|
+
|
|
179
184
|
console.log(`
|
|
180
185
|
murmur8 initialized successfully!
|
|
181
186
|
|
package/src/telemetry.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const zlib = require('zlib');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// loadConfig — parse .env line by line; real process.env takes precedence
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
function loadConfig(dotenvPath) {
|
|
12
|
+
const fileVars = {};
|
|
13
|
+
try {
|
|
14
|
+
const lines = fs.readFileSync(dotenvPath, 'utf8').split('\n');
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
const trimmed = line.trim();
|
|
17
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
18
|
+
const eqIdx = trimmed.indexOf('=');
|
|
19
|
+
if (eqIdx === -1) continue;
|
|
20
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
21
|
+
const val = trimmed.slice(eqIdx + 1).trim();
|
|
22
|
+
fileVars[key] = val;
|
|
23
|
+
}
|
|
24
|
+
} catch (_) { /* file absent or unreadable — ignore */ }
|
|
25
|
+
|
|
26
|
+
const rawUrl = process.env.MURMUR8_TELEMETRY_URL ?? fileVars.MURMUR8_TELEMETRY_URL ?? '';
|
|
27
|
+
const rawKey = process.env.MURMUR8_TELEMETRY_KEY ?? fileVars.MURMUR8_TELEMETRY_KEY ?? '';
|
|
28
|
+
|
|
29
|
+
let url = null;
|
|
30
|
+
try { url = rawUrl ? (new URL(rawUrl), rawUrl) : null; } catch (_) { url = null; }
|
|
31
|
+
|
|
32
|
+
return { url, key: rawKey || null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// generateRunId — crypto UUID v4
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
function generateRunId() {
|
|
39
|
+
return crypto.randomUUID();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// ensureFeatureId — reads/writes featureId into YAML frontmatter
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
function ensureFeatureId(specPath) {
|
|
46
|
+
const content = fs.readFileSync(specPath, 'utf8');
|
|
47
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
|
|
48
|
+
|
|
49
|
+
if (fmMatch) {
|
|
50
|
+
const fmBody = fmMatch[1];
|
|
51
|
+
const idMatch = fmBody.match(/^featureId:\s*(.+)$/m);
|
|
52
|
+
if (idMatch) return idMatch[1].trim();
|
|
53
|
+
|
|
54
|
+
// Frontmatter exists but no featureId — insert it
|
|
55
|
+
const newId = generateRunId();
|
|
56
|
+
const newFm = `---\nfeatureId: ${newId}\n${fmBody}\n---\n`;
|
|
57
|
+
fs.writeFileSync(specPath, newFm + content.slice(fmMatch[0].length));
|
|
58
|
+
return newId;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// No frontmatter — prepend
|
|
62
|
+
const newId = generateRunId();
|
|
63
|
+
fs.writeFileSync(specPath, `---\nfeatureId: ${newId}\n---\n${content}`);
|
|
64
|
+
return newId;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// buildPayload — assembles telemetry payload; omits feedback when empty
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
function buildPayload(runData) {
|
|
71
|
+
const { runId, featureId, slug, status, startedAt, completedAt,
|
|
72
|
+
totalDurationMs, stages, artifacts, feedback } = runData;
|
|
73
|
+
|
|
74
|
+
const run = { featureId, slug, status, startedAt, completedAt, totalDurationMs };
|
|
75
|
+
if (stages) run.stages = stages;
|
|
76
|
+
if (feedback && typeof feedback === 'object' && Object.keys(feedback).length > 0) {
|
|
77
|
+
run.feedback = feedback;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const payload = { runId, run };
|
|
81
|
+
if (artifacts) payload.artifacts = artifacts;
|
|
82
|
+
return payload;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// compressArtifact — gzip + base64 (synchronous)
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
function compressArtifact(content) {
|
|
89
|
+
return zlib.gzipSync(Buffer.from(content, 'utf8')).toString('base64');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// enqueueFailure — appends to queue; caps at 50; creates file if absent
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
function enqueueFailure(payload, queuePath) {
|
|
96
|
+
let queue = [];
|
|
97
|
+
try {
|
|
98
|
+
queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
|
|
99
|
+
if (!Array.isArray(queue)) queue = [];
|
|
100
|
+
} catch (_) { queue = []; }
|
|
101
|
+
|
|
102
|
+
queue.push(payload);
|
|
103
|
+
if (queue.length > 50) queue = queue.slice(queue.length - 50);
|
|
104
|
+
fs.writeFileSync(queuePath, JSON.stringify(queue, null, 2));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// retryQueue — calls sendFn per entry; removes successes; writes remaining
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
function retryQueue(queuePath, sendFn) {
|
|
111
|
+
let queue;
|
|
112
|
+
try {
|
|
113
|
+
queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
|
|
114
|
+
if (!Array.isArray(queue)) return;
|
|
115
|
+
} catch (_) { return; }
|
|
116
|
+
|
|
117
|
+
const remaining = [];
|
|
118
|
+
for (const entry of queue) {
|
|
119
|
+
const ok = sendFn(entry);
|
|
120
|
+
if (!ok) remaining.push(entry);
|
|
121
|
+
}
|
|
122
|
+
fs.writeFileSync(queuePath, JSON.stringify(remaining, null, 2));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// ensureDotenv — creates/appends .env with commented telemetry template
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
const DOTENV_MARKER = 'MURMUR8_TELEMETRY_URL';
|
|
129
|
+
const DOTENV_TEMPLATE = `
|
|
130
|
+
# murmur8 Telemetry — remove comments and set values to enable
|
|
131
|
+
# MURMUR8_TELEMETRY_URL=https://your-endpoint.example.com/events
|
|
132
|
+
# MURMUR8_TELEMETRY_KEY=your-api-key
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
function ensureDotenv(targetDir) {
|
|
136
|
+
const envPath = path.join(targetDir, '.env');
|
|
137
|
+
try {
|
|
138
|
+
const existing = fs.readFileSync(envPath, 'utf8');
|
|
139
|
+
if (existing.includes(DOTENV_MARKER)) return;
|
|
140
|
+
fs.writeFileSync(envPath, existing + DOTENV_TEMPLATE);
|
|
141
|
+
} catch (_) {
|
|
142
|
+
fs.writeFileSync(envPath, DOTENV_TEMPLATE.trimStart());
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// ensureGitignore — adds .env to .gitignore; creates .gitignore if absent
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
function ensureGitignore(targetDir) {
|
|
150
|
+
const giPath = path.join(targetDir, '.gitignore');
|
|
151
|
+
try {
|
|
152
|
+
const existing = fs.readFileSync(giPath, 'utf8');
|
|
153
|
+
const lines = existing.split('\n').map(l => l.trim());
|
|
154
|
+
if (lines.includes('.env')) return;
|
|
155
|
+
fs.writeFileSync(giPath, existing + (existing.endsWith('\n') ? '' : '\n') + '.env\n');
|
|
156
|
+
} catch (_) {
|
|
157
|
+
fs.writeFileSync(giPath, '.env\n');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// formatTelemetryConfig — returns formatted config string
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
function formatTelemetryConfig(config, queuePath) {
|
|
165
|
+
const { url, key } = config;
|
|
166
|
+
const status = url ? 'active' : 'inactive';
|
|
167
|
+
|
|
168
|
+
let maskedKey = 'not set';
|
|
169
|
+
if (key) {
|
|
170
|
+
maskedKey = key.length > 4 ? `****${key.slice(-4)}` : '****';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let queueDepth = 0;
|
|
174
|
+
try {
|
|
175
|
+
const q = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
|
|
176
|
+
if (Array.isArray(q)) queueDepth = q.length;
|
|
177
|
+
} catch (_) { /* file absent or corrupt */ }
|
|
178
|
+
|
|
179
|
+
return [
|
|
180
|
+
`Telemetry status : ${status}`,
|
|
181
|
+
`URL : ${url || 'not set'}`,
|
|
182
|
+
`API key : ${maskedKey}`,
|
|
183
|
+
`Failed queue : ${queueDepth} entries`,
|
|
184
|
+
].join('\n');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = {
|
|
188
|
+
loadConfig,
|
|
189
|
+
generateRunId,
|
|
190
|
+
ensureFeatureId,
|
|
191
|
+
buildPayload,
|
|
192
|
+
compressArtifact,
|
|
193
|
+
enqueueFailure,
|
|
194
|
+
retryQueue,
|
|
195
|
+
ensureDotenv,
|
|
196
|
+
ensureGitignore,
|
|
197
|
+
formatTelemetryConfig,
|
|
198
|
+
};
|