murmur8 4.3.0 → 4.3.1

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.
@@ -0,0 +1,216 @@
1
+ # Feature Specification — Cost Tracking
2
+
3
+ ## 1. Feature Intent
4
+ **Why this feature exists.**
5
+
6
+ - Users have no visibility into token consumption or estimated costs when running the pipeline
7
+ - Without cost data, users cannot identify expensive stages or optimize prompt efficiency
8
+ - Cost awareness enables informed decisions about when to use `--skip-stories` or other optimizations
9
+ - Aligns with existing observability patterns (history, insights, feedback) in the system spec
10
+
11
+ > This feature reinforces Section 8 (Cross-Cutting Concerns: Observability) of the System Spec.
12
+
13
+ ---
14
+
15
+ ## 2. Scope
16
+ ### In Scope
17
+ - Track input/output tokens per agent stage (alex, cass, nigel, codey-plan, codey-implement)
18
+ - Estimate cost per stage using configurable pricing (default: Claude Sonnet pricing)
19
+ - Store token/cost data in pipeline history alongside existing timing data
20
+ - Display cost summary at pipeline completion
21
+ - Add `--cost` flag to `history` command to show cost breakdowns
22
+ - Add `cost-config` CLI command for pricing configuration
23
+
24
+ ### Out of Scope
25
+ - Real-time token streaming (report after each stage completes)
26
+ - Multi-model pricing within a single run (assumes one model for entire pipeline)
27
+ - Billing integration or payment tracking
28
+ - Token optimization recommendations (covered by insights.js)
29
+ - Cost alerts or budget limits (potential future feature)
30
+
31
+ ---
32
+
33
+ ## 3. Actors Involved
34
+
35
+ | Actor | Role in Cost Tracking |
36
+ |-------|----------------------|
37
+ | Human User | Views cost summaries, configures pricing, uses data for optimization decisions |
38
+ | Pipeline Orchestrator | Records token usage at each stage transition |
39
+ | History Module | Persists and retrieves cost data |
40
+ | Each Agent (Alex, Cass, Nigel, Codey) | Token consumption is measured but agents are unaware of tracking |
41
+
42
+ ---
43
+
44
+ ## 4. Behaviour Overview
45
+
46
+ **Happy Path:**
47
+ 1. User runs `/implement-feature "slug"`
48
+ 2. At each stage completion, orchestrator records tokens used (input + output)
49
+ 3. Pipeline completion displays cost summary table
50
+ 4. History entry includes token/cost data per stage
51
+
52
+ **Cost Summary Display (at pipeline end):**
53
+ ```
54
+ Cost Summary for feature: user-auth
55
+
56
+ STAGE INPUT OUTPUT COST
57
+ alex 2,450 1,230 $0.014
58
+ cass 3,100 1,850 $0.019
59
+ nigel 2,800 2,100 $0.018
60
+ codey-plan 1,500 890 $0.009
61
+ codey-impl 4,200 3,500 $0.028
62
+ ─────────────────────────────────────────
63
+ TOTAL 14,050 9,570 $0.088
64
+ ```
65
+
66
+ **History with Costs:**
67
+ ```bash
68
+ $ murmur8 history --cost
69
+
70
+ SLUG STATUS DURATION TOTAL COST
71
+ user-auth success 12m 30s $0.088
72
+ api-validation success 8m 15s $0.062
73
+ cache-layer failed 4m 22s $0.031 (failed at: nigel)
74
+ ```
75
+
76
+ **Stats with Costs:**
77
+ ```bash
78
+ $ murmur8 history --stats --cost
79
+
80
+ Pipeline Statistics (based on 15 runs)
81
+
82
+ METRIC VALUE
83
+ ...existing stats...
84
+ Avg cost per run $0.075
85
+ Total cost (all runs) $1.12
86
+ Most expensive stage codey-implement ($0.42 total)
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 5. State & Lifecycle Interactions
92
+
93
+ **State additions to history entry:**
94
+ ```json
95
+ {
96
+ "slug": "user-auth",
97
+ "stages": {
98
+ "alex": {
99
+ "durationMs": 45000,
100
+ "tokens": { "input": 2450, "output": 1230 },
101
+ "cost": 0.014
102
+ }
103
+ },
104
+ "totalTokens": { "input": 14050, "output": 9570 },
105
+ "totalCost": 0.088
106
+ }
107
+ ```
108
+
109
+ **Lifecycle:**
110
+ - **State-extending:** Adds new fields to existing history entries
111
+ - No new states created
112
+ - Backward compatible (missing token data displays "N/A")
113
+
114
+ ---
115
+
116
+ ## 6. Rules & Decision Logic
117
+
118
+ | Rule | Description | Deterministic |
119
+ |------|-------------|---------------|
120
+ | Token counting | Sum of input + output tokens per stage | Yes |
121
+ | Cost calculation | `(input_tokens * input_price) + (output_tokens * output_price)` | Yes |
122
+ | Default pricing | Claude Sonnet 3.5: $3/M input, $15/M output | Yes (configurable) |
123
+ | Skipped stages | Record 0 tokens for skipped stages (e.g., Cass when `--skip-stories`) | Yes |
124
+ | Failed stages | Record tokens consumed up to failure point | Yes |
125
+ | Rounding | Costs rounded to 3 decimal places ($0.001 precision) | Yes |
126
+
127
+ ---
128
+
129
+ ## 7. Dependencies
130
+
131
+ **System Dependencies:**
132
+ - `src/history.js` - Extended to store/retrieve token data
133
+ - `.claude/pipeline-history.json` - Schema extended with token fields
134
+
135
+ **New Files:**
136
+ - `src/cost.js` - Token counting, cost calculation, formatting
137
+ - `src/commands/cost-config.js` - CLI command for pricing config
138
+ - `.claude/cost-config.json` - Pricing configuration (gitignored)
139
+
140
+ **Technical Dependencies:**
141
+ - Claude Code must expose token usage in Task tool responses
142
+ - If token data unavailable, feature gracefully degrades to "N/A"
143
+
144
+ ---
145
+
146
+ ## 8. Non-Functional Considerations
147
+
148
+ | Concern | Consideration |
149
+ |---------|---------------|
150
+ | Performance | Token counting adds negligible overhead (<1ms per stage) |
151
+ | Storage | ~100 bytes additional per history entry |
152
+ | Backward compatibility | Existing history entries without token data display "N/A" |
153
+ | Accuracy | Estimated costs only; actual billing may differ |
154
+ | Privacy | No sensitive data; token counts are numeric |
155
+
156
+ ---
157
+
158
+ ## 9. Assumptions & Open Questions
159
+
160
+ **Assumptions:**
161
+ - Claude Code Task tool responses include token usage metadata
162
+ - Single pricing model per pipeline run is acceptable
163
+ - Users want cost visibility but not enforcement (no budget limits)
164
+
165
+ **Open Questions:**
166
+ - How does Claude Code expose token usage? Via response metadata or separate API?
167
+ - Should we support custom pricing for enterprise/volume discounts?
168
+ - Should cost data be included in CSV/JSON exports by default?
169
+
170
+ ---
171
+
172
+ ## 10. Impact on System Specification
173
+
174
+ **Reinforces existing patterns:**
175
+ - Section 8 (Observability) - Adds cost as new observability dimension
176
+ - Section 9 (Reliability) - Token data helps diagnose token limit failures
177
+
178
+ **No contradictions identified.**
179
+
180
+ **Suggested System Spec addition (Section 8):**
181
+ ```markdown
182
+ ### Cost Visibility
183
+ - Token usage tracked per stage
184
+ - Estimated costs calculated and persisted
185
+ - Cost summaries available via `history --cost`
186
+ ```
187
+
188
+ ---
189
+
190
+ ## 11. Handover to BA (Cass)
191
+
192
+ **Story themes:**
193
+ 1. **Core tracking** - Record token usage at each stage completion
194
+ 2. **Cost calculation** - Apply pricing model to token counts
195
+ 3. **Display** - Show cost summary at pipeline end
196
+ 4. **History integration** - Store in history, display in `history` command
197
+ 5. **Configuration** - `cost-config` command for pricing settings
198
+
199
+ **Expected story boundaries:**
200
+ - Story 1: Token tracking infrastructure (cost.js, history schema)
201
+ - Story 2: Cost calculation and formatting
202
+ - Story 3: Pipeline completion summary display
203
+ - Story 4: History command integration (`--cost` flag)
204
+ - Story 5: Configuration CLI (`cost-config` command)
205
+
206
+ **Areas needing careful framing:**
207
+ - Graceful degradation when token data unavailable
208
+ - Backward compatibility with existing history entries
209
+ - Cost display formatting (currency, precision)
210
+
211
+ ---
212
+
213
+ ## 12. Change Log (Feature-Level)
214
+ | Date | Change | Reason | Raised By |
215
+ |-----|------|--------|-----------|
216
+ | 2026-03-04 | Initial spec | Enable cost visibility for pipeline optimization | Alex |
@@ -0,0 +1,50 @@
1
+ # Implementation Plan — Cost Tracking
2
+
3
+ ## Summary
4
+
5
+ Implement a cost tracking module (`src/cost.js`) that calculates token costs using configurable pricing, integrate it with the existing history module to persist cost data per stage, and add CLI commands for displaying costs (`history --cost`) and managing pricing configuration (`cost-config`).
6
+
7
+ ## Files to Create/Modify
8
+
9
+ | Path | Action | Purpose |
10
+ |------|--------|---------|
11
+ | `src/cost.js` | Create | Core module: cost calculation, formatting, pricing config |
12
+ | `src/commands/cost-config.js` | Create | CLI command for managing pricing configuration |
13
+ | `src/history.js` | Modify | Add `--cost` display support, cost data in formatters |
14
+ | `src/commands/history.js` | Modify | Parse `--cost` flag and pass to display functions |
15
+ | `src/commands/help.js` | Modify | Add `cost-config` command to help output |
16
+ | `.gitignore` | Modify | Add `.claude/cost-config.json` entry |
17
+
18
+ ## Implementation Steps
19
+
20
+ 1. **Create `src/cost.js`** — Implement core functions using `config-factory.js` pattern:
21
+ - `getDefaultPricing()` — Returns `{ inputPricePerMillion: 3, outputPricePerMillion: 15 }`
22
+ - `loadPricingConfig()` / `savePricingConfig()` — Read/write `.claude/cost-config.json`
23
+ - `calculateCost(inputTokens, outputTokens, pricing)` — Formula per FEATURE_SPEC.md Section 6
24
+ - `formatCost(cost)` — Returns `$X.XXX` or `N/A` for null/undefined
25
+ - `formatTokens(tokens)` — Returns number with thousands separator or `N/A`
26
+ - `getCostSummary(stages)` — Generates formatted cost summary table
27
+
28
+ 2. **Create `src/commands/cost-config.js`** — CLI command following `retry-config.js` pattern:
29
+ - `cost-config` — Display current pricing
30
+ - `cost-config set <key> <value>` — Update pricing
31
+ - `cost-config reset` — Reset to defaults
32
+
33
+ 3. **Modify `src/history.js`** — Extend `displayHistory()` and `showStats()`:
34
+ - Add `cost` option parameter to `displayHistory()`
35
+ - Display `TOTAL COST` column when `--cost` flag present
36
+ - Add cost statistics to `showStats()` when cost data available
37
+ - Handle legacy entries gracefully (display N/A for missing token data)
38
+
39
+ 4. **Modify `src/commands/history.js`** — Parse `--cost` flag:
40
+ - Pass `{ cost: flags.cost }` to `displayHistory()` and `showStats()`
41
+
42
+ 5. **Modify `src/commands/help.js`** — Add `cost-config` to command list
43
+
44
+ 6. **Update `.gitignore`** — Add `.claude/cost-config.json` entry
45
+
46
+ ## Risks/Questions
47
+
48
+ - **Token data availability**: Claude Code must expose token usage in Task tool responses. If unavailable, gracefully degrade to N/A.
49
+ - **History schema migration**: No migration needed — new fields are additive and missing data displays as N/A.
50
+ - **Pricing accuracy**: Costs are estimates only; document this in CLI output.
@@ -0,0 +1,182 @@
1
+ # Feature Specification — Diff Preview
2
+
3
+ ## 1. Feature Intent
4
+
5
+ Provide transparency into pipeline-generated changes before committing them.
6
+
7
+ **Problem:** The auto-commit stage (Step 11) commits all changes without showing users what will be committed. Users may want to review file additions, modifications, and deletions before they become part of the git history.
8
+
9
+ **Solution:** Before auto-commit, display a summary of all pending changes (staged and unstaged) so users can review and approve or abort.
10
+
11
+ **System alignment:** Supports the system spec's "Observability" cross-cutting concern and empowers the Human User actor who has "final arbiter" authority over scope and quality.
12
+
13
+ ---
14
+
15
+ ## 2. Scope
16
+
17
+ ### In Scope
18
+ - Show diff summary before auto-commit (single-feature pipeline)
19
+ - Show diff summary before worktree commit (murmuration mode, Step M5.5)
20
+ - Display file counts: additions, modifications, deletions
21
+ - Display file paths for each category
22
+ - Display actual diff content (optionally truncated for large changes)
23
+ - Prompt user to confirm commit, abort, or review full diff
24
+ - `--no-diff-preview` flag to skip (for automation)
25
+ - Integration with `--no-commit` flag (skip preview when commit is skipped)
26
+
27
+ ### Out of Scope
28
+ - Interactive editing of changes before commit
29
+ - Selective staging/unstaging from the preview
30
+ - Diff viewing in external tools (editor integration)
31
+ - Syntax highlighting in diff output
32
+
33
+ ---
34
+
35
+ ## 3. Actors Involved
36
+
37
+ ### Human User
38
+ - **Can:** View diff summary, approve commit, abort commit, request full diff
39
+ - **Cannot:** Edit changes from the preview screen
40
+
41
+ ### Pipeline Orchestrator
42
+ - **Can:** Trigger diff preview, pass result to commit stage
43
+ - **Cannot:** Auto-approve on user's behalf (unless `--no-diff-preview`)
44
+
45
+ ---
46
+
47
+ ## 4. Behaviour Overview
48
+
49
+ ### Single-Feature Pipeline (Step 11)
50
+
51
+ Before committing, the pipeline:
52
+
53
+ 1. Runs `git status --porcelain` to detect changes
54
+ 2. Categorizes changes: Added (A/??), Modified (M), Deleted (D)
55
+ 3. Displays summary:
56
+ ```
57
+ Changes to commit for feature_user-auth:
58
+
59
+ Added (3 files):
60
+ + .blueprint/features/feature_user-auth/FEATURE_SPEC.md
61
+ + test/feature_user-auth.test.js
62
+ + src/auth.js
63
+
64
+ Modified (1 file):
65
+ ~ src/index.js
66
+
67
+ Deleted (0 files):
68
+ (none)
69
+
70
+ Total: 4 files changed
71
+
72
+ [c]ommit / [a]bort / [d]iff (show full diff)?
73
+ ```
74
+ 4. On 'c': proceed to commit
75
+ 5. On 'a': abort pipeline, exit cleanly
76
+ 6. On 'd': show full `git diff --staged` output, then re-prompt
77
+
78
+ ### Murmuration Mode (Step M5.5)
79
+
80
+ For each successful worktree, before committing:
81
+
82
+ 1. Change to worktree directory
83
+ 2. Run same diff preview flow
84
+ 3. If user aborts one worktree, mark it as "user-aborted" (not "failed")
85
+ 4. Continue to next worktree
86
+
87
+ ### Skip Conditions
88
+
89
+ Diff preview is skipped when:
90
+ - `--no-commit` flag is set (no commit will happen)
91
+ - `--no-diff-preview` flag is set (user opted out)
92
+ - `--yes` flag is set (non-interactive mode)
93
+ - No changes detected (`git status` returns empty)
94
+
95
+ ---
96
+
97
+ ## 5. State & Lifecycle Interactions
98
+
99
+ **States touched:**
100
+ - Feature enters a new transient state: `awaiting-commit-review`
101
+ - Exits to: `committing` (on approve) or `aborted` (on abort)
102
+
103
+ **This feature is:** state-constraining (adds a gate before commit)
104
+
105
+ ---
106
+
107
+ ## 6. Rules & Decision Logic
108
+
109
+ | Rule | Description | Deterministic |
110
+ |------|-------------|---------------|
111
+ | Empty diff | Skip preview if no changes detected | Yes |
112
+ | Truncation | Show first 20 files per category; "... and N more" for overflow | Yes |
113
+ | Large diff | For full diff view, paginate if >100 lines | Yes |
114
+ | Abort handling | Exit code 0 (user choice, not error) | Yes |
115
+
116
+ ---
117
+
118
+ ## 7. Dependencies
119
+
120
+ - Git CLI (`git status`, `git diff`)
121
+ - readline module (for interactive prompt)
122
+ - Existing orchestrator.js or SKILL.md commit flow
123
+
124
+ ---
125
+
126
+ ## 8. Non-Functional Considerations
127
+
128
+ - **Performance:** `git status` and `git diff` are fast; no significant overhead
129
+ - **Error tolerance:** If git commands fail, show error and proceed with prompt
130
+
131
+ ---
132
+
133
+ ## 9. Assumptions & Open Questions
134
+
135
+ ### Assumptions
136
+ - Git working tree is accessible at commit time
137
+ - Terminal supports interactive prompts (or flags are used for CI)
138
+
139
+ ### Open Questions
140
+ - Should diff preview show binary files differently? (Assume: just note "[binary]")
141
+ - Should large diffs auto-truncate in full view? (Assume: yes, with option to show all)
142
+
143
+ ---
144
+
145
+ ## 10. Impact on System Specification
146
+
147
+ **Reinforces:**
148
+ - Observability (users see what's happening)
149
+ - Human User authority (explicit confirmation)
150
+
151
+ **Stretches:**
152
+ - Pipeline lifecycle adds a transient state (`awaiting-commit-review`)
153
+ - May need to document this in system spec's lifecycle section
154
+
155
+ **No contradictions identified.**
156
+
157
+ ---
158
+
159
+ ## 11. Handover to BA (Cass)
160
+
161
+ ### Story Themes
162
+ 1. **Preview display:** Show diff summary before commit
163
+ 2. **User choice:** Confirm, abort, or view full diff
164
+ 3. **Skip conditions:** Honor flags to bypass preview
165
+ 4. **Murmuration integration:** Per-worktree preview in parallel mode
166
+
167
+ ### Expected Story Boundaries
168
+ - Keep preview display and user interaction in one story
169
+ - Separate story for murmuration integration if complex
170
+ - Flags/skip conditions can be part of the main preview story
171
+
172
+ ### Areas Needing Careful Framing
173
+ - Abort behavior should clearly distinguish user-abort from pipeline failure
174
+ - Full diff view interaction: single prompt or back-and-forth?
175
+
176
+ ---
177
+
178
+ ## 12. Change Log
179
+
180
+ | Date | Change | Reason |
181
+ |------|--------|--------|
182
+ | 2026-03-04 | Initial spec | Add transparency before auto-commit |
@@ -0,0 +1,42 @@
1
+ # Implementation Plan: Diff Preview
2
+
3
+ ## Summary
4
+
5
+ Create a new `src/diff-preview.js` module that provides transparency before auto-commit by displaying pending changes and prompting for user confirmation. The module exports pure functions for parsing git status, formatting summaries, and handling user choices, plus integration functions for the pipeline commit flow.
6
+
7
+ ## Files to Create/Modify
8
+
9
+ | Path | Action | Purpose |
10
+ |------|--------|---------|
11
+ | `src/diff-preview.js` | Create | Core module with all exported functions |
12
+ | `src/index.js` | Modify | Export diff-preview functions |
13
+ | `src/theme.js` | Modify | Add `user-aborted` status icon |
14
+ | `SKILL.md` | Modify | Document `--no-diff-preview` flag and Step 10.5 |
15
+
16
+ ## Implementation Steps
17
+
18
+ 1. **Create `src/diff-preview.js` skeleton** with all required exports: `parseGitStatus`, `formatDiffSummary`, `shouldSkipPreview`, `parseUserChoice`, `createAbortResult`, `truncateDiff`, `getPreviewState`, `markWorktreeAborted`, `getPromptText`, `hasChanges`, `formatFeatureHeader`.
19
+
20
+ 2. **Implement `parseGitStatus(porcelainOutput)`** - Parse `git status --porcelain` output into `{ added: [], modified: [], deleted: [], total: number }`. Handle status codes: `A`/`??` (added), `M`/` M` (modified), `D`/` D` (deleted).
21
+
22
+ 3. **Implement display functions** - `formatDiffSummary(changes, slug)` with 20-file truncation per category, `formatFeatureHeader(slug)`, and `hasChanges(changes)`.
23
+
24
+ 4. **Implement skip logic** - `shouldSkipPreview({ noCommit, noDiffPreview, yes, hasChanges })` returns true if any skip condition met.
25
+
26
+ 5. **Implement user choice handling** - `parseUserChoice(input)` maps `c/C` to `'commit'`, `a/A` to `'abort'`, `d/D` to `'diff'`, else `null`.
27
+
28
+ 6. **Implement abort/state functions** - `createAbortResult(slug)` returns `{ exitCode: 0, reason: 'user-aborted', slug }`, `getPreviewState()` returns `'awaiting-commit-review'`, `markWorktreeAborted(worktree)` sets status to `'user-aborted'`.
29
+
30
+ 7. **Implement truncation** - `truncateDiff(diffOutput, threshold)` limits to threshold lines with "... N more lines" message.
31
+
32
+ 8. **Implement `getPromptText()`** - Return the interactive prompt string `[c]ommit / [a]bort / [d]iff`.
33
+
34
+ 9. **Update `src/theme.js`** - Add `'user-aborted': '\u25a1'` (or similar) to STATUS_ICONS for murmuration display consistency.
35
+
36
+ 10. **Update `src/index.js`** - Import and export diff-preview functions for programmatic access.
37
+
38
+ ## Risks/Questions
39
+
40
+ - Git command execution not part of pure functions (caller provides porcelain output)
41
+ - Interactive prompt loop (re-prompt after 'd') handled by integration layer, not tested in unit tests
42
+ - Binary file detection (`[binary]` note) deferred to display integration
package/README.md CHANGED
@@ -103,7 +103,9 @@ This updates `.blueprint/agents/`, `.blueprint/templates/`, `.blueprint/ways_of_
103
103
  | Command | Description |
