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.
- package/.blueprint/agents/TEAM_MANIFESTO.md +1 -1
- package/.blueprint/features/feature_export-history/FEATURE_SPEC.md +215 -0
- package/.blueprint/features/feature_export-history/IMPLEMENTATION_PLAN.md +48 -0
- package/.blueprint/features/feature_export-history/story-basic-export.md +48 -0
- package/.blueprint/features/feature_export-history/story-date-filter.md +42 -0
- package/.blueprint/features/feature_export-history/story-feature-filter.md +42 -0
- package/.blueprint/features/feature_export-history/story-file-output.md +48 -0
- package/.blueprint/features/feature_export-history/story-status-filter.md +42 -0
- package/README.md +33 -2
- package/bin/cli.js +28 -1
- package/package.json +2 -1
- package/src/history.js +92 -1
- package/src/init.js +41 -5
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
'#
|
|
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 "
|
|
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/
|
|
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
|
|
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
|
|