murmur8 4.5.0 → 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.
Files changed (28) hide show
  1. package/.blueprint/features/feature_feedback-test/FEATURE_SPEC.md +229 -0
  2. package/.blueprint/features/feature_feedback-test/IMPLEMENTATION_PLAN.md +25 -0
  3. package/.blueprint/features/feature_feedback-test/handoff-alex.md +20 -0
  4. package/.blueprint/features/feature_feedback-test/handoff-cass.md +21 -0
  5. package/.blueprint/features/feature_feedback-test/handoff-nigel.md +20 -0
  6. package/.blueprint/features/feature_feedback-test/story-config-management.md +103 -0
  7. package/.blueprint/features/feature_feedback-test/story-parse-pipeline.md +65 -0
  8. package/.blueprint/features/feature_feedback-test/story-validation-normalisation.md +99 -0
  9. package/.blueprint/features/feature_pipeline-telemetry/FEATURE_SPEC.md +297 -0
  10. package/.blueprint/features/feature_pipeline-telemetry/IMPLEMENTATION_PLAN.md +34 -0
  11. package/.blueprint/features/feature_pipeline-telemetry/handoff-alex.md +21 -0
  12. package/.blueprint/features/feature_pipeline-telemetry/handoff-cass.md +25 -0
  13. package/.blueprint/features/feature_pipeline-telemetry/handoff-nigel.md +20 -0
  14. package/.blueprint/features/feature_pipeline-telemetry/story-failed-queue-retry.md +53 -0
  15. package/.blueprint/features/feature_pipeline-telemetry/story-identifiers.md +47 -0
  16. package/.blueprint/features/feature_pipeline-telemetry/story-init-integration.md +48 -0
  17. package/.blueprint/features/feature_pipeline-telemetry/story-payload-send.md +54 -0
  18. package/.blueprint/features/feature_pipeline-telemetry/story-telemetry-activation.md +54 -0
  19. package/.blueprint/features/feature_pipeline-telemetry/story-telemetry-config-command.md +52 -0
  20. package/README.md +54 -0
  21. package/SKILL.md +35 -24
  22. package/package.json +1 -1
  23. package/src/commands/history.js +41 -2
  24. package/src/commands/telemetry-config.js +16 -0
  25. package/src/history.js +31 -0
  26. package/src/index.js +16 -1
  27. package/src/init.js +5 -0
  28. package/src/telemetry.js +198 -0
@@ -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/SKILL.md CHANGED
@@ -138,7 +138,7 @@ If no history exists, skip this step silently.
138
138
  ### Step 5: Initialize
139
139
  Create/read `{QUEUE}`. Ensure dirs exist: `mkdir -p {FEAT_DIR} {TEST_DIR}`
140
140
 
141
- Unless `--no-history`, start a history entry (slug, startedAt, stages, feedback).
141
+ Unless `--no-history`, note the pipeline start time (ISO 8601 UTC) in your working context as `PIPELINE_START`.
142
142
 
143
143
  ---
144
144
 
@@ -146,7 +146,7 @@ Unless `--no-history`, start a history entry (slug, startedAt, stages, feedback)
146
146
 
147
147
  **Announce:** `} Alex — creating feature spec`
148
148
 
149
- **History:** Record `stages.alex.startedAt` before spawning.
149
+ **History:** Note `ALEX_START` (ISO 8601 UTC) before spawning.
150
150
 
151
151
  **Runtime prompt:** `.blueprint/prompts/alex-runtime.md`
152
152
 
@@ -204,7 +204,7 @@ Brief summary (5 bullets max): intent, key behaviours, scope, story themes, tens
204
204
 
205
205
  **On completion:**
206
206
  1. Verify `{FEAT_SPEC}` and `{FEAT_DIR}/handoff-alex.md` exist
207
- 2. **Record history:** `stages.alex = { completedAt, durationMs, status: "success" }`
207
+ 2. Note `ALEX_END` and compute `ALEX_DURATION_MS`
208
208
  3. Update queue: move feature to `cassQueue`
209
209
  4. If `--pause-after=alex`: Show output path, ask user to continue
210
210
 
@@ -247,7 +247,7 @@ FEEDBACK: {"rating":N,"issues":["..."],"rec":"proceed|pause|revise"}
247
247
 
248
248
  **Announce:** ` } Cass — writing user stories`
249
249
 
250
- **History:** Record `stages.cass.startedAt` before spawning.
250
+ **History:** Note `CASS_START` (ISO 8601 UTC) before spawning.
251
251
 
252
252
  **Runtime prompt:** `.blueprint/prompts/cass-runtime.md`
253
253
 
@@ -311,7 +311,7 @@ Brief summary: story count, filenames, behaviours covered (5 bullets max)
311
311
  **On completion:**