104
104
  |---------|-------------|
105
105
  | `npx murmur8 history` | View recent pipeline runs |
106
+ | `npx murmur8 history --cost` | View runs with cost breakdown |
106
107
  | `npx murmur8 history --stats` | View aggregate statistics |
108
+ | `npx murmur8 history --stats --cost` | View stats with cost metrics |
107
109
  | `npx murmur8 history --all` | View all runs |
108
110
  | `npx murmur8 history clear` | Clear history |
109
111
  | `npx murmur8 history export` | Export history as CSV (default) |
@@ -136,6 +138,9 @@ This updates `.blueprint/agents/`, `.blueprint/templates/`, `.blueprint/ways_of_
136
138
  | `npx murmur8 stack-config` | View detected tech stack |
137
139
  | `npx murmur8 stack-config set <key> <value>` | Modify stack settings (language, frameworks, testRunner, etc.) |
138
140
  | `npx murmur8 stack-config reset` | Reset to empty defaults |
141
+ | `npx murmur8 cost-config` | View cost tracking pricing |
142
+ | `npx murmur8 cost-config set <key> <value>` | Modify pricing (inputPrice, outputPrice) |
143
+ | `npx murmur8 cost-config reset` | Reset to default Claude pricing |
139
144
  | `npx murmur8 retry-config` | View retry configuration |
140
145
  | `npx murmur8 retry-config set <key> <value>` | Modify retry settings |
141
146
  | `npx murmur8 retry-config reset` | Reset to defaults |
@@ -155,6 +160,7 @@ Run the pipeline with the `/implement-feature` skill in Claude Code:
155
160
  /implement-feature "Your Slug" --no-validate # Skip pre-flight validation
156
161
  /implement-feature "Your Slug" --no-history # Skip history recording
157
162
  /implement-feature "Your Slug" --no-commit # Skip auto-commit
163
+ /implement-feature "Your Slug" --no-diff-preview # Skip diff preview before commit
158
164
  /implement-feature "Your Slug" --pause-after=alex|cass|nigel|codey-plan
159
165
  /implement-feature "Your Slug" --with-stories # Force include Cass stage
160
166
  /implement-feature "Your Slug" --skip-stories # Force skip Cass stage
@@ -243,8 +249,15 @@ The pipeline includes validation, smart routing, feedback loops, and history tra
243
249
 
244
250
 
245
251
  ┌─────────────────────────────────────────────────────────────────┐
252
+ │ Diff Preview (unless --no-diff-preview) │
253
+ │ • Show file changes (added/modified/deleted) │
254
+ │ • User confirms: commit / abort / view full diff │
255
+ └─────────────────────────────────────────────────────────────────┘
256
+
257
+
258
+ ┌─────────────────────────────────────────────────────────────────┐
246
259
  │ Auto-commit → Record to History │
247
- │ • Duration, feedback scores, outcome
260
+ │ • Duration, feedback scores, token cost, outcome
248
261
  └─────────────────────────────────────────────────────────────────┘
249
262
  ```
