murmur8 4.0.1 → 4.2.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.
@@ -18,7 +18,7 @@ This is our shared manifesto — for every agent and the human. Read it before y
18
18
 
19
19
  ## We build things that matter
20
20
 
21
- We are not here to generate output. We are here to build a product that makes a real impact on the people or agents who use it. Every line of code, every test, every story should serve that purpose. If it doesn't make things better for the user, we ask why we're doing it. We enjoy building
21
+ We are not here to generate output. We are here to build a product that makes a real impact on the people or agents who use it. Every line of code, every test, every story should serve that purpose. If it doesn't make things better for the user, we ask why we're doing it. We enjoy building great things. We don't just make slop!
22
22
 
23
23
  ## We take pride in beautiful code
24
24
 
@@ -0,0 +1,215 @@
1
+ # Feature Specification — Export Pipeline History
2
+
3
+ ## 1. Feature Intent
4
+
5
+ **Why this feature exists.**
6
+
7
+ Users need to extract pipeline history data for analysis, reporting, and integration with external tools. The existing `history` and `insights` commands provide on-demand viewing, but teams often require:
8
+
9
+ - **Spreadsheet analysis** — CSV export for pivot tables, charts, and ad-hoc queries
10
+ - **Custom dashboards** — JSON export for ingestion into monitoring tools or team portals
11
+ - **Filtered exports** — Ability to slice data by date range, status, or specific features
12
+
13
+ This feature supports the system purpose of **observability** (System Spec Section 8) by making pipeline execution data portable and actionable beyond the CLI.
14
+
15
+ ---
16
+
17
+ ## 2. Scope
18
+
19
+ ### In Scope
20
+
21
+ - New `export` subcommand under `murmur8 history`
22
+ - Export formats: CSV and JSON
23
+ - Filtering options: `--since`, `--until`, `--status`, `--feature`
24
+ - Output to stdout (default) or file via `--output`
25
+
26
+ ### Out of Scope
27
+
28
+ - Export of queue state (`.claude/implement-queue.json`) — separate concern
29
+ - Scheduled/automated exports — users can integrate via shell scripts
30
+ - Direct integration with external services (Slack, email, dashboards)
31
+ - Aggregated statistics export — `--stats` output remains display-only
32
+
33
+ ---
34
+
35
+ ## 3. Actors Involved
36
+
37
+ ### Human User
38
+
39
+ - **Can:** Export history data in CSV or JSON format, apply filters, redirect to file
40
+ - **Cannot:** Export queue state or insights aggregations through this command
41
+
42
+ ---
43
+
44
+ ## 4. Behaviour Overview
45
+
46
+ **Happy Path:**
47
+
48
+ 1. User runs `murmur8 history export --format=csv`
49
+ 2. System reads `.claude/pipeline-history.json`
50
+ 3. System converts entries to CSV format
51
+ 4. System outputs to stdout
52
+
53
+ **With Filters:**
54
+
55
+ 1. User runs `murmur8 history export --format=json --since=2024-01-01 --status=failed`
56
+ 2. System reads history, filters by date and status
57
+ 3. System outputs filtered JSON to stdout
58
+
59
+ **To File:**
60
+
61
+ 1. User runs `murmur8 history export --format=csv --output=report.csv`
62
+ 2. System writes CSV to specified file
63
+ 3. System prints confirmation message
64
+
65
+ **Edge Cases:**
66
+
67
+ - Empty history: Output empty array/CSV header with no data rows
68
+ - Corrupted history file: Warn and exit with non-zero status
69
+ - No matches for filters: Output empty result (not an error)
70
+ - Invalid date format: Error with usage hint
71
+
72
+ ---
73
+
74
+ ## 5. State & Lifecycle Interactions
75
+
76
+ This feature is **state-reading only**. It does not modify pipeline state.
77
+
78
+ - **Reads:** `.claude/pipeline-history.json`
79
+ - **Does not modify:** Any state files or artifacts
80
+
81
+ ---
82
+
83
+ ## 6. Rules & Decision Logic
84
+
85
+ ### Format Selection Rule
86
+
87
+ | `--format` value | Output |
88
+ |------------------|--------|
89
+ | `csv` (default) | Comma-separated values with header row |
90
+ | `json` | Pretty-printed JSON array |
91
+
92
+ ### Date Filtering Rules
93
+
94
+ - `--since=YYYY-MM-DD`: Include entries where `completedAt >= since`
95
+ - `--until=YYYY-MM-DD`: Include entries where `completedAt <= until`
96
+ - Both can be combined for a range
97
+ - Dates are interpreted as start of day (00:00:00) in local timezone
98
+
99
+ ### Status Filtering Rule
100
+
101
+ - `--status=success|failed|paused`: Include only entries with matching status
102
+ - Multiple values not supported in v1 (e.g., no `--status=success,failed`)
103
+
104
+ ### Feature Filtering Rule
105
+
106
+ - `--feature=<slug>`: Include only entries matching the feature slug
107
+ - Exact match (not substring)
108
+
109
+ ### Output Destination Rule
110
+
111
+ - No `--output`: Write to stdout
112
+ - `--output=<path>`: Write to file, creating parent directories if needed
113
+
114
+ ---
115
+
116
+ ## 7. Dependencies
117
+
118
+ ### System Components
119
+
120
+ - `src/history.js` — Existing `readHistoryFile()` function for reading history
121
+ - `src/theme.js` — Optional colorization for confirmation messages
122
+
123
+ ### Data Format
124
+
125
+ Relies on existing history entry structure:
126
+
127
+ ```javascript
128
+ {
129
+ slug: string,
130
+ status: 'success' | 'failed' | 'paused',
131
+ startedAt: ISO8601,
132
+ completedAt: ISO8601,
133
+ totalDurationMs: number,
134
+ stages: { [stage]: { durationMs, feedback? } },
135
+ failedStage?: string,
136
+ pausedAfter?: string
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ## 8. Non-Functional Considerations
143
+
144
+ ### Performance
145
+
146
+ - History files are typically small (<1MB). No streaming required for v1.
147
+ - If performance becomes an issue with large histories, consider streaming JSON output.
148
+
149
+ ### Error Handling
150
+
151
+ - Invalid date format: Exit with code 1, print usage hint
152
+ - Missing history file: Treat as empty (not an error)
153
+ - Corrupted history file: Exit with code 1, suggest `history clear`
154
+ - File write error: Exit with code 1 with descriptive message
155
+
156
+ ---
157
+
158
+ ## 9. Assumptions & Open Questions
159
+
160
+ ### Assumptions
161
+
162
+ - History file structure is stable (no migration needed)
163
+ - Users have write permissions for `--output` destination
164
+ - Date parsing uses native JavaScript Date (ISO 8601 format expected)
165
+
166
+ ### Open Questions
167
+
168
+ - **Q:** Should we support `--limit` to cap exported rows?
169
+ - **A:** Defer to v2. Users can pipe to `head` for now.
170
+ - **Q:** Should CSV include nested stage data?
171
+ - **A:** v1 exports top-level fields only. Stage details available in JSON.
172
+
173
+ ---
174
+
175
+ ## 10. Impact on System Specification
176
+
177
+ This feature **reinforces** existing system assumptions:
178
+
179
+ - Aligns with observability cross-cutting concern (System Spec Section 8)
180
+ - Builds on existing history module without modifying its behavior
181
+ - Does not introduce new state, lifecycle, or invariant changes
182
+
183
+ No system spec changes required.
184
+
185
+ ---
186
+
187
+ ## 11. Handover to BA (Cass)
188
+
189
+ ### Story Themes
190
+
191
+ 1. **Basic Export** — Export history to CSV or JSON format
192
+ 2. **Date Filtering** — Filter exports by date range
193
+ 3. **Status Filtering** — Filter exports by pipeline status
194
+ 4. **Feature Filtering** — Filter exports by feature slug
195
+ 5. **File Output** — Write exports to a file instead of stdout
196
+
197
+ ### Expected Story Boundaries
198
+
199
+ - Each filter type should be a separate story for independent testing
200
+ - File output can be combined with basic export
201
+ - Error handling can be implicit in each story's acceptance criteria
202
+
203
+ ### Areas Needing Careful Story Framing
204
+
205
+ - CSV field ordering and escaping rules (commas, quotes in slugs)
206
+ - Empty result behavior (should still output valid CSV/JSON structure)
207
+ - Date parsing edge cases (timezone handling, invalid formats)
208
+
209
+ ---
210
+
211
+ ## 12. Change Log (Feature-Level)
212
+
213
+ | Date | Change | Reason | Raised By |
214
+ |------|--------|--------|-----------|
215
+ | 2026-03-03 | Initial feature specification | Implement export-history from backlog | Alex |
@@ -0,0 +1,48 @@
1
+ # Implementation Plan — Export Pipeline History
2
+
3
+ ## Summary
4
+
5
+ Add `exportHistory()` function to `src/history.js` that reads pipeline history, applies optional filters (date range, status, feature slug), and outputs CSV or JSON. Wire `history export` subcommand in CLI to invoke this function with parsed flags.
6
+
7
+ ## Files to Create/Modify
8
+
9
+ | Path | Action | Purpose |
10
+ |------|--------|---------|
11
+ | `src/history.js` | Modify | Add `exportHistory()` function with filtering, CSV/JSON formatting, file output |
12
+ | `bin/cli.js` | Modify | Add `export` subcommand to `history` handler, parse new flags |
13
+
14
+ ## Implementation Steps
15
+
16
+ 1. **Add date validation helper** in `src/history.js` — validate YYYY-MM-DD format, return Date or null
17
+
18
+ 2. **Add CSV formatter** in `src/history.js` — convert entry array to CSV string with header row (slug, status, startedAt, completedAt, totalDurationMs, failedStage, pausedAfter), escape commas/quotes in values
19
+
20
+ 3. **Add JSON formatter** in `src/history.js` — pretty-print array with 2-space indent
21
+
22
+ 4. **Implement `exportHistory(options)` function** in `src/history.js`:
23
+ - Read history via `readHistoryFile()`
24
+ - Return `{ exitCode: 1, error: '...' }` if corrupted
25
+ - Apply filters: `since`, `until`, `status`, `feature` (all optional)
26
+ - Validate date formats and status values, return error if invalid
27
+ - Format output as CSV (default) or JSON based on `options.format`
28
+ - If `options.output`: write to file (create parent dirs), return `{ message: '...' }`
29
+ - Otherwise return `{ output: '...' }` for stdout
30
+
31
+ 5. **Export `exportHistory`** from `src/history.js` module.exports
32
+
33
+ 6. **Add export subcommand handling** in `bin/cli.js`:
34
+ - In `history` command handler, check if `subArg === 'export'`
35
+ - Parse flags: `--format=csv|json`, `--since=YYYY-MM-DD`, `--until=YYYY-MM-DD`, `--status=success|failed|paused`, `--feature=<slug>`, `--output=<path>`
36
+ - Call `exportHistory()` with options object
37
+ - Print `result.output` to stdout or `result.message` for file confirmation
38
+ - Exit with `result.exitCode` if error
39
+
40
+ 7. **Update help text** in `showHelp()` to document `history export` and its flags
41
+
42
+ 8. **Run tests** — `node --test test/feature_export-history.test.js`
43
+
44
+ ## Risks/Questions
45
+
46
+ - CSV escaping edge case: slugs containing commas or quotes need proper RFC 4180 escaping (wrap in quotes, double internal quotes)
47
+ - Date filtering uses `completedAt` per spec; entries without `completedAt` should be excluded from date filters
48
+ - Empty/missing history file treated as empty array (not an error per spec)
@@ -0,0 +1,48 @@
1
+ # Story — Basic Export
2
+
3
+ ## User story
4
+
5
+ As a developer, I want to export pipeline history to CSV or JSON format so that I can analyze execution data in spreadsheets or integrate with external tools.
6
+
7
+ ---
8
+
9
+ ## Acceptance criteria
10
+
11
+ **AC-1 — Default CSV export to stdout**
12
+ - Given the history file contains one or more entries,
13
+ - When I run `murmur8 history export`,
14
+ - Then the output is written to stdout in CSV format with a header row.
15
+
16
+ **AC-2 — Explicit CSV format selection**
17
+ - Given the history file contains entries,
18
+ - When I run `murmur8 history export --format=csv`,
19
+ - Then the output is CSV format with columns: slug, status, startedAt, completedAt, totalDurationMs, failedStage, pausedAfter.
20
+
21
+ **AC-3 — JSON format selection**
22
+ - Given the history file contains entries,
23
+ - When I run `murmur8 history export --format=json`,
24
+ - Then the output is a pretty-printed JSON array containing all history entries with their full structure.
25
+
26
+ **AC-4 — Empty history produces valid structure**
27
+ - Given the history file is empty or does not exist,
28
+ - When I run `murmur8 history export --format=csv`,
29
+ - Then the output is a CSV header row with no data rows.
30
+
31
+ **AC-5 — Empty history produces valid JSON**
32
+ - Given the history file is empty or does not exist,
33
+ - When I run `murmur8 history export --format=json`,
34
+ - Then the output is an empty JSON array `[]`.
35
+
36
+ **AC-6 — Corrupted history file handling**
37
+ - Given the history file contains invalid JSON,
38
+ - When I run `murmur8 history export`,
39
+ - Then the command exits with code 1 and displays an error message suggesting `history clear`.
40
+
41
+ ---
42
+
43
+ ## Out of scope
44
+
45
+ - Exporting nested stage data in CSV (available in JSON only)
46
+ - Streaming output for large files
47
+ - Custom column selection
48
+ - Export of queue state or insights aggregations
@@ -0,0 +1,42 @@
1
+ # Story — Date Filtering
2
+
3
+ ## User story
4
+
5
+ As a developer, I want to filter exported history by date range so that I can extract data for specific time periods for reporting or analysis.
6
+
7
+ ---
8
+
9
+ ## Acceptance criteria
10
+
11
+ **AC-1 — Filter by since date**
12
+ - Given the history file contains entries from multiple dates,
13
+ - When I run `murmur8 history export --since=2024-01-15`,
14
+ - Then only entries where `completedAt >= 2024-01-15T00:00:00` (local time) are included in the output.
15
+
16
+ **AC-2 — Filter by until date**
17
+ - Given the history file contains entries from multiple dates,
18
+ - When I run `murmur8 history export --until=2024-01-20`,
19
+ - Then only entries where `completedAt <= 2024-01-20T23:59:59` (local time) are included in the output.
20
+
21
+ **AC-3 — Combined date range filter**
22
+ - Given the history file contains entries from multiple dates,
23
+ - When I run `murmur8 history export --since=2024-01-15 --until=2024-01-20`,
24
+ - Then only entries within the inclusive date range are included in the output.
25
+
26
+ **AC-4 — No matches returns empty result**
27
+ - Given the history file contains entries but none within the specified range,
28
+ - When I run `murmur8 history export --since=2099-01-01`,
29
+ - Then the output is a valid empty structure (CSV header only or empty JSON array).
30
+
31
+ **AC-5 — Invalid date format error**
32
+ - Given an invalid date format is provided,
33
+ - When I run `murmur8 history export --since=invalid-date`,
34
+ - Then the command exits with code 1 and displays a usage hint showing the expected YYYY-MM-DD format.
35
+
36
+ ---
37
+
38
+ ## Out of scope
39
+
40
+ - Time-of-day filtering (only date precision)
41
+ - Timezone configuration (uses local timezone)
42
+ - Relative date expressions (e.g., "last week")
@@ -0,0 +1,42 @@
1
+ # Story — Feature Filtering
2
+
3
+ ## User story
4
+
5
+ As a developer, I want to filter exported history by feature slug so that I can extract execution data for a specific feature across all its runs.
6
+
7
+ ---
8
+
9
+ ## Acceptance criteria
10
+
11
+ **AC-1 — Filter by exact feature slug**
12
+ - Given the history file contains entries for multiple features,
13
+ - When I run `murmur8 history export --feature=user-auth`,
14
+ - Then only entries where `slug === 'user-auth'` are included in the output.
15
+
16
+ **AC-2 — Exact match only**
17
+ - Given the history file contains entries for "user-auth" and "user-auth-v2",
18
+ - When I run `murmur8 history export --feature=user-auth`,
19
+ - Then only entries with exact slug "user-auth" are included (not "user-auth-v2").
20
+
21
+ **AC-3 — No matches returns empty result**
22
+ - Given the history file contains entries but none matching the specified slug,
23
+ - When I run `murmur8 history export --feature=nonexistent`,
24
+ - Then the output is a valid empty structure (CSV header only or empty JSON array).
25
+
26
+ **AC-4 — Combine with other filters**
27
+ - Given the history file contains multiple entries for a feature with different statuses,
28
+ - When I run `murmur8 history export --feature=user-auth --status=failed`,
29
+ - Then only entries matching both the feature slug AND the status are included.
30
+
31
+ **AC-5 — Case-sensitive matching**
32
+ - Given the history file contains an entry with slug "User-Auth",
33
+ - When I run `murmur8 history export --feature=user-auth`,
34
+ - Then no entries are returned (matching is case-sensitive).
35
+
36
+ ---
37
+
38
+ ## Out of scope
39
+
40
+ - Substring or pattern matching
41
+ - Multiple feature slugs in a single filter
42
+ - Case-insensitive matching option
@@ -0,0 +1,48 @@
1
+ # Story — File Output
2
+
3
+ ## User story
4
+
5
+ As a developer, I want to write exported history directly to a file so that I can save reports without shell redirection and receive confirmation of the write.
6
+
7
+ ---
8
+
9
+ ## Acceptance criteria
10
+
11
+ **AC-1 — Write to specified file**
12
+ - Given the history file contains entries,
13
+ - When I run `murmur8 history export --output=report.csv`,
14
+ - Then the export is written to `report.csv` in the current directory.
15
+
16
+ **AC-2 — Success confirmation message**
17
+ - Given the export writes successfully,
18
+ - When I run `murmur8 history export --output=report.csv`,
19
+ - Then a confirmation message is displayed showing the file path and entry count.
20
+
21
+ **AC-3 — Create parent directories**
22
+ - Given the output path includes directories that do not exist,
23
+ - When I run `murmur8 history export --output=reports/2024/january.csv`,
24
+ - Then the parent directories are created and the file is written.
25
+
26
+ **AC-4 — Combine with format option**
27
+ - Given the history file contains entries,
28
+ - When I run `murmur8 history export --format=json --output=report.json`,
29
+ - Then the file contains JSON-formatted export data.
30
+
31
+ **AC-5 — File write error handling**
32
+ - Given the output path is not writable (e.g., permission denied),
33
+ - When I run `murmur8 history export --output=/readonly/report.csv`,
34
+ - Then the command exits with code 1 and displays a descriptive error message.
35
+
36
+ **AC-6 — Overwrite existing file**
37
+ - Given a file already exists at the output path,
38
+ - When I run `murmur8 history export --output=existing.csv`,
39
+ - Then the existing file is overwritten without prompting.
40
+
41
+ ---
42
+
43
+ ## Out of scope
44
+
45
+ - Interactive overwrite confirmation
46
+ - Append mode for existing files
47
+ - Automatic file extension based on format
48
+ - Output to multiple files
@@ -0,0 +1,42 @@
1
+ # Story — Status Filtering
2
+
3
+ ## User story
4
+
5
+ As a developer, I want to filter exported history by pipeline status so that I can analyze specific outcomes like failures or successful runs.
6
+
7
+ ---
8
+
9
+ ## Acceptance criteria
10
+
11
+ **AC-1 — Filter by success status**
12
+ - Given the history file contains entries with mixed statuses,
13
+ - When I run `murmur8 history export --status=success`,
14
+ - Then only entries with `status === 'success'` are included in the output.
15
+
16
+ **AC-2 — Filter by failed status**
17
+ - Given the history file contains entries with mixed statuses,
18
+ - When I run `murmur8 history export --status=failed`,
19
+ - Then only entries with `status === 'failed'` are included in the output.
20
+
21
+ **AC-3 — Filter by paused status**
22
+ - Given the history file contains entries with mixed statuses,
23
+ - When I run `murmur8 history export --status=paused`,
24
+ - Then only entries with `status === 'paused'` are included in the output.
25
+
26
+ **AC-4 — No matches returns empty result**
27
+ - Given the history file contains entries but none with the specified status,
28
+ - When I run `murmur8 history export --status=failed`,
29
+ - Then the output is a valid empty structure (CSV header only or empty JSON array).
30
+
31
+ **AC-5 — Invalid status value error**
32
+ - Given an invalid status value is provided,
33
+ - When I run `murmur8 history export --status=unknown`,
34
+ - Then the command exits with code 1 and displays an error listing valid status values: success, failed, paused.
35
+
36
+ ---
37
+
38
+ ## Out of scope
39
+
40
+ - Multiple status values in a single filter (e.g., `--status=success,failed`)
41
+ - Negation filters (e.g., exclude failed)
42
+ - Custom status values
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # murmur8
2
2
 
3
- A multi-agent workflow framework for automated feature development. Like a murmuration of starlings individual agents moving together as one, each responding to its neighbours to create something greater than the sum of its parts.
3
+ A multi-agent workflow framework for automated feature development In Claude Code and Copilot CLI. Four specialised AI agents collaborate in sequence to take features from specification to implementation, with built-in feedback loops and self-improvement capabilities.
4
4
 
5
- Four specialized AI agents collaborate in sequence to take features from specification to implementation, with built-in feedback loops and self-improvement capabilities.
5
+ Like a murmuration of starlings, individual agents move together as one, each responding to its neighbours to create something greater than the sum of its parts.
6
6
 
7
7
  ## Upgrading to v4.0
8
8
 
@@ -98,6 +98,13 @@ This updates `.blueprint/agents/`, `.blueprint/templates/`, `.blueprint/ways_of_
98
98
  | `npx murmur8 history --stats` | View aggregate statistics |
