sequant 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +18 -2
  4. package/dist/bin/cli.d.ts +1 -1
  5. package/dist/bin/cli.js +12 -2
  6. package/dist/bin/preflight.d.ts +21 -0
  7. package/dist/bin/preflight.js +45 -0
  8. package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
  9. package/dist/marketplace/external_plugins/sequant/README.md +2 -0
  10. package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +18 -3
  11. package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +330 -57
  12. package/dist/marketplace/external_plugins/sequant/skills/_shared/references/force-push.md +34 -0
  13. package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +117 -19
  14. package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +9 -6
  15. package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +29 -0
  16. package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +1 -1
  17. package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +100 -2
  18. package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +24 -0
  19. package/dist/marketplace/external_plugins/sequant/skills/qa/references/anti-pattern-detection.md +285 -0
  20. package/dist/marketplace/external_plugins/sequant/skills/qa/references/call-site-review.md +202 -0
  21. package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +287 -0
  22. package/dist/marketplace/external_plugins/sequant/skills/qa/references/test-quality-checklist.md +272 -0
  23. package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +40 -0
  24. package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +95 -11
  25. package/dist/marketplace/external_plugins/sequant/skills/references/shared/framework-gotchas.md +186 -0
  26. package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +27 -13
  27. package/dist/marketplace/external_plugins/sequant/skills/reflect/references/documentation-tiers.md +80 -68
  28. package/dist/marketplace/external_plugins/sequant/skills/reflect/references/phase-reflection.md +31 -15
  29. package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +669 -0
  30. package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +1 -1
  31. package/dist/marketplace/external_plugins/sequant/skills/test/references/browser-testing-patterns.md +423 -0
  32. package/dist/marketplace/external_plugins/sequant/skills/upstream/SKILL.md +419 -0
  33. package/dist/src/commands/logs.js +6 -1
  34. package/dist/src/commands/run-display.d.ts +20 -0
  35. package/dist/src/commands/run-display.js +80 -1
  36. package/dist/src/commands/stats.js +47 -0
  37. package/dist/src/lib/assess-collision-detect.d.ts +19 -2
  38. package/dist/src/lib/assess-collision-detect.js +68 -4
  39. package/dist/src/lib/cli-ui/run-renderer.js +17 -9
  40. package/dist/src/lib/errors.d.ts +91 -0
  41. package/dist/src/lib/errors.js +118 -0
  42. package/dist/src/lib/manifest.js +1 -17
  43. package/dist/src/lib/version-check.d.ts +19 -0
  44. package/dist/src/lib/version-check.js +45 -5
  45. package/dist/src/lib/workflow/batch-executor.d.ts +13 -0
  46. package/dist/src/lib/workflow/batch-executor.js +142 -24
  47. package/dist/src/lib/workflow/chain-preflight.d.ts +89 -0
  48. package/dist/src/lib/workflow/chain-preflight.js +199 -0
  49. package/dist/src/lib/workflow/chain-resume.d.ts +116 -0
  50. package/dist/src/lib/workflow/chain-resume.js +166 -0
  51. package/dist/src/lib/workflow/dependency-markers.d.ts +29 -0
  52. package/dist/src/lib/workflow/dependency-markers.js +79 -0
  53. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +17 -0
  54. package/dist/src/lib/workflow/drivers/claude-code.d.ts +29 -0
  55. package/dist/src/lib/workflow/drivers/claude-code.js +136 -8
  56. package/dist/src/lib/workflow/error-classifier.d.ts +9 -2
  57. package/dist/src/lib/workflow/error-classifier.js +14 -1
  58. package/dist/src/lib/workflow/log-writer.d.ts +1 -1
  59. package/dist/src/lib/workflow/log-writer.js +6 -8
  60. package/dist/src/lib/workflow/metrics-schema.d.ts +39 -0
  61. package/dist/src/lib/workflow/metrics-schema.js +16 -0
  62. package/dist/src/lib/workflow/metrics-writer.d.ts +2 -1
  63. package/dist/src/lib/workflow/phase-executor.d.ts +50 -0
  64. package/dist/src/lib/workflow/phase-executor.js +151 -17
  65. package/dist/src/lib/workflow/run-log-schema.d.ts +26 -0
  66. package/dist/src/lib/workflow/run-log-schema.js +52 -1
  67. package/dist/src/lib/workflow/run-orchestrator.d.ts +14 -0
  68. package/dist/src/lib/workflow/run-orchestrator.js +291 -30
  69. package/dist/src/lib/workflow/state-manager.d.ts +1 -0
  70. package/dist/src/lib/workflow/state-manager.js +6 -0
  71. package/dist/src/lib/workflow/state-schema.d.ts +3 -0
  72. package/dist/src/lib/workflow/state-schema.js +7 -0
  73. package/dist/src/lib/workflow/status-derivation.d.ts +30 -0
  74. package/dist/src/lib/workflow/status-derivation.js +27 -0
  75. package/dist/src/lib/workflow/types.d.ts +40 -0
  76. package/dist/src/lib/workflow/worktree-manager.d.ts +43 -1
  77. package/dist/src/lib/workflow/worktree-manager.js +103 -33
  78. package/dist/src/mcp/tools/run.d.ts +2 -0
  79. package/dist/src/mcp/tools/run.js +2 -0
  80. package/dist/src/ui/tui/theme.d.ts +18 -4
  81. package/dist/src/ui/tui/theme.js +18 -4
  82. package/package.json +5 -6
  83. package/templates/hooks/post-tool.sh +18 -3
  84. package/templates/hooks/pre-tool.sh +330 -57
  85. package/templates/scripts/cleanup-worktree.sh +103 -14
  86. package/templates/skills/_shared/references/force-push.md +34 -0
  87. package/templates/skills/assess/SKILL.md +117 -19
  88. package/templates/skills/assess/references/predicted-collision-detection.md +9 -6
  89. package/templates/skills/exec/SKILL.md +29 -0
  90. package/templates/skills/fullsolve/SKILL.md +1 -1
  91. package/templates/skills/loop/SKILL.md +100 -2
  92. package/templates/skills/qa/SKILL.md +24 -0
  93. package/templates/skills/qa/references/anti-pattern-detection.md +285 -0
  94. package/templates/skills/qa/references/call-site-review.md +202 -0
  95. package/templates/skills/qa/references/quality-gates.md +287 -0
  96. package/templates/skills/qa/references/test-quality-checklist.md +272 -0
  97. package/templates/skills/qa/references/testing-requirements.md +40 -0
  98. package/templates/skills/qa/scripts/quality-checks.sh +95 -11
  99. package/templates/skills/references/shared/framework-gotchas.md +186 -0
  100. package/templates/skills/reflect/SKILL.md +27 -13
  101. package/templates/skills/reflect/references/documentation-tiers.md +80 -68
  102. package/templates/skills/reflect/references/phase-reflection.md +31 -15
  103. package/templates/skills/release/SKILL.md +669 -0
  104. package/templates/skills/spec/references/verification-criteria.md +1 -1
  105. package/templates/skills/test/references/browser-testing-patterns.md +423 -0
  106. package/templates/skills/upstream/SKILL.md +419 -0