250
263
 
@@ -255,7 +268,7 @@ murmur8 includes these built-in modules for observability and self-improvement:
255
268
  | Module | Purpose |
256
269
  |--------|---------|
257
270
  | **validate** | Pre-flight checks before pipeline runs |
258
- | **history** | Records execution data (timing, status, feedback) |
271
+ | **history** | Records execution data (timing, status, feedback, cost) |
259
272
  | **insights** | Analyzes patterns, detects bottlenecks, recommends improvements |
260
273
  | **retry** | Smart retry strategies based on failure history |
261
274
  | **feedback** | Agent-to-agent quality assessment with correlation tracking |
@@ -265,6 +278,8 @@ murmur8 includes these built-in modules for observability and self-improvement:
265
278
  | **tools** | Tool schemas and validation for Claude native features |
266
279
  | **murm** | Murmuration pipeline execution using git worktrees |
267
280
  | **stack** | Configurable tech stack detection and configuration |
281
+ | **cost** | Token usage tracking and cost estimation per stage |
282
+ | **diff-preview** | Pre-commit change review with user confirmation |
268
283
 
269
284
  ### How They Work Together
270
285
 
@@ -273,8 +288,12 @@ Pipeline Run
273
288
 
274
289
  ├──► history.js records timing at each stage
275
290
 