312
312
  1. Verify at least one `story-*.md` exists in `{FEAT_DIR}`
313
313
  2. Verify `{FEAT_DIR}/handoff-cass.md` exists
314
- 2. **Record history:** `stages.cass = { completedAt, durationMs, status: "success" }`
314
+ 2. Note `CASS_END` and compute `CASS_DURATION_MS`
315
315
  3. Update queue: move feature to `nigelQueue`
316
316
  4. If `--pause-after=cass`: Show story paths, ask user to continue
317
317
 
@@ -349,7 +349,7 @@ FEEDBACK: {"rating":N,"issues":["..."],"rec":"proceed|pause|revise"}
349
349
 
350
350
  **Announce:** ` } Nigel — building test spec`
351
351
 
352
- **History:** Record `stages.nigelSpec.startedAt` before spawning.
352
+ **History:** Note `NIGEL_SPEC_START` (ISO 8601 UTC) before spawning.
353
353
 
354
354
  **Runtime prompt:** `.blueprint/prompts/nigel-runtime.md`
355
355
 
@@ -412,7 +412,7 @@ Brief summary: test case count planned, AC coverage %, assumptions (5 bullets ma
412
412
 
413
413
  **On completion:**
414
414
  1. Verify `{TEST_SPEC}` and `{FEAT_DIR}/handoff-nigel.md` exist
415
- 2. **Record history:** `stages.nigelSpec = { completedAt, durationMs, status: "success" }`
415
+ 2. Note `NIGEL_SPEC_END` and compute `NIGEL_SPEC_DURATION_MS`
416
416
 
417
417
  **On failure:** See [Error Handling with Retry](#error-handling-with-smart-retry)
418
418
 
@@ -422,7 +422,7 @@ Brief summary: test case count planned, AC coverage %, assumptions (5 bullets ma
422
422
 
423
423
  **Announce:** ` } Nigel — writing executable tests`
424
424
 
425
- **History:** Record `stages.nigelTests.startedAt` before spawning.
425
+ **History:** Note `NIGEL_TESTS_START` (ISO 8601 UTC) before spawning.
426
426
 
427
427
  Use the Task tool with `subagent_type="general-purpose"`:
428
428
 
@@ -460,7 +460,7 @@ Brief summary: test count, file(s) written, any tests deferred
460
460
 
461
461
  **On completion:**
462
462
  1. Verify `{TEST_FILE}` exists
463
- 2. **Record history:** `stages.nigelTests = { completedAt, durationMs, status: "success" }`
463
+ 2. Note `NIGEL_TESTS_END` and compute `NIGEL_TESTS_DURATION_MS`
464
464
  3. Update queue: move feature to `codeyQueue`
465
465
  4. If `--pause-after=nigel`: Show test paths, ask user to continue
466
466
 
@@ -499,7 +499,7 @@ FEEDBACK: {"rating":N,"issues":["..."],"rec":"proceed|pause|revise"}
499
499
 
500
500
  **Announce:** ` } Codey — drafting implementation plan`
501
501
 
502
- **History:** Record `stages.codeyPlan.startedAt` before spawning.
502
+ **History:** Note `CODEY_PLAN_START` (ISO 8601 UTC) before spawning.
503
503
 
504
504
  **Runtime prompt:** `.blueprint/prompts/codey-plan-runtime.md`
505
505
 
@@ -556,7 +556,7 @@ Brief summary: files planned, step count, identified risks
556
556
 
557
557
  **On completion:**
558
558
  1. Verify `{PLAN}` exists
559
- 2. **Record history:** `stages.codeyPlan = { completedAt, durationMs, status: "success" }`
559
+ 2. Note `CODEY_PLAN_END` and compute `CODEY_PLAN_DURATION_MS`
560
560
  3. If `--pause-after=codey-plan`: Show plan path, ask user to continue
561
561
 
562
562
  **On failure:** See [Error Handling with Retry](#error-handling-with-smart-retry)
@@ -567,7 +567,7 @@ Brief summary: files planned, step count, identified risks
567
567
 
568
568
  **Announce:** ` } Codey — implementing feature`
569
569
 
570
- **History:** Record `stages.codeyImplement.startedAt` before spawning.
570
+ **History:** Note `CODEY_IMPL_START` (ISO 8601 UTC) before spawning.
571
571
 
572
572
  **Runtime prompt:** `.blueprint/prompts/codey-implement-runtime.md`
573
573
 
@@ -637,13 +637,13 @@ for each step in IMPLEMENTATION_PLAN.steps:
637
637
 
638
638
  **On all steps complete:**
639
639
  1. Run full test suite: `node --test {TEST_FILE}`
640
- 2. **Record history:** `stages.codeyImplement = { completedAt, durationMs, status: "success", stepsCompleted: N }`
640
+ 2. Note `CODEY_IMPL_END`, compute `CODEY_IMPL_DURATION_MS`, and note `STEPS_COMPLETED`
641
641
  3. Update queue: move feature to `completed`
642
642
  4. Proceed to auto-commit (unless `--no-commit`)
643
643
 
644
644
  **On partial failure:**
645
645
  1. Record which steps completed and which failed
646
- 2. **Record history:** `stages.codeyImplement = { status: "partial", stepsCompleted: M, totalSteps: N, failedAt: step }`
646
+ 2. Note partial completion: `STEPS_COMPLETED=M`, `TOTAL_STEPS=N`, `FAILED_AT_STEP=step`
647
647
  3. Report to user with option to continue manually
648
648
 
649
649
  **On failure:** See [Error Handling with Retry](#error-handling-with-smart-retry)
@@ -694,17 +694,28 @@ After commit, remove the slug's row from `{BACKLOG}` (if it exists). Stage with
694
694
 
695
695
  **Modules:** `src/history.js`, `src/cost.js`
696
696
 
697
- Unless `--no-history` flag is set, finalize the history entry:
697
+ Unless `--no-history` flag is set, build the history entry JSON from the timestamps noted during the run and write it via the CLI:
698
698
 
699
- ```javascript
700
- historyEntry.status = "success";
701
- historyEntry.completedAt = new Date().toISOString();
702
- historyEntry.totalDurationMs = completedAt - startedAt;
703
- historyEntry.commitHash = "{hash}";
704
- historyEntry.totalTokens = { input: N, output: M };
705
- historyEntry.totalCost = X.XXX;
706
- // Save to .claude/pipeline-history.json
707
- ```
699
+ ```bash
700
+ node bin/cli.js history record '{
701
+ "slug": "{slug}",
702
+ "status": "success",
703
+ "startedAt": "<PIPELINE_START>",
704
+ "completedAt": "<now ISO 8601>",
705
+ "totalDurationMs": <elapsed ms>,
706
+ "commitHash": "<hash or null>",
707
+ "stages": {
708
+ "alex": { "startedAt": "<ALEX_START>", "completedAt": "<ALEX_END>", "durationMs": <ALEX_DURATION_MS>, "status": "success" },
709
+ "cass": { "startedAt": "<CASS_START>", "completedAt": "<CASS_END>", "durationMs": <CASS_DURATION_MS>, "status": "success" },
710
+ "nigel-spec": { "startedAt": "<NIGEL_SPEC_START>", "completedAt": "<NIGEL_SPEC_END>", "durationMs": <NIGEL_SPEC_DURATION_MS>, "status": "success" },
711
+ "nigel-tests": { "startedAt": "<NIGEL_TESTS_START>", "completedAt": "<NIGEL_TESTS_END>", "durationMs": <NIGEL_TESTS_DURATION_MS>, "status": "success" },
712
+ "codey-plan": { "startedAt": "<CODEY_PLAN_START>", "completedAt": "<CODEY_PLAN_END>", "durationMs": <CODEY_PLAN_DURATION_MS>, "status": "success" },
713
+ "codey-implement": { "startedAt": "<CODEY_IMPL_START>", "completedAt": "<CODEY_IMPL_END>", "durationMs": <CODEY_IMPL_DURATION_MS>, "status": "success", "stepsCompleted": <N> }
714
+ }
715
+ }'
716
+ ```
717
+
718
+ Omit stages that were skipped (e.g. cass when `--skip-stories` was used). Set `status` to `"failed"` and add `"failedStage": "<stage>"` on failure, or `"paused"` and `"pausedAfter": "<stage>"` on pause.
708
719
 
709
720
  **Display summary:** Stage status (✓/✗), test count, duration, commit hash, feedback ratings, cost breakdown per stage.
710
721
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "murmur8",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "description": "Multi-agent workflow framework for automated feature development",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * history command - View pipeline execution history
3
3
  */
4
- const { displayHistory, showStats, clearHistory, exportHistory } = require('../history');
4
+ const { displayHistory, showStats, clearHistory, exportHistory, recordHistory, updateStage } = require('../history');
5
5
  const { parseFlags } = require('./utils');
6
6
 
7
7
  const description = 'View pipeline execution history';
@@ -10,7 +10,46 @@ async function run(args) {
10
10
  const flags = parseFlags(args);
11
11
  const subArg = args[1];
12
12
 
13
- if (subArg === 'clear') {
13
+ if (subArg === 'record') {
14
+ const jsonArg = args[2];
15
+ if (!jsonArg) {
16
+ console.error('Usage: history record \'{"slug":"...","status":"...","startedAt":"...","completedAt":"...","totalDurationMs":N}\'');
17
+ process.exit(1);
18
+ }
19
+ let entry;
20
+ try {
21
+ entry = JSON.parse(jsonArg);
22
+ } catch (err) {
23
+ console.error(`Invalid JSON: ${err.message}`);
24
+ process.exit(1);
25
+ }
26
+ if (!entry.slug || !entry.status || !entry.startedAt || !entry.completedAt || entry.totalDurationMs === undefined) {
27
+ console.error('Entry must include: slug, status, startedAt, completedAt, totalDurationMs');
28
+ process.exit(1);
29
+ }
30
+ const ok = recordHistory(entry);
31
+ if (!ok) process.exit(1);
32
+ console.log(`Recorded history entry for "${entry.slug}" (${entry.status})`);
33
+ } else if (subArg === 'update-stage') {
34
+ // history update-stage <slug> <stage> '<json>'
35
+ const slug = args[2];
36
+ const stage = args[3];
37
+ const jsonArg = args[4];
38
+ if (!slug || !stage || !jsonArg) {
39
+ console.error('Usage: history update-stage <slug> <stage> \'{"durationMs":N,"status":"success"}\'');
40
+ process.exit(1);
41
+ }
42
+ let stageData;
43
+ try {
44
+ stageData = JSON.parse(jsonArg);
45
+ } catch (err) {
46
+ console.error(`Invalid JSON: ${err.message}`);
47
+ process.exit(1);
48
+ }
49
+ const ok = updateStage(slug, stage, stageData);
50
+ if (!ok) process.exit(1);
51
+ console.log(`Updated stage "${stage}" for "${slug}"`);
52
+ } else if (subArg === 'clear') {
14
53
  await clearHistory({ force: flags.force });
15
54
  } else if (subArg === 'export') {
16
55
  const exportOpts = {};
@@ -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/history.js CHANGED
@@ -90,6 +90,36 @@ function storeStageFeedback(slug, stage, feedback) {
90
90
  }
91
91
  }
92
92
 
93
+ /**
94
+ * Updates (merges) stage data into the most recent history entry for a slug.
95
+ * Used by the CLI skill to record per-stage timing after each pipeline step.
96
+ * @param {string} slug - Feature slug
97
+ * @param {string} stage - Stage name (alex, cass, nigel, codey-plan, codey-implement)
98
+ * @param {object} data - Stage fields to merge (startedAt, completedAt, durationMs, status, etc.)
99
+ * @returns {boolean} True if updated successfully
100
+ */
101
+ function updateStage(slug, stage, data) {
102
+ try {
103
+ const history = readHistoryFile();
104
+ if (history.error) {
105
+ console.warn('Warning: History file is corrupted, cannot update stage.');
106
+ return false;
107
+ }
108
+ const entry = history.findLast(e => e.slug === slug);
109
+ if (!entry) {
110
+ console.warn(`Warning: No history entry found for slug: ${slug}`);
111
+ return false;
112
+ }
113
+ if (!entry.stages) entry.stages = {};
114
+ entry.stages[stage] = { ...entry.stages[stage], ...data };
115
+ writeHistoryFile(history);
116
+ return true;
117
+ } catch (err) {
118
+ console.warn(`Warning: Failed to update stage: ${err.message}`);
119
+ return false;
120
+ }
121
+ }
122
+
93
123
  function formatDuration(ms) {
94
124
  const seconds = Math.floor(ms / 1000);
95
125
  const minutes = Math.floor(seconds / 60);
@@ -427,6 +457,7 @@ module.exports = {
427
457
  writeHistoryFile,
428
458
  recordHistory,
429
459
  storeStageFeedback,
460
+ updateStage,
430
461
  displayHistory,
431
462
  showStats,
432
463
  clearHistory,
package/src/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const { init } = require('./init');
2
2
  const { update } = require('./update');
3
3
  const { validate, formatOutput, checkNodeVersion } = require('./validate');
4
- const { recordHistory, displayHistory, showStats, clearHistory, storeStageFeedback } = require('./history');
4
+ const { recordHistory, displayHistory, showStats, clearHistory, storeStageFeedback, updateStage } = require('./history');
5
5
  const {
6
6
  readConfig,
7
7
  writeConfig,
@@ -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
 
@@ -108,6 +111,7 @@ module.exports = {
108
111
  showStats,
109
112
  clearHistory,
110
113
  storeStageFeedback,
114
+ updateStage,
111
115
  // Retry module exports
112
116
  readConfig,
113
117
  writeConfig,
@@ -158,6 +162,17 @@ module.exports = {
158
162
  setStackConfigValue,
159
163
  detectStackConfig,
160
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,
161
176
  // Tools module (model native features)
162
177
  tools,
163
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