@@ -0,0 +1,419 @@
1
+ ---
2
+ name: upstream
3
+ description: "Monitor Claude Code releases, assess compatibility with sequant, and create issues for breaking changes and deprecations. Opportunities are noted in assessment reports for human triage."
4
+ license: MIT
5
+ metadata:
6
+ author: sequant
7
+ version: "1.0"
8
+ allowed-tools:
9
+ - Read
10
+ - Write
11
+ - Glob
12
+ - Grep
13
+ - Bash(gh *)
14
+ - Bash(git *)
15
+ - Bash(jq *)
16
+ - Bash(base64 *)
17
+ - Bash(npx tsx *)
18
+ ---
19
+
20
+ <!-- sequant:local-override -->
21
+ > **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/upstream/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/upstream` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
22
+
23
+ # Upstream: Claude Code Release Tracking
24
+
25
+ You are the "Upstream Assessment Agent" for the sequant repository.
26
+
27
+ ## Purpose
28
+
29
+ When invoked as `/upstream`, your job is to:
30
+
31
+ 1. Fetch Claude Code release information from the public GitHub repo
32
+ 2. Analyze changes against sequant's current capabilities baseline
33
+ 3. Detect relevant changes using keyword matching and regex patterns
34
+ 4. Skip out-of-scope changes (configured in baseline.json `outOfScope`)
35
+ 5. Generate a structured compatibility assessment report with Actionable and Informational sections
36
+ 6. Auto-create GitHub issues for breaking changes, deprecations, new tools, and hook changes
37
+ 7. List opportunities in the assessment report for human triage (no individual issues created)
38
+
39
+ ## Invocation
40
+
41
+ ```bash
42
+ # Analyze latest release
43
+ /upstream
44
+
45
+ # Analyze specific version
46
+ /upstream v2.1.29
47
+
48
+ # Analyze all releases since version
49
+ /upstream --since v2.1.25
50
+
51
+ # Dry-run mode (no issues created)
52
+ /upstream --dry-run
53
+
54
+ # Help
55
+ /upstream --help
56
+ ```
57
+
58
+ ## Assessment Process
59
+
60
+ ### 1. Parse Arguments
61
+
62
+ Parse the command arguments to determine:
63
+
64
+ - **Target version**: Specific version (e.g., `v2.1.29`) or `latest`
65
+ - **Since version**: If `--since` flag provided, assess all versions since that release
66
+ - **Dry-run mode**: If `--dry-run` flag, generate report but skip issue creation
67
+ - **Help**: If `--help` flag, show usage instructions
68
+
69
+ ### 2. Fetch Release Data
70
+
71
+ Fetch release information from the public Claude Code repository:
72
+
73
+ ```bash
74
+ # Get latest release
75
+ gh release view --repo anthropics/claude-code --json tagName,name,body,publishedAt
76
+
77
+ # Get specific version
78
+ gh release view v2.1.29 --repo anthropics/claude-code --json tagName,name,body,publishedAt
79
+
80
+ # List releases for --since support
81
+ gh release list --repo anthropics/claude-code --limit 50 --json tagName,publishedAt
82
+ ```
83
+
84
+ ### 3. Load Baseline
85
+
86
+ Load the sequant capabilities baseline from `.sequant/upstream/baseline.json`:
87
+
88
+ ```json
89
+ {
90
+ "lastAssessedVersion": "v2.1.25",
91
+ "tools": {
92
+ "core": ["Task", "Bash", "Read", "Write", "Edit", "Glob", "Grep"],
93
+ "optional": ["WebFetch", "WebSearch", "NotebookEdit"]
94
+ },
95
+ "hooks": {
96
+ "used": ["PreToolUse"],
97
+ "files": ["src/hooks/pre-tool-hook.ts"]
98
+ },
99
+ "mcpServers": {
100
+ "required": [],
101
+ "optional": ["chrome-devtools", "context7", "sequential-thinking"]
102
+ },
103
+ "keywords": [
104
+ "Task", "Bash", "hook", "PreToolUse", "PostToolUse",
105
+ "MCP", "permission", "allow", "deny", "tool",
106
+ "background", "parallel", "agent", "subagent",
107
+ "settings", "config", "plugin"
108
+ ],
109
+ "dependencyMap": {
110
+ "permission": ["src/hooks/pre-tool-hook.ts", ".claude/settings.json"],
111
+ "hook": ["src/hooks/pre-tool-hook.ts"],
112
+ "Task": [".claude/skills/**/*.md", "src/lib/workflow/*.ts"],
113
+ "MCP": ["docs/mcp-integrations.md", ".claude/settings.json"]
114
+ },
115
+ "outOfScope": [
116
+ "PDF/document processing - users work with code and GitHub issues",
117
+ "Slack/OAuth integrations - workflow is GitHub-centric",
118
+ "Notebook editing - not a data science tool",
119
+ "IDE-specific features (VSCode, JetBrains) - sequant is CLI/terminal focused",
120
+ "Windows-specific fixes - sequant targets macOS/Linux"
121
+ ]
122
+ }
123
+ ```
124
+
125
+ ### 4. Analyze Changes
126
+
127
+ For each release, analyze the changelog/release body:
128
+
129
+ **Step 1: Extract Changes**
130
+
131
+ Parse the release body to extract individual change items. Common formats:
132
+ - Bullet points: `- Added new feature X`
133
+ - "What's changed" sections
134
+ - BREAKING CHANGE markers
135
+
136
+ **Step 2: Relevance Detection**
137
+
138
+ For each change, check if it's relevant to sequant:
139
+
140
+ ```typescript
141
+ // Keyword matching
142
+ const isRelevant = baseline.keywords.some(kw =>
143
+ change.toLowerCase().includes(kw.toLowerCase())
144
+ );
145
+
146
+ // Pattern matching
147
+ const patterns = {
148
+ newTool: /added.*tool|new.*tool|introducing/i,
149
+ deprecation: /deprecat|remov|no longer support/i,
150
+ breaking: /breaking|incompatible|must update/i,
151
+ hook: /hook|PreToolUse|PostToolUse/i,
152
+ permission: /permission|allow|deny|ask/i,
153
+ };
154
+ ```
155
+
156
+ **Step 3: Categorize**
157
+
158
+ Categorize each relevant change:
159
+
160
+ | Category | Detection Pattern | Issue Labels |
161
+ |----------|------------------|--------------|
162
+ | `breaking` | Breaking, incompatible, must update | `upstream`, `bug`, `priority:high` |
163
+ | `deprecation` | Deprecated, removed, no longer supported | `upstream`, `bug` |
164
+ | `new-tool` | Added tool, new tool, introducing | `upstream`, `enhancement` |
165
+ | `hook-change` | Hook, PreToolUse, PostToolUse | `upstream`, `enhancement` |
166
+ | `opportunity` | Keywords match but not above categories | (no issue — noted in assessment for human triage) |
167
+ | `no-action` | Doesn't match patterns or keywords | (no issue) |
168
+
169
+ **Step 4: Impact Mapping**
170
+
171
+ For relevant changes, map to affected sequant files using `dependencyMap`:
172
+
173
+ ```typescript
174
+ const impactFiles = baseline.dependencyMap[matchedKeyword] || [];
175
+ ```
176
+
177
+ ### 5. Check for Duplicates
178
+
179
+ Before creating issues, check for existing upstream issues:
180
+
181
+ ```bash
182
+ # Search for similar issues
183
+ gh issue list --label upstream --search "<finding-title>" --json number,title
184
+ ```
185
+
186
+ If a similar issue exists:
187
+ - Add a comment linking to the new assessment
188
+ - Skip creating a duplicate
189
+
190
+ ### 6. Generate Outputs
191
+
192
+ **Output 1: Assessment Report (GitHub Issue)**
193
+
194
+ Create a summary issue with the full assessment:
195
+
196
+ ```markdown
197
+ ## Upstream: Claude Code <version> Assessment
198
+
199
+ **Release:** [<version>](https://github.com/anthropics/claude-code/releases/tag/<version>)
200
+ **Released:** <date>
201
+ **Assessed:** <today>
202
+
203
+ ### Summary
204
+
205
+ | Category | Count | Action Required |
206
+ |----------|-------|-----------------|
207
+ | Breaking Changes | N | [status] |
208
+ | New Tools | N | [status] |
209
+ | Deprecations | N | [status] |
210
+ | Opportunities | N | [status] |
211
+
212
+ ### Actionable
213
+
214
+ *Breaking changes, deprecations, and other items that affect sequant.*
215
+
216
+ [list of breaking, deprecation, new-tool, hook-change findings]
217
+
218
+ ### Informational
219
+
220
+ *Opportunities noted for human triage. No individual issues auto-created.*
221
+
222
+ [list of opportunity findings]
223
+
224
+ ### No Action Required
225
+
226
+ [list of irrelevant changes]
227
+
228
+ ---
229
+
230
+ *Generated by /upstream skill*
231
+ ```
232
+
233
+ **Output 2: Individual Issues (Actionable Findings)**
234
+
235
+ For each actionable finding (breaking, deprecation, new-tool, hook-change), create an issue.
236
+ **Note:** Opportunities do NOT get individual issues — they are listed in the assessment report's Informational section for human triage.
237
+
238
+ ```markdown
239
+ ## feat: Leverage <feature> from Claude Code <version>
240
+
241
+ **Upstream:** Claude Code <version>
242
+ **Category:** <category>
243
+ **Assessment:** #<assessment-issue>
244
+
245
+ ### Context
246
+
247
+ <description from release notes>
248
+
249
+ ### Opportunity
250
+
251
+ <how sequant could use this>
252
+
253
+ ### Proposed Implementation
254
+
255
+ [To be determined during /spec phase]
256
+
257
+ ### Acceptance Criteria
258
+
259
+ - [ ] AC-1: [To be defined]
260
+
261
+ ---
262
+
263
+ *Auto-created by /upstream assessment #<N>*
264
+ ```
265
+
266
+ Labels: `upstream`, `needs-triage`, `enhancement`
267
+
268
+ **Output 3: Local Report**
269
+
270
+ Save to `.sequant/upstream/<version>.md`:
271
+
272
+ ```markdown
273
+ # Claude Code <version> Assessment
274
+
275
+ Assessed: <date>
276
+ Previous: <last-assessed-version>
277
+
278
+ ## Summary
279
+
280
+ [same as GitHub issue]
281
+
282
+ ## Raw Findings
283
+
284
+ [detailed analysis data]
285
+ ```
286
+
287
+ **Output 4: Update Baseline**
288
+
289
+ Update `.sequant/upstream/baseline.json`:
290
+ - Set `lastAssessedVersion` to the assessed version
291
+
292
+ ### 7. Multi-Version Batching
293
+
294
+ When `--since <version>` is used:
295
+
296
+ 1. List all releases after the specified version
297
+ 2. Assess each version individually
298
+ 3. Create a batched summary issue linking all individual assessments
299
+ 4. Update baseline to latest assessed version
300
+
301
+ ## Dry-Run Mode
302
+
303
+ When `--dry-run` is specified:
304
+
305
+ 1. Perform full analysis
306
+ 2. Generate local report
307
+ 3. Output what issues WOULD be created (titles, labels)
308
+ 4. Skip actual GitHub issue creation
309
+ 5. Skip baseline update
310
+
311
+ ## Error Handling
312
+
313
+ - **No releases found**: Exit with clear message
314
+ - **Baseline missing**: Create default baseline, warn user
315
+ - **GitHub API errors**: Retry with backoff, then fail gracefully
316
+ - **Already assessed**: Skip with message (idempotent)
317
+
318
+ ## Output Verification
319
+
320
+ **Before completing, verify:**
321
+
322
+ - [ ] Release data successfully fetched
323
+ - [ ] Baseline loaded (or created with defaults)
324
+ - [ ] Each change categorized
325
+ - [ ] Duplicates checked before issue creation
326
+ - [ ] Assessment report created (or dry-run output shown)
327
+ - [ ] Individual issues created for actionable findings (not opportunities)
328
+ - [ ] Local report saved
329
+ - [ ] Baseline updated with new version
330
+
331
+ ## Examples
332
+
333
+ ### Example 1: Assess Latest Release
334
+
335
+ ```
336
+ /upstream
337
+
338
+ Fetching latest Claude Code release...
339
+ Release: v2.1.29 (2025-01-31)
340
+
341
+ Loading baseline from .sequant/upstream/baseline.json...
342
+ Last assessed: v2.1.27
343
+
344
+ Analyzing 12 changes from release notes...
345
+ - 3 relevant changes detected
346
+ - 9 no-action changes
347
+
348
+ Findings:
349
+ 1. [opportunity] New --background flag on Task tool
350
+ Matched keywords: Task, background
351
+ Impact files: .claude/skills/**/*.md
352
+
353
+ 2. [hook-change] Permissions now respect content-level ask
354
+ Matched keywords: permission
355
+ Impact files: src/hooks/pre-tool-hook.ts
356
+
357
+ 3. [deprecation] oldHookName deprecated
358
+ Matched pattern: deprecat
359
+ Impact files: src/hooks/pre-tool-hook.ts
360
+
361
+ Creating assessment issue...
362
+ Created: #250 - Upstream: Claude Code v2.1.29 Assessment
363
+
364
+ Creating individual issues...
365
+ Created: #251 - feat: Leverage new --background flag from Claude Code v2.1.29
366
+ Created: #252 - chore: Update to new hook name (deprecation from v2.1.29)
367
+
368
+ Saving local report to .sequant/upstream/v2.1.29.md...
369
+ Updating baseline lastAssessedVersion to v2.1.29...
370
+
371
+ Done! Assessment complete.
372
+ ```
373
+
374
+ ### Example 2: Dry Run
375
+
376
+ ```
377
+ /upstream --dry-run
378
+
379
+ [DRY RUN MODE - No issues will be created]
380
+
381
+ Fetching latest Claude Code release...
382
+ Release: v2.1.29 (2025-01-31)
383
+
384
+ ...analysis...
385
+
386
+ Would create issues:
387
+ 1. Assessment: Upstream: Claude Code v2.1.29 Assessment
388
+ 2. Finding: feat: Leverage new --background flag from Claude Code v2.1.29
389
+ 3. Finding: chore: Update to new hook name (deprecation from v2.1.29)
390
+
391
+ Local report saved: .sequant/upstream/v2.1.29.md
392
+ Baseline NOT updated (dry-run mode)
393
+ ```
394
+
395
+ ### Example 3: Already Assessed
396
+
397
+ ```
398
+ /upstream
399
+
400
+ Fetching latest Claude Code release...
401
+ Release: v2.1.29 (2025-01-31)
402
+
403
+ Already assessed: .sequant/upstream/v2.1.29.md exists
404
+ Use --force to re-assess.
405
+
406
+ No action taken.
407
+ ```
408
+
409
+ ## Notes
410
+
411
+ - This is an internal tool for sequant maintainers
412
+ - All created issues get `needs-triage` label for human review
413
+ - The skill is read-only for Claude Code repo (no PRs or edits)
414
+ - Baseline file should be committed to version control
415
+ - Local reports in `.sequant/upstream/` should be committed
416
+
417
+ ---
418
+
419
+ *This skill monitors the upstream Claude Code project to help sequant stay current with new features and breaking changes.*
@@ -81,6 +81,9 @@ function formatTime(isoString) {
81
81
  function displayLogSummary(log, filename, options) {
82
82
  const passed = log.summary.passed;
83
83
  const failed = log.summary.failed;
84
+ // #766: `partial` gets its own bucket so an all-partial run isn't rendered as
85
+ // `0 passed, 0 failed` (older logs predate the field → default to 0).
86
+ const partial = log.summary.partial ?? 0;
84
87
  const total = log.summary.totalIssues;
85
88
  const status = failed > 0
86
89
  ? chalk.red("FAILED")
@@ -91,7 +94,9 @@ function displayLogSummary(log, filename, options) {
91
94
  console.log(chalk.gray(` File: ${filename}`));
92
95
  console.log(chalk.gray(` Time: ${formatTime(log.startTime)}`));
93
96
  console.log(chalk.gray(` Duration: ${formatDuration(log.summary.totalDurationSeconds)}`));
94
- console.log(chalk.gray(` Status: ${status} (${passed}/${total} passed, ${failed} failed)`));
97
+ console.log(chalk.gray(` Status: ${status} (${passed}/${total} passed, ${failed} failed` +
98
+ (partial > 0 ? `, ${partial} partial` : "") +
99
+ `)`));
95
100
  console.log(chalk.gray(` Phases: ${log.config.phases.join(" → ")}`));
96
101
  // Show issues
97
102
  for (const issue of log.issues) {
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import type { RunRenderer } from "../lib/cli-ui/run-renderer-types.js";
11
11
  import type { ResolvedRun, RunResult } from "../lib/workflow/run-orchestrator.js";
12
+ import type { IssueResult } from "../lib/workflow/types.js";
12
13
  /**
13
14
  * Print pre-run config block.
14
15
  *
@@ -16,6 +17,25 @@ import type { ResolvedRun, RunResult } from "../lib/workflow/run-orchestrator.js
16
17
  * appear when non-default, matching the pre-#503 format.
17
18
  */
18
19
  export declare function displayConfig(r: ResolvedRun): void;
20
+ /**
21
+ * Detect a chain halted by a rate-limit/billing failure and build the summary
22
+ * notice for it (#761 AC-5). Returns null when the run wasn't a chain, no
23
+ * issue failed, or the halting failure wasn't rate-limit-classified.
24
+ *
25
+ * Extracted from `displaySummary` so the halt-and-print decision is testable
26
+ * standalone — the same treatment #760 gave `planChainResumeFromState` when it
27
+ * hit the executeSequential testability wall.
28
+ *
29
+ * The failing phase is found with the same reverse non-loop scan as
30
+ * `toIssueSummary` (#766): the classification must describe the LAST attempt,
31
+ * not a stale first-iteration failure.
32
+ *
33
+ * @internal Exported for testing
34
+ */
35
+ export declare function buildRateLimitHaltNotice(results: IssueResult[], chainEnabled: boolean): {
36
+ issueNumber: number;
37
+ label: string;
38
+ } | null;
19
39
  /**
20
40
  * Print post-run summary: per-issue grid, log path, reflection, tips.
21
41
  *
@@ -10,7 +10,9 @@
10
10
  import chalk from "chalk";
11
11
  import { ui, colors } from "../lib/cli-ui.js";
12
12
  import { renderRunSummary } from "../lib/cli-ui/run-renderer.js";
13
+ import { BillingError, RateLimitError, formatRateLimitMessage, isBillingFailure, } from "../lib/errors.js";
13
14
  import { analyzeRun, formatReflection } from "../lib/workflow/run-reflect.js";
15
+ import { LOOP_PHASE } from "../lib/workflow/status-derivation.js";
14
16
  /**
15
17
  * Print pre-run config block.
16
18
  *
@@ -67,7 +69,19 @@ export function displayConfig(r) {
67
69
  * Convert workflow `IssueResult` to renderer `IssueSummary`.
68
70
  */
69
71
  function toIssueSummary(r) {
70
- const failedPhase = r.phaseResults.find((p) => !p.success);
72
+ // #766: the reason to show is the LAST failing attempt, not the first.
73
+ // `phaseResults` accumulates every attempt across every quality-loop
74
+ // iteration, so `.find()` (first-wins) rendered a stale first-iteration
75
+ // reason: #762's cell read `Timeout after 1800s` when its real last failure
76
+ // was an API drop. `verdict`/`unmetCount` below hang off the same entry, so
77
+ // they were stale for the same reason. `loop` is excluded on the same grounds
78
+ // the card and log exclude it (see `status-derivation.ts`) — it is auxiliary
79
+ // recovery, and a trailing loop failure would mask the phase that actually
80
+ // failed. Reverse scan rather than `findLast`: tsconfig pins `lib: ES2022`
81
+ // and `findLast` is ES2023.
82
+ const failedPhase = [...r.phaseResults]
83
+ .reverse()
84
+ .find((p) => !p.success && p.phase !== LOOP_PHASE);
71
85
  const summary = {
72
86
  issueNumber: r.issueNumber,
73
87
  success: r.success,
@@ -91,6 +105,48 @@ function toIssueSummary(r) {
91
105
  }
92
106
  return summary;
93
107
  }
108
+ /**
109
+ * Detect a chain halted by a rate-limit/billing failure and build the summary
110
+ * notice for it (#761 AC-5). Returns null when the run wasn't a chain, no
111
+ * issue failed, or the halting failure wasn't rate-limit-classified.
112
+ *
113
+ * Extracted from `displaySummary` so the halt-and-print decision is testable
114
+ * standalone — the same treatment #760 gave `planChainResumeFromState` when it
115
+ * hit the executeSequential testability wall.
116
+ *
117
+ * The failing phase is found with the same reverse non-loop scan as
118
+ * `toIssueSummary` (#766): the classification must describe the LAST attempt,
119
+ * not a stale first-iteration failure.
120
+ *
121
+ * @internal Exported for testing
122
+ */
123
+ export function buildRateLimitHaltNotice(results, chainEnabled) {
124
+ if (!chainEnabled)
125
+ return null;
126
+ // Chain mode halts at the first failed link, so at most one failed issue
127
+ // exists; scan defensively anyway.
128
+ for (const r of results) {
129
+ if (r.success)
130
+ continue;
131
+ const failedPhase = [...r.phaseResults]
132
+ .reverse()
133
+ .find((p) => !p.success && p.phase !== LOOP_PHASE);
134
+ const err = failedPhase?.structuredError;
135
+ if (err instanceof RateLimitError || err instanceof BillingError) {
136
+ // Event-derived errors get their message from formatRateLimitMessage, so
137
+ // re-deriving from metadata is exact and includes resetsAt. But errors
138
+ // from the assistant-error channel carry no billing/reset metadata —
139
+ // re-deriving there would mislabel a BillingError as "Rate limited"
140
+ // (isBillingFailure({}) is false) — so fall back to the typed message
141
+ // when the metadata carries no signal.
142
+ const label = err.metadata.resetsAt !== undefined || isBillingFailure(err.metadata)
143
+ ? formatRateLimitMessage(err.metadata)
144
+ : err.message;
145
+ return { issueNumber: r.issueNumber, label };
146
+ }
147
+ }
148
+ return null;
149
+ }
94
150
  /**
95
151
  * Print post-run summary: per-issue grid, log path, reflection, tips.
96
152
  *
@@ -120,6 +176,29 @@ export function displaySummary(result, renderer) {
120
176
  dryRun: config.dryRun,
121
177
  });
122
178
  }
179
+ // #760: a chain link whose checkpoint commit failed keeps its own work but
180
+ // loses the recovery point resume depends on, and the per-issue warning has
181
+ // long scrolled past by now on a multi-hour chain. Restate it at the summary,
182
+ // where the user is actually looking, so the next run's fail-fast is expected.
183
+ const checkpointFailures = results.filter((r) => r.checkpointFailed);
184
+ if (checkpointFailures.length > 0) {
185
+ console.log(colors.warning(` ⚠️ Checkpoint commit failed for ${checkpointFailures
186
+ .map((r) => `#${r.issueNumber}`)
187
+ .join(", ")} — uncommitted work is missing from the feature branch.`));
188
+ console.log(colors.muted(" Resuming this chain will stop at that link until the work is committed (or use --force)."));
189
+ console.log("");
190
+ }
191
+ // #761: a chain halted by a rate limit already stopped at the right link,
192
+ // but the labeled cause has long scrolled past by now (same rationale as the
193
+ // #760 restatement above), and #760 added no resume flag — resume IS
194
+ // re-running the identical command. Say both explicitly, or the halt reads
195
+ // as a bug and the resume path stays undiscovered.
196
+ const rateLimitHalt = buildRateLimitHaltNotice(results, mergedOptions.chain === true);
197
+ if (rateLimitHalt) {
198
+ console.log(colors.warning(` ⚠️ ${rateLimitHalt.label} — chain halted at #${rateLimitHalt.issueNumber}.`));
199
+ console.log(colors.muted(` Re-run the same command to resume from #${rateLimitHalt.issueNumber} (no flag needed; completed links are skipped).`));
200
+ console.log("");
201
+ }
123
202
  if (mergedOptions.reflect && results.length > 0) {
124
203
  const reflection = analyzeRun({
125
204
  results,
@@ -263,6 +263,35 @@ function loadMetrics() {
263
263
  return null;
264
264
  }
265
265
  }
266
+ /**
267
+ * Compute the failure-category breakdown over runs that recorded a failure —
268
+ * outcome "failed" (every issue failed) or "partial" (at least one issue
269
+ * failed) (#783).
270
+ *
271
+ * NOTE — deviation from AC-1's literal "over failed runs" wording: partial runs
272
+ * also carry a failureCategory (recorded whenever >=1 issue fails) and are
273
+ * genuine signal for "what's killing my runs", so they are counted here by
274
+ * explicit user decision. Success runs never carry the field and are excluded.
275
+ *
276
+ * Runs without the field (pre-#761 records, or a partial whose failure was
277
+ * never categorized) are bucketed as "unclassified" — never dropped and never
278
+ * conflated with the "unknown" enum value.
279
+ *
280
+ * Pure function of the passed runs array, so any upstream cohort filtering is
281
+ * automatically respected (computed after filtering, not before).
282
+ */
283
+ function calculateFailureCategoryBreakdown(runs) {
284
+ const counts = new Map();
285
+ for (const run of runs) {
286
+ if (run.outcome === "success")
287
+ continue;
288
+ const category = run.failureCategory ?? "unclassified";
289
+ counts.set(category, (counts.get(category) ?? 0) + 1);
290
+ }
291
+ return [...counts.entries()]
292
+ .map(([category, count]) => ({ category, count }))
293
+ .sort((a, b) => b.count - a.count || a.category.localeCompare(b.category));
294
+ }
266
295
  /**
267
296
  * Calculate analytics from metrics
268
297
  */
@@ -274,6 +303,7 @@ function calculateMetricsAnalytics(metrics) {
274
303
  successCount: 0,
275
304
  partialCount: 0,
276
305
  failedCount: 0,
306
+ failureCategories: [],
277
307
  successRate: 0,
278
308
  avgTokensPerRun: 0,
279
309
  avgFilesChanged: 0,
@@ -294,6 +324,7 @@ function calculateMetricsAnalytics(metrics) {
294
324
  const successCount = runs.filter((r) => r.outcome === "success").length;
295
325
  const partialCount = runs.filter((r) => r.outcome === "partial").length;
296
326
  const failedCount = runs.filter((r) => r.outcome === "failed").length;
327
+ const failureCategories = calculateFailureCategoryBreakdown(runs);
297
328
  const successRate = (successCount / runs.length) * 100;
298
329
  const avgTokensPerRun = runs.reduce((sum, r) => sum + r.metrics.tokensUsed, 0) / runs.length;
299
330
  const avgFilesChanged = runs.reduce((sum, r) => sum + r.metrics.filesChanged, 0) / runs.length;
@@ -323,6 +354,7 @@ function calculateMetricsAnalytics(metrics) {
323
354
  successCount,
324
355
  partialCount,
325
356
  failedCount,
357
+ failureCategories,
326
358
  successRate,
327
359
  avgTokensPerRun,
328
360
  avgFilesChanged,
@@ -415,6 +447,19 @@ function displayMetricsAnalytics(analytics) {
415
447
  const failedRate = (analytics.failedCount / total) * 100;
416
448
  console.log(` ${colors.error("\u2717 Failed")} ${analytics.failedCount} (${failedRate.toFixed(0)}%) ${failedBar}`);
417
449
  }
450
+ // Failure-category breakdown (#783) \u2014 adjacent to the outcome bars above.
451
+ // Counts runs that recorded a failure (outcome "failed" or "partial"); hidden
452
+ // entirely when there are none (AC-3).
453
+ if (analytics.failureCategories.length > 0) {
454
+ const failureRunCount = analytics.failureCategories.reduce((sum, b) => sum + b.count, 0);
455
+ const runNoun = failureRunCount === 1 ? "run with a failure" : "runs with a failure";
456
+ console.log(ui.sectionHeader(`Failure Categories (${failureRunCount} ${runNoun})`));
457
+ const pad = Math.max(...analytics.failureCategories.map((b) => b.category.length));
458
+ for (const bucket of analytics.failureCategories) {
459
+ const pct = ((bucket.count / failureRunCount) * 100).toFixed(0);
460
+ console.log(` ${bucket.category.padEnd(pad)} ${bucket.count} (${pct}%)`);
461
+ }
462
+ }
418
463
  // Averages table
419
464
  console.log(ui.sectionHeader("Averages"));
420
465
  const avgData = {};
@@ -653,6 +698,8 @@ export async function statsCommand(options) {
653
698
  partialCount: analytics.partialCount,
654
699
  failedCount: analytics.failedCount,
655
700
  successRate: analytics.successRate,
701
+ // Failure-category breakdown over failed runs (#783 AC-5)
702
+ failureCategories: analytics.failureCategories,
656
703
  avgTokensPerRun: analytics.avgTokensPerRun,
657
704
  avgFilesChanged: analytics.avgFilesChanged,
658
705
  avgLinesAdded: analytics.avgLinesAdded,