291
+ ├──► cost.js tracks token usage per stage
292
+
276
293
  ├──► feedback.js collects quality ratings between stages
277
294
 
295
+ ├──► diff-preview.js shows changes before commit
296
+
278
297
  └──► On completion/failure, data stored in pipeline-history.json
279
298
 
280
299
 
@@ -326,6 +345,7 @@ your-project/
326
345
  │ ├── pipeline-history.json # Execution history (gitignored)
327
346
  │ ├── retry-config.json # Retry configuration (gitignored)
328
347
  │ ├── feedback-config.json # Feedback thresholds (gitignored)
348
+ │ ├── cost-config.json # Cost tracking pricing (gitignored)
329
349
  │ ├── murm-config.json # Murmuration execution config (gitignored)
330
350
  │ ├── murm-queue.json # Murmuration pipeline state (gitignored)
331
351
  │ ├── stack-config.json # Tech stack configuration (gitignored)
@@ -419,6 +439,44 @@ Version 2.7 introduces several optimizations to reduce token usage:
419
439
 
420
440
  **Total estimated savings: 10,000+ tokens per pipeline run** (more for technical features)
421
441
 
442
+ ## Cost Tracking
443
+
444
+ Track token usage and estimated costs per pipeline stage:
445
+
446
+ ```bash
447
+ # View costs in history
448
+ npx murmur8 history --cost
449
+
450
+ SLUG STATUS DURATION TOTAL COST
451
+ user-auth success 12m 30s $0.088
452
+ api-validation success 8m 15s $0.062
453
+
454
+ # View cost statistics
455
+ npx murmur8 history --stats --cost
456
+
457
+ Avg cost per run: $0.075
458
+ Total cost (all runs): $1.12
459
+ Most expensive stage: codey-implement
460
+ ```
461
+
462
+ ### Configuration
463
+
464
+ Default pricing uses Claude Sonnet rates ($3/M input, $15/M output):
465
+
466
+ ```bash
467
+ # View current pricing
468
+ npx murmur8 cost-config
469
+
470
+ # Customize pricing (per million tokens)
471
+ npx murmur8 cost-config set inputPrice 3
472
+ npx murmur8 cost-config set outputPrice 15
473
+
474
+ # Reset to defaults
475
+ npx murmur8 cost-config reset
476
+ ```
477
+
478
+ Cost data is stored in `pipeline-history.json` alongside timing and feedback data.
479
+
422
480
  ## Murmuration