99
99
  | `npx murmur8 history --all` | View all runs |
100
100
  | `npx murmur8 history clear` | Clear history |
101
+ | `npx murmur8 history export` | Export history as CSV (default) |
102
+ | `npx murmur8 history export --format=json` | Export as JSON |
103
+ | `npx murmur8 history export --since=YYYY-MM-DD` | Filter by start date |
104
+ | `npx murmur8 history export --until=YYYY-MM-DD` | Filter by end date |
105
+ | `npx murmur8 history export --status=<status>` | Filter by status (success/failed/paused) |
106
+ | `npx murmur8 history export --feature=<slug>` | Filter by feature slug |
107
+ | `npx murmur8 history export --output=<path>` | Write to file instead of stdout |
101
108
  | `npx murmur8 insights` | Analyze patterns and get recommendations |
102
109
  | `npx murmur8 insights --feedback` | View feedback correlation analysis |
103
110
  | `npx murmur8 insights --bottlenecks` | View bottleneck analysis |
@@ -363,6 +370,30 @@ $ npx murmur8 insights
363
370
  - Avg duration: 14 min → 11 min (improving)
364
371
  ```
365
372
 
373
+ ## Multi-CLI Support (v4.1)
374
+
375
+ The `/implement-feature` skill works with both **Claude Code** and **GitHub Copilot CLI**. During initialization, murmur8 installs the skill to both locations:
376
+
377
+ | CLI | Location |
378
+ |-----|----------|
379
+ | Claude Code | `.claude/commands/implement-feature.md` |
380
+ | Copilot CLI | `.github/skills/implement-feature/SKILL.md` |
381
+
382
+ The Copilot CLI location is a symlink to the Claude Code master, ensuring both tools use identical skill definitions.
383
+
384
+ ### Usage
385
+
386
+ ```bash
387
+ # Initialize (installs skill for both CLIs)
388
+ npx murmur8 init
389
+
390
+ # Then use either CLI:
391
+ /implement-feature user-auth # Works in Claude Code
392
+ /implement-feature user-auth # Works in Copilot CLI
393
+ ```
394
+
395
+ Both CLIs execute the same pipeline: Alex → Cass → Nigel → Codey. The skill uses each CLI's native agent/task mechanism.
396
+
366
397
  ## Token Efficiency (v2.7)
367
398
 
368
399
  Version 2.7 introduces several optimizations to reduce token usage:
package/bin/cli.js CHANGED
@@ -4,7 +4,7 @@ const { init } = require('../src/init');
4
4
  const { update } = require('../src/update');
5
5
  const { displayQueue, resetQueue } = require('../src/orchestrator');
6
6
  const { validate, formatOutput } = require('../src/validate');
7
- const { displayHistory, showStats, clearHistory } = require('../src/history');
7
+ const { displayHistory, showStats, clearHistory, exportHistory } = require('../src/history');
8
8
  const { displayInsights } = require('../src/insights');
9
9
  const { displayConfig, setConfigValue, resetConfig } = require('../src/retry');
10
10
  const {
@@ -87,6 +87,26 @@ const commands = {
87
87
  const flags = parseFlags(args);
88
88
  if (subArg === 'clear') {
89
89
  await clearHistory({ force: flags.force });
90
+ } else if (subArg === 'export') {
91
+ const exportOpts = {};
92
+ for (const arg of args) {
93
+ if (arg.startsWith('--format=')) exportOpts.format = arg.split('=')[1];
94
+ if (arg.startsWith('--since=')) exportOpts.since = arg.split('=')[1];
95
+ if (arg.startsWith('--until=')) exportOpts.until = arg.split('=')[1];
96
+ if (arg.startsWith('--status=')) exportOpts.status = arg.split('=')[1];
97
+ if (arg.startsWith('--feature=')) exportOpts.feature = arg.split('=')[1];
98
+ if (arg.startsWith('--output=')) exportOpts.output = arg.split('=')[1];
99
+ }
100
+ const result = await exportHistory(exportOpts);
101
+ if (result.exitCode) {
102
+ console.error(`Error: ${result.error}`);
103
+ process.exit(result.exitCode);
104
+ }
105
+ if (result.message) {
106
+ console.log(result.message);
107
+ } else if (result.output) {
108
+ console.log(result.output);
109
+ }
90
110
  } else if (flags.stats) {
91
111
  showStats();
92
112
  } else {
@@ -339,6 +359,13 @@ Commands:
339
359
  history --stats View aggregate statistics
340
360
  history clear Clear all pipeline history (with confirmation)
341
361
  history clear --force Clear all pipeline history (no confirmation)
362
+ history export Export history as CSV (default) or JSON
363
+ history export --format=json Export as JSON
364
+ history export --since=YYYY-MM-DD Filter entries on or after date
365
+ history export --until=YYYY-MM-DD Filter entries on or before date
366
+ history export --status=<status> Filter by status (success|failed|paused)
367
+ history export --feature=<slug> Filter by feature slug
368
+ history export --output=<path> Write to file instead of stdout
342
369
  insights Analyze pipeline for bottlenecks, failures, and trends
343
370
  insights --bottlenecks Show only bottleneck analysis
344
371
  insights --failures Show only failure patterns
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "murmur8",
3
- "version": "4.0.1",
3
+ "version": "4.2.0",
4
4
  "description": "Multi-agent workflow framework for automated feature development",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  "ai",
16
16
  "automation",
17
17
  "claude",
18
+ "copilot",
18
19
  "feature-development"
19
20
  ],
20
21
  "author": "NewmanJustice",
package/src/history.js CHANGED
@@ -100,6 +100,96 @@ function formatDuration(ms) {
100
100
  return `${minutes}m ${secs}s`;
101
101
  }
102
102
 
103
+ function isValidDateFormat(dateStr) {
104
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return false;
105
+ const date = new Date(dateStr);
106
+ return !isNaN(date.getTime());
107
+ }
108
+
109
+ function escapeCSVField(value) {
110
+ if (value === null || value === undefined) return '';
111
+ const str = String(value);
112
+ if (str.includes(',') || str.includes('"') || str.includes('\n')) {
113
+ return `"${str.replace(/"/g, '""')}"`;
114
+ }
115
+ return str;
116
+ }
117
+
118
+ function formatCSV(entries) {
119
+ const headers = ['slug', 'status', 'startedAt', 'completedAt', 'totalDurationMs', 'failedStage', 'pausedAfter'];
120
+ const lines = [headers.join(',')];
121
+ for (const entry of entries) {
122
+ const row = headers.map(h => escapeCSVField(entry[h]));
123
+ lines.push(row.join(','));
124
+ }
125
+ return lines.join('\n');
126
+ }
127
+
128
+ function formatJSON(entries) {
129
+ return JSON.stringify(entries, null, 2);
130
+ }
131
+
132
+ async function exportHistory(options = {}) {
133
+ const { format = 'csv', since, until, status, feature, output } = options;
134
+
135
+ const history = readHistoryFile();
136
+
137
+ if (history.error === 'corrupted') {
138
+ return { exitCode: 1, error: "History file is corrupted. Run 'murmur8 history clear' to reset." };
139
+ }
140
+
141
+ if (since && !isValidDateFormat(since)) {
142
+ return { exitCode: 1, error: 'Invalid --since format. Use YYYY-MM-DD.' };
143
+ }
144
+
145
+ if (until && !isValidDateFormat(until)) {
146
+ return { exitCode: 1, error: 'Invalid --until format. Use YYYY-MM-DD.' };
147
+ }
148
+
149
+ const validStatuses = ['success', 'failed', 'paused'];
150
+ if (status && !validStatuses.includes(status)) {
151
+ return { exitCode: 1, error: `Invalid --status value. Use: success, failed, paused.` };
152
+ }
153
+
154
+ let filtered = history;
155
+
156
+ if (since) {
157
+ const sinceDate = new Date(since);
158
+ filtered = filtered.filter(e => e.completedAt && new Date(e.completedAt) >= sinceDate);
159
+ }
160
+
161
+ if (until) {
162
+ const untilDate = new Date(until);
163
+ untilDate.setDate(untilDate.getDate() + 1);
164
+ filtered = filtered.filter(e => e.completedAt && new Date(e.completedAt) < untilDate);
165
+ }
166
+
167
+ if (status) {
168
+ filtered = filtered.filter(e => e.status === status);
169
+ }
170
+
171
+ if (feature) {
172
+ filtered = filtered.filter(e => e.slug === feature);
173
+ }
174
+
175
+ const formatted = format === 'json' ? formatJSON(filtered) : formatCSV(filtered);
176
+
177
+ if (output) {
178
+ try {
179
+ const dir = path.dirname(output);
180
+ if (!fs.existsSync(dir)) {
181
+ fs.mkdirSync(dir, { recursive: true });
182
+ }
183
+ fs.writeFileSync(output, formatted);
184
+ return { message: `Exported ${filtered.length} entries to ${output}` };
185
+ } catch (err) {
186
+ return { exitCode: 1, error: err.message };
187
+ }
188
+ }
189
+
190
+ return { output: formatted };
191
+ }
192
+
103
193
  function formatDate(isoString) {
104
194
  const date = new Date(isoString);
105
195
  return date.toISOString().replace('T', ' ').slice(0, 19);
@@ -292,5 +382,6 @@ module.exports = {
292
382
  displayHistory,
293
383
  showStats,
294
384
  clearHistory,
295
- formatDuration
385
+ formatDuration,
386
+ exportHistory
296
387
  };
package/src/init.js CHANGED
@@ -41,7 +41,7 @@ function copyDir(src, dest) {
41
41
  function updateGitignore() {
42
42
  const gitignorePath = path.join(TARGET_DIR, '.gitignore');
43
43
  const entriesToAdd = [
44
- '# agent-workflow',
44
+ '# murmur8',
45
45
  '.claude/implement-queue.json',
46
46
  '.claude/pipeline-history.json',
47
47
  '.claude/stack-config.json'
@@ -61,6 +61,40 @@ function updateGitignore() {
61
61
  }
62
62
  }
63
63
 
64
+ /**
65
+ * Create symlink for Copilot CLI
66
+ * Links .github/skills/implement-feature/SKILL.md -> .claude/commands/implement-feature.md
67
+ */
68
+ function createCopilotSymlink() {
69
+ const copilotSkillDir = path.join(TARGET_DIR, '.github', 'skills', 'implement-feature');
70
+ const copilotSkillPath = path.join(copilotSkillDir, 'SKILL.md');
71
+ const claudeSkillPath = path.join(TARGET_DIR, '.claude', 'commands', 'implement-feature.md');
72
+
73
+ // Relative path from .github/skills/implement-feature/ to .claude/commands/
74
+ const relativePath = path.relative(copilotSkillDir, claudeSkillPath);
75
+
76
+ // Create directory
77
+ fs.mkdirSync(copilotSkillDir, { recursive: true });
78
+
79
+ // Remove existing symlink or file
80
+ if (fs.existsSync(copilotSkillPath)) {
81
+ fs.unlinkSync(copilotSkillPath);
82
+ }
83
+
84
+ // Create symlink
85
+ try {
86
+ fs.symlinkSync(relativePath, copilotSkillPath);
87
+ console.log('Created Copilot CLI symlink at .github/skills/implement-feature/SKILL.md');
88
+ return true;
89
+ } catch (err) {
90
+ console.warn(`Warning: Could not create symlink: ${err.message}`);
91
+ // Fallback: copy the file instead
92
+ fs.copyFileSync(claudeSkillPath, copilotSkillPath);
93
+ console.log('Copied skill to .github/skills/implement-feature/SKILL.md (symlink failed)');
94
+ return false;
95
+ }
96
+ }
97
+
64
98
  async function init() {
65
99
  const blueprintSrc = path.join(PACKAGE_ROOT, '.blueprint');
66
100
  const blueprintDest = path.join(TARGET_DIR, '.blueprint');
@@ -74,25 +108,27 @@ async function init() {
74
108
  if (fs.existsSync(blueprintDest)) {
75
109
  const answer = await prompt('.blueprint directory already exists. Overwrite? (y/N): ');
76
110
  if (answer !== 'y' && answer !== 'yes') {
77
- console.log('Aborted. Use "agent-workflow update" to update existing installation.');
111
+ console.log('Aborted. Use "murmur8 update" to update existing installation.');
78
112
  return;
79
113
  }
80
114
  fs.rmSync(blueprintDest, { recursive: true });
81
115
  }
82
116
 
83
- // Copy skill to .claude/commands/ for Claude Code discovery
117
+ // Copy skill to .claude/commands/ (master location)
84
118
  fs.mkdirSync(claudeCommandsDir, { recursive: true });
85
119
  if (fs.existsSync(skillCommandDest)) {
86
120
  const answer = await prompt('.claude/commands/implement-feature.md already exists. Overwrite? (y/N): ');
87
121
  if (answer !== 'y' && answer !== 'yes') {
88
- console.log('Skipping skill command');
122
+ console.log('Skipping skill installation');
89
123
  } else {
90
124
  fs.copyFileSync(skillSrc, skillCommandDest);
91
125
  console.log('Copied skill to .claude/commands/implement-feature.md');
126
+ createCopilotSymlink();
92
127
  }
93
128
  } else {
94
129
  fs.copyFileSync(skillSrc, skillCommandDest);
95
130
  console.log('Copied skill to .claude/commands/implement-feature.md');
131
+ createCopilotSymlink();
96
132
  }
97
133
 
98
134
  // Copy .blueprint directory
@@ -132,7 +168,7 @@ murmur8 initialized successfully!
132
168
  Next steps:
133
169
  1. Review your tech stack with \`npx murmur8 stack-config\`
134
170
  2. Add business context documents to .business_context/
135
- 3. Run /implement-feature in Claude Code to start your first feature
171
+ 3. Run /implement-feature in Claude Code or Copilot CLI to start your first feature
136
172
  `);
137
173
  }
138
174