423
481
 
424
482
  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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "murmur8",
3
- "version": "4.3.0",
3
+ "version": "4.3.1",
4
4
  "description": "Multi-agent workflow framework for automated feature development",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,28 @@
1
+ /**
2
+ * cost-config command - Manage pricing configuration for cost tracking
3
+ */
4
+ const { displayConfig, setConfigValue, resetConfig } = require('../cost');
5
+
6
+ const description = 'Manage pricing configuration for cost tracking';
7
+
8
+ async function run(args) {
9
+ const subArg = args[1];
10
+
11
+ if (subArg === 'set') {
12
+ const key = args[2];
13
+ const value = args[3];
14
+ if (!key || !value) {
15
+ console.error('Usage: cost-config set <key> <value>');
16
+ console.error('Valid keys: inputPricePerMillion, outputPricePerMillion');
17
+ process.exit(1);
18
+ }
19
+ setConfigValue(key, value);
20
+ } else if (subArg === 'reset') {
21
+ resetConfig();
22
+ console.log('Cost configuration reset to defaults.');
23
+ } else {
24
+ displayConfig();
25
+ }
26
+ }
27
+
28
+ module.exports = { run, description };
@@ -19,6 +19,8 @@ Commands:
19
19
  history View recent pipeline runs (last 10 by default)
20
20
  history --all View all pipeline runs
21
21
  history --stats View aggregate statistics
22
+ history --cost View history with total cost column
23
+ history --stats --cost View statistics with cost metrics
22
24
  history clear Clear all pipeline history (with confirmation)
23
25
  history clear --force Clear all pipeline history (no confirmation)
24
26
  history export Export history as CSV (default) or JSON
@@ -42,6 +44,9 @@ Commands:
42
44
  stack-config View current tech stack configuration
43
45
  stack-config set <key> <value> Modify a config value (language, runtime, frameworks, etc.)
44
46
  stack-config reset Reset tech stack configuration to defaults
47
+ cost-config View current pricing configuration for cost tracking
48
+ cost-config set <key> <value> Modify a config value (inputPricePerMillion, outputPricePerMillion)
49
+ cost-config reset Reset pricing configuration to defaults
45
50
  murm <slugs...> Run multiple feature pipelines in parallel (murmuration)
46
51
  murm <slugs...> --dry-run Show execution plan without running
47
52
  murm <slugs...> --yes Skip confirmation prompt
@@ -33,9 +33,9 @@ async function run(args) {
33
33
  console.log(result.output);
34
34
  }
35
35
  } else if (flags.stats) {
36
- showStats();
36
+ showStats({ cost: flags.cost });
37
37
  } else {
38
- displayHistory({ all: flags.all });
38
+ displayHistory({ all: flags.all, cost: flags.cost });
39
39
  }
40
40
  }
41
41
 
@@ -17,6 +17,7 @@ function parseFlags(args) {
17
17
  if (arg === '--failures') flags.failures = true;
18
18
  if (arg === '--json') flags.json = true;
19
19
  if (arg === '--feedback') flags.feedback = true;
20
+ if (arg === '--cost') flags.cost = true;
20
21
  }
21
22
  return flags;
22
23
  }
package/src/cost.js ADDED
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ const { createConfigModule } = require('./config-factory');
4
+
5
+ const DEFAULT_INPUT_PRICE = 3;
6
+ const DEFAULT_OUTPUT_PRICE = 15;
7
+
8
+ const costConfig = createConfigModule({
9
+ name: 'Cost',
10
+ file: '.claude/cost-config.json',
11
+ defaults: {
12
+ inputPricePerMillion: DEFAULT_INPUT_PRICE,
13
+ outputPricePerMillion: DEFAULT_OUTPUT_PRICE
14
+ },
15
+ validators: {
16
+ inputPricePerMillion: (v) => v >= 0 || 'Must be non-negative',
17
+ outputPricePerMillion: (v) => v >= 0 || 'Must be non-negative'
18
+ }
19
+ });
20
+
21
+ function getDefaultPricing() {
22
+ return {
23
+ inputPricePerMillion: DEFAULT_INPUT_PRICE,
24
+ outputPricePerMillion: DEFAULT_OUTPUT_PRICE
25
+ };
26
+ }
27
+
28
+ function loadPricingConfig() {
29
+ return costConfig.read();
30
+ }
31
+
32
+ function savePricingConfig(config) {
33
+ costConfig.write(config);
34
+ }
35
+
36
+ function calculateCost(inputTokens, outputTokens, pricing = getDefaultPricing()) {
37
+ const inputCost = (inputTokens / 1_000_000) * pricing.inputPricePerMillion;
38
+ const outputCost = (outputTokens / 1_000_000) * pricing.outputPricePerMillion;
39
+ return Math.round((inputCost + outputCost) * 1000) / 1000;
40
+ }
41
+
42
+ function formatCost(cost) {
43
+ if (cost === null || cost === undefined) return 'N/A';
44
+ return `$${cost.toFixed(3)}`;
45
+ }
46
+
47
+ function formatTokens(tokens) {
48
+ if (tokens === null || tokens === undefined) return 'N/A';
49
+ return tokens.toLocaleString();
50
+ }
51
+
52
+ function getCostSummary(stages, pricing = loadPricingConfig()) {
53
+ const lines = [];
54
+ let totalInput = 0;
55
+ let totalOutput = 0;
56
+ let totalCost = 0;
57
+
58
+ for (const [name, data] of Object.entries(stages)) {
59
+ if (data && data.tokens) {
60
+ const input = data.tokens.input || 0;
61
+ const output = data.tokens.output || 0;
62
+ const cost = data.cost !== undefined ? data.cost : calculateCost(input, output, pricing);
63
+
64
+ totalInput += input;
65
+ totalOutput += output;
66
+ totalCost += cost;
67
+
68
+ lines.push({
69
+ stage: name,
70
+ input,
71
+ output,
72
+ cost
73
+ });
74
+ }
75
+ }
76
+
77
+ return {
78
+ stages: lines,
79
+ totals: {
80
+ input: totalInput,
81
+ output: totalOutput,
82
+ cost: totalCost
83
+ }
84
+ };
85
+ }
86
+
87
+ function displayCostSummary(slug, stages) {
88
+ const summary = getCostSummary(stages);
89
+
90
+ console.log(`\nCost Summary for feature: ${slug}\n`);
91
+ console.log('STAGE INPUT OUTPUT COST');
92
+
93
+ for (const s of summary.stages) {
94
+ const stage = s.stage.padEnd(16);
95
+ const input = formatTokens(s.input).padStart(9);
96
+ const output = formatTokens(s.output).padStart(10);
97
+ const cost = formatCost(s.cost).padStart(8);
98
+ console.log(`${stage} ${input} ${output} ${cost}`);
99
+ }
100
+
101
+ console.log('─'.repeat(45));
102
+ const totalStage = 'TOTAL'.padEnd(16);
103
+ const totalInput = formatTokens(summary.totals.input).padStart(9);
104
+ const totalOutput = formatTokens(summary.totals.output).padStart(10);
105
+ const totalCost = formatCost(summary.totals.cost).padStart(8);
106
+ console.log(`${totalStage} ${totalInput} ${totalOutput} ${totalCost}`);
107
+ }
108
+
109
+ module.exports = {
110
+ CONFIG_FILE: costConfig.CONFIG_FILE,
111
+ getDefaultPricing,
112
+ loadPricingConfig,
113
+ savePricingConfig,
114
+ calculateCost,
115
+ formatCost,
116
+ formatTokens,
117
+ getCostSummary,
118
+ displayCostSummary,
119
+ displayConfig: costConfig.display,
120
+ setConfigValue: costConfig.setValue,
121
+ resetConfig: costConfig.reset
122
+ };
@@ -0,0 +1,165 @@
1
+ 'use strict';
2
+
3
+ function parseGitStatus(porcelainOutput) {
4
+ const added = [];
5
+ const modified = [];
6
+ const deleted = [];
7
+
8
+ if (!porcelainOutput || porcelainOutput.trim() === '') {
9
+ return { added, modified, deleted, total: 0 };
10
+ }
11
+
12
+ const lines = porcelainOutput.trim().split('\n');
13
+ for (const line of lines) {
14
+ if (line.length < 3) continue;
15
+
16
+ const statusCode = line.substring(0, 2);
17
+ const filePath = line.substring(3);
18
+
19
+ if (statusCode === 'A ' || statusCode === '??') {
20
+ added.push(filePath);
21
+ } else if (statusCode === 'M ' || statusCode === ' M') {
22
+ modified.push(filePath);
23
+ } else if (statusCode === 'D ' || statusCode === ' D') {
24
+ deleted.push(filePath);
25
+ }
26
+ }
27
+
28
+ return {
29
+ added,
30
+ modified,
31
+ deleted,
32
+ total: added.length + modified.length + deleted.length
33
+ };
34
+ }
35
+
36
+ function formatDiffSummary(changes, slug) {
37
+ const lines = [];
38
+ const MAX_FILES = 20;
39
+
40
+ lines.push(formatFeatureHeader(slug));
41
+ lines.push('');
42
+
43
+ const addedLabel = `Added (${changes.added.length} file${changes.added.length === 1 ? '' : 's'})`;
44
+ lines.push(addedLabel);
45
+ if (changes.added.length === 0) {
46
+ lines.push(' (none)');
47
+ } else {
48
+ const displayFiles = changes.added.slice(0, MAX_FILES);
49
+ for (const file of displayFiles) {
50
+ lines.push(` + ${file}`);
51
+ }
52
+ if (changes.added.length > MAX_FILES) {
53
+ lines.push(` ... and ${changes.added.length - MAX_FILES} more`);
54
+ }
55
+ }
56
+ lines.push('');
57
+
58
+ const modifiedLabel = `Modified (${changes.modified.length} file${changes.modified.length === 1 ? '' : 's'})`;
59
+ lines.push(modifiedLabel);
60
+ if (changes.modified.length === 0) {
61
+ lines.push(' (none)');
62
+ } else {
63
+ const displayFiles = changes.modified.slice(0, MAX_FILES);
64
+ for (const file of displayFiles) {
65
+ lines.push(` ~ ${file}`);
66
+ }
67
+ if (changes.modified.length > MAX_FILES) {
68
+ lines.push(` ... and ${changes.modified.length - MAX_FILES} more`);
69
+ }
70
+ }
71
+ lines.push('');
72
+
73
+ const deletedLabel = `Deleted (${changes.deleted.length} file${changes.deleted.length === 1 ? '' : 's'})`;
74
+ lines.push(deletedLabel);
75
+ if (changes.deleted.length === 0) {
76
+ lines.push(' (none)');
77
+ } else {
78
+ const displayFiles = changes.deleted.slice(0, MAX_FILES);
79
+ for (const file of displayFiles) {
80
+ lines.push(` - ${file}`);
81
+ }
82
+ if (changes.deleted.length > MAX_FILES) {
83
+ lines.push(` ... and ${changes.deleted.length - MAX_FILES} more`);
84
+ }
85
+ }
86
+ lines.push('');
87
+
88
+ lines.push(`Total: ${changes.total} files changed`);
89
+
90
+ return lines.join('\n');
91
+ }
92
+
93
+ function formatFeatureHeader(slug) {
94
+ return `--- Changes to commit for feature: ${slug} ---`;
95
+ }
96
+
97
+ function hasChanges(changes) {
98
+ return changes.total > 0;
99
+ }
100
+
101
+ function shouldSkipPreview({ noCommit, noDiffPreview, yes, hasChanges }) {
102
+ if (noCommit) return true;
103
+ if (noDiffPreview) return true;
104
+ if (yes) return true;
105
+ if (!hasChanges) return true;
106
+ return false;
107
+ }
108
+
109
+ function parseUserChoice(input) {
110
+ if (!input || typeof input !== 'string') return null;
111
+ const normalized = input.toLowerCase().trim();
112
+ if (normalized === 'c') return 'commit';
113
+ if (normalized === 'a') return 'abort';
114
+ if (normalized === 'd') return 'diff';
115
+ return null;
116
+ }
117
+
118
+ function createAbortResult(slug) {
119
+ return {
120
+ exitCode: 0,
121
+ reason: 'user-aborted',
122
+ slug
123
+ };
124
+ }
125
+
126
+ function truncateDiff(diffOutput, threshold) {
127
+ if (!diffOutput) return diffOutput;
128
+ const lines = diffOutput.split('\n');
129
+ if (lines.length <= threshold) {
130
+ return diffOutput;
131
+ }
132
+ const truncated = lines.slice(0, threshold);
133
+ const remaining = lines.length - threshold;
134
+ truncated.push(`... ${remaining} more lines`);
135
+ return truncated.join('\n');
136
+ }
137
+
138
+ function getPreviewState() {
139
+ return 'awaiting-commit-review';
140
+ }
141
+
142
+ function markWorktreeAborted(worktree) {
143
+ return {
144
+ ...worktree,
145
+ status: 'user-aborted'
146
+ };
147
+ }
148
+
149
+ function getPromptText() {
150
+ return '[c]ommit / [a]bort / [d]iff';
151
+ }
152
+
153
+ module.exports = {
154
+ parseGitStatus,
155
+ formatDiffSummary,
156
+ formatFeatureHeader,
157
+ hasChanges,
158
+ shouldSkipPreview,
159
+ parseUserChoice,
160
+ createAbortResult,
161
+ truncateDiff,
162
+ getPreviewState,
163
+ markWorktreeAborted,
164
+ getPromptText
165
+ };
package/src/history.js CHANGED
@@ -195,8 +195,14 @@ function formatDate(isoString) {
195
195
  return date.toISOString().replace('T', ' ').slice(0, 19);
196
196
  }
197
197
 
198
+ function formatCostValue(cost) {
199
+ if (cost === null || cost === undefined) return 'N/A';
200
+ return `$${cost.toFixed(3)}`;
201
+ }
202
+
198
203
  function displayHistory(options = {}) {
199
204
  const showAll = options.all || false;
205
+ const showCost = options.cost || false;
200
206
  const useColor = options.color !== false && process.stdout.isTTY;
201
207
 
202
208
  const history = readHistoryFile();
@@ -220,7 +226,11 @@ function displayHistory(options = {}) {
220
226
  const showing = entries.length;
221
227
 
222
228
  console.log(`\nPipeline History (showing ${showing} of ${total} runs)\n`);
223
- console.log(' SLUG STATUS DATE DURATION');
229
+ if (showCost) {
230
+ console.log(' SLUG STATUS DURATION TOTAL COST');
231
+ } else {
232
+ console.log(' SLUG STATUS DATE DURATION');
233
+ }
224
234
 
225
235
  for (const entry of entries) {
226
236
  const slug = entry.slug.padEnd(18);
@@ -243,7 +253,12 @@ function displayHistory(options = {}) {
243
253
  suffix = ` (paused at: ${entry.pausedAfter})`;
244
254
  }
245
255
 
246
- console.log(` ${slug} ${status} ${date} ${duration}${suffix}`);
256
+ if (showCost) {
257
+ const costDisplay = formatCostValue(entry.totalCost).padEnd(10);
258
+ console.log(` ${slug} ${status} ${duration.padEnd(9)} ${costDisplay}${suffix}`);
259
+ } else {
260
+ console.log(` ${slug} ${status} ${date} ${duration}${suffix}`);
261
+ }
247
262
  }
248
263
 
249
264
  if (!showAll && total > 10) {
@@ -252,7 +267,8 @@ function displayHistory(options = {}) {
252
267
  console.log(`Run 'murmur8 history --stats' for aggregate statistics.`);
253
268
  }
254
269
 
255
- function showStats() {
270
+ function showStats(options = {}) {
271
+ const showCost = options.cost || false;
256
272
  const history = readHistoryFile();
257
273
 
258
274
  if (history.error === 'corrupted') {
@@ -285,6 +301,38 @@ function showStats() {
285
301
  console.log(` Avg pipeline duration ${formatDuration(avgTotal)}`);
286
302
  }
287
303
 
304
+ if (showCost) {
305
+ const runsWithCost = history.filter(e => e.totalCost !== undefined);
306
+ if (runsWithCost.length > 0) {
307
+ const totalCostAll = runsWithCost.reduce((sum, e) => sum + e.totalCost, 0);
308
+ const avgCost = totalCostAll / runsWithCost.length;
309
+ console.log(` Avg cost per run ${formatCostValue(avgCost)}`);
310
+ console.log(` Total cost (all runs) ${formatCostValue(totalCostAll)}`);
311
+
312
+ const stages = ['alex', 'cass', 'nigel', 'codey-plan', 'codey-implement'];
313
+ const stageCosts = {};
314
+ for (const stage of stages) {
315
+ stageCosts[stage] = 0;
316
+ }
317
+ for (const entry of runsWithCost) {
318
+ if (entry.stages) {
319
+ for (const stage of stages) {
320
+ if (entry.stages[stage] && entry.stages[stage].cost !== undefined) {
321
+ stageCosts[stage] += entry.stages[stage].cost;
322
+ }
323
+ }
324
+ }
325
+ }
326
+ const maxCost = Math.max(...Object.values(stageCosts));
327
+ const mostExpensive = Object.entries(stageCosts)
328
+ .filter(([, cost]) => cost === maxCost)
329
+ .map(([stage]) => stage);
330
+ if (mostExpensive.length > 0 && maxCost > 0) {
331
+ console.log(` Most expensive stage ${mostExpensive[0]} (${formatCostValue(maxCost)} total)`);
332
+ }
333
+ }
334
+ }
335
+
288
336
  const stages = ['alex', 'cass', 'nigel', 'codey-plan', 'codey-implement'];
289
337
  const stageStats = {};
290
338
  const failureCounts = {};
package/src/index.js CHANGED
@@ -81,6 +81,19 @@ const {
81
81
  detectStackConfig,
82
82
  displayStackConfig
83
83
  } = require('./stack');
84
+ const {
85
+ parseGitStatus,
86
+ formatDiffSummary,
87
+ formatFeatureHeader,
88
+ hasChanges,
89
+ shouldSkipPreview,
90
+ parseUserChoice,
91
+ createAbortResult,
92
+ truncateDiff,
93
+ getPreviewState,
94
+ markWorktreeAborted,
95
+ getPromptText
96
+ } = require('./diff-preview');
84
97
  const tools = require('./tools');
85
98
  const theme = require('./theme');
86
99
 
@@ -149,6 +162,18 @@ module.exports = {
149
162
  tools,
150
163
  // Theme module (murmuration visual theming)
151
164
  theme,
165
+ // Diff preview exports
166
+ parseGitStatus,
167
+ formatDiffSummary,
168
+ formatFeatureHeader,
169
+ hasChanges,
170
+ shouldSkipPreview,
171
+ parseUserChoice,
172
+ createAbortResult,
173
+ truncateDiff,
174
+ getPreviewState,
175
+ markWorktreeAborted,
176
+ getPromptText,
152
177
  // Interactive mode exports
153
178
  parseInteractiveFlags,
154
179
  shouldEnterInteractiveMode,
package/src/theme.js CHANGED
@@ -50,7 +50,8 @@ const STATUS_ICONS = {
50
50
  'murm_complete': '\u2713', // ✓
51
51
  'murm_failed': '\u2717', // ✗
52
52
  'merge_conflict': '\u26a0', // ⚠
53
- 'aborted': '\u25a0' // ■
53
+ 'aborted': '\u25a0', // ■
54
+ 'user-aborted': '\u25a1' // □
54
55
  };
55
56
 
56
57
  // --- Spinner ---