@hecer/yoke 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +13 -13
- package/.codex-plugin/plugin.json +7 -7
- package/CHANGELOG.md +294 -288
- package/README.md +874 -874
- package/TODOS.md +5 -5
- package/agents/docs.toml +6 -6
- package/agents/implementer.toml +6 -6
- package/agents/reviewer.toml +6 -6
- package/agents/security.toml +6 -6
- package/bench/README.md +86 -86
- package/bench/RESULTS.md +35 -35
- package/bench/output-compaction.mjs +65 -65
- package/bench/result-schema.mjs +12 -12
- package/bench/results/claude-2026-07-27T18-03-26.json +50 -50
- package/bench/results/codex-unavailable-1785175418318.json +15 -15
- package/bench/results/gemini-2026-07-27T18-03-44.json +46 -46
- package/bench/run-matrix.mjs +26 -26
- package/bench/run.mjs +106 -106
- package/canon/AGENTS.md +30 -30
- package/canon/context/DECISIONS.md +4 -4
- package/canon/context/GLOSSARY.md +11 -11
- package/canon/context/KNOWLEDGE.md +4 -4
- package/canon/context/PROJECT.md +15 -15
- package/canon/loop/loop-spec.md +65 -65
- package/canon/loop/prd.schema.md +43 -43
- package/canon/manifest.yaml +59 -59
- package/canon/policy/gates.md +7 -7
- package/canon/policy/roles.md +9 -9
- package/canon/skills/ATTRIBUTION.md +99 -99
- package/canon/skills/authoring-prd/SKILL.md +58 -58
- package/canon/skills/brainstorming/SKILL.md +164 -164
- package/canon/skills/codebase-design/DEEPENING.md +15 -15
- package/canon/skills/codebase-design/DESIGN-IT-TWICE.md +12 -12
- package/canon/skills/codebase-design/SKILL.md +39 -39
- package/canon/skills/dispatching-parallel-agents/SKILL.md +182 -182
- package/canon/skills/document-release/SKILL.md +302 -302
- package/canon/skills/domain-modeling/ADR-FORMAT.md +19 -19
- package/canon/skills/domain-modeling/CONTEXT-FORMAT.md +39 -39
- package/canon/skills/domain-modeling/SKILL.md +35 -35
- package/canon/skills/executing-plans/SKILL.md +70 -70
- package/canon/skills/finishing-a-development-branch/SKILL.md +200 -200
- package/canon/skills/health/SKILL.md +177 -177
- package/canon/skills/maintaining-context/SKILL.md +34 -34
- package/canon/skills/minimal-code/SKILL.md +21 -21
- package/canon/skills/no-ai-slop/SKILL.md +103 -103
- package/canon/skills/no-ai-slop/eval.md +43 -43
- package/canon/skills/plan-ceo-review/SKILL.md +541 -541
- package/canon/skills/plan-eng-review/SKILL.md +362 -362
- package/canon/skills/receiving-code-review/SKILL.md +213 -213
- package/canon/skills/requesting-code-review/SKILL.md +105 -105
- package/canon/skills/resolving-merge-conflicts/SKILL.md +18 -18
- package/canon/skills/retro/SKILL.md +397 -397
- package/canon/skills/review/SKILL.md +246 -246
- package/canon/skills/ship/SKILL.md +691 -691
- package/canon/skills/subagent-driven-development/SKILL.md +277 -277
- package/canon/skills/systematic-debugging/SKILL.md +296 -296
- package/canon/skills/tdd/SKILL.md +371 -371
- package/canon/skills/unslop-ui/SKILL.md +34 -34
- package/canon/skills/using-git-worktrees/SKILL.md +218 -218
- package/canon/skills/verification-before-completion/SKILL.md +139 -139
- package/canon/skills/visual-verification/SKILL.md +54 -54
- package/canon/skills/workflow/SKILL.md +22 -22
- package/canon/skills/writing-for-agents/SKILL-MECHANICS.md +27 -27
- package/canon/skills/writing-for-agents/SKILL.md +42 -42
- package/canon/skills/writing-plans/SKILL.md +152 -152
- package/canon/skills/writing-skills/SKILL.md +655 -655
- package/canon/skills/yoke-retrofit/SKILL.md +26 -26
- package/canon/skills/yoke-workflow/SKILL.md +20 -20
- package/canon/tools/codex-rtk-hook.mjs +35 -35
- package/canon/tools/graphify.md +3 -3
- package/canon/tools/playwright-mcp.md +3 -3
- package/canon/tools/rtk.md +7 -7
- package/canon/tools/serena.md +6 -6
- package/dist/agents/process.js +3 -0
- package/dist/loop/watchdog.js +1 -1
- package/dist/prd/command.js +17 -17
- package/dist/retrofit/planners/claude.js +14 -14
- package/dist/retrofit/preserve.js +2 -2
- package/docs/MIGRATING-TO-1.0.md +33 -33
- package/docs/MIGRATING-TO-1.1.md +27 -27
- package/docs/MIGRATING-TO-1.4.md +70 -70
- package/docs/PUBLISHING.md +91 -91
- package/docs/superpowers/plans/2026-06-28-baustein-e-context-layer.md +981 -981
- package/docs/superpowers/plans/2026-06-29-baustein-f-routing.md +258 -258
- package/docs/superpowers/plans/2026-06-29-baustein-g-loop-observability.md +1006 -1006
- package/docs/superpowers/plans/2026-06-29-baustein-h-loop-robustness.md +374 -374
- package/docs/superpowers/plans/2026-06-30-baustein-i-visual-design-verification.md +450 -450
- package/docs/superpowers/plans/2026-07-02-baustein-k-zero-to-100-bootstrap.md +1024 -1024
- package/docs/superpowers/plans/2026-07-02-baustein-m-flow-smoke-proofs.md +574 -574
- package/docs/superpowers/plans/2026-08-13-gauntlet-quality-loop.md +537 -537
- package/docs/superpowers/plans/2026-08-16-artifact-backed-output-compaction.md +329 -329
- package/docs/superpowers/specs/2026-06-28-baustein-e-context-layer-design.md +146 -146
- package/docs/superpowers/specs/2026-06-29-baustein-f-routing-design.md +106 -106
- package/docs/superpowers/specs/2026-06-29-baustein-g-loop-observability-design.md +186 -186
- package/docs/superpowers/specs/2026-06-29-baustein-h-loop-robustness-design.md +113 -113
- package/docs/superpowers/specs/2026-06-30-baustein-i-visual-design-verification-design.md +98 -98
- package/docs/superpowers/specs/2026-07-02-baustein-k-zero-to-100-bootstrap-design.md +200 -200
- package/docs/superpowers/specs/2026-07-02-baustein-m-flow-smoke-proofs-design.md +155 -155
- package/docs/superpowers/specs/2026-08-13-gauntlet-quality-loop-design.md +422 -422
- package/docs/superpowers/specs/2026-08-16-artifact-backed-output-compaction-design.md +166 -166
- package/gemini-extension.json +6 -6
- package/hooks/hooks.json +19 -19
- package/package.json +87 -87
|
@@ -1,329 +1,329 @@
|
|
|
1
|
-
# Artifact-backed Output Compaction Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
-
|
|
5
|
-
**Goal:** Add deterministic, artifact-backed compaction for failed Yoke gate commands without changing provider internals or adding runtime dependencies.
|
|
6
|
-
|
|
7
|
-
**Architecture:** A pure output compactor selects high-signal failure lines within a UTF-8 byte budget. A separate content-addressed artifact writer preserves exact combined stdout/stderr. Gate verifiers compose both units, while config and loop wiring supply phase-specific options.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** TypeScript ESM, Node.js built-ins, Zod, Vitest, existing Yoke benchmark scripts.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## File map
|
|
14
|
-
|
|
15
|
-
- Create `src/output/compact.ts`: pure preview selection and byte bounding.
|
|
16
|
-
- Create `src/output/artifact.ts`: content-addressed local artifact persistence and marker formatting.
|
|
17
|
-
- Create `src/output/types.ts`: shared output policy, phase, and artifact contracts.
|
|
18
|
-
- Create `tests/output/compact.test.ts`: compactor behavior.
|
|
19
|
-
- Create `tests/output/artifact.test.ts`: artifact fidelity and identity.
|
|
20
|
-
- Modify `src/loop/verify.ts`: capture both streams, compact failures, and optionally persist artifacts.
|
|
21
|
-
- Modify `tests/loop/verify.test.ts`: verifier integration and regression coverage.
|
|
22
|
-
- Modify `src/retrofit/config.ts`: validate optional output policy and expose resolved defaults.
|
|
23
|
-
- Modify `tests/retrofit/config.test.ts`: output configuration coverage.
|
|
24
|
-
- Modify `src/loop/run-command.ts`: thread resolved phase-specific verifier options.
|
|
25
|
-
- Modify `src/retrofit/gitignore.ts` and `tests/retrofit/gitignore.test.ts`: ignore `.yoke/artifacts/`.
|
|
26
|
-
- Create `bench/output-compaction.mjs`: deterministic local compression and digest benchmark.
|
|
27
|
-
- Create `tests/bench/output-compaction.test.ts`: benchmark acceptance checks.
|
|
28
|
-
- Modify `bench/README.md`, `README.md`, and `CHANGELOG.md`: document scope, privacy, and honest claims.
|
|
29
|
-
|
|
30
|
-
### Task 1: Pure deterministic preview compactor
|
|
31
|
-
|
|
32
|
-
**Files:**
|
|
33
|
-
- Create: `src/output/types.ts`
|
|
34
|
-
- Create: `src/output/compact.ts`
|
|
35
|
-
- Create: `tests/output/compact.test.ts`
|
|
36
|
-
|
|
37
|
-
- [ ] **Step 1: Write failing compactor tests**
|
|
38
|
-
|
|
39
|
-
Add tests calling the wished-for API:
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
import { compactCommandOutput } from '../../src/output/compact.js'
|
|
43
|
-
|
|
44
|
-
const result = compactCommandOutput(noisyOutput, { previewBytes: 256 })
|
|
45
|
-
expect(Buffer.byteLength(result.preview)).toBeLessThanOrEqual(256)
|
|
46
|
-
expect(result.preview).toContain('error TS2304')
|
|
47
|
-
expect(result.preview).toContain('Tests: 1 failed, 20 passed')
|
|
48
|
-
expect(result.preview).not.toContain('\u001b[')
|
|
49
|
-
expect(compactCommandOutput(noisyOutput, { previewBytes: 256 })).toEqual(result)
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Cover error priority, warning selection, adjacent context, duplicate removal, final summary retention, empty input, ANSI/control cleanup, deterministic output, and multibyte UTF-8 bounds.
|
|
53
|
-
|
|
54
|
-
- [ ] **Step 2: Verify RED**
|
|
55
|
-
|
|
56
|
-
Run: `npx vitest run tests/output/compact.test.ts`
|
|
57
|
-
|
|
58
|
-
Expected: FAIL because `src/output/compact.ts` does not exist.
|
|
59
|
-
|
|
60
|
-
- [ ] **Step 3: Implement the minimal pure API**
|
|
61
|
-
|
|
62
|
-
Define these contracts:
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
export type OutputPhase = 'criterion' | 'verify' | 'perf' | 'audit' | 'completion'
|
|
66
|
-
export interface OutputPolicy { readonly previewBytes: number; readonly artifactThresholdBytes: number }
|
|
67
|
-
export interface CompactedOutput { readonly preview: string; readonly originalBytes: number; readonly originalLines: number; readonly omitted: boolean }
|
|
68
|
-
export const DEFAULT_OUTPUT_POLICY: OutputPolicy = { previewBytes: 2_048, artifactThresholdBytes: 8_192 }
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Implement `compactCommandOutput(raw, { previewBytes })` with signal-priority selection, ordered de-duplication, tail retention, and safe UTF-8 truncation. Keep the implementation dependency-free and deterministic.
|
|
72
|
-
|
|
73
|
-
- [ ] **Step 4: Verify GREEN and refactor**
|
|
74
|
-
|
|
75
|
-
Run: `npx vitest run tests/output/compact.test.ts`
|
|
76
|
-
|
|
77
|
-
Expected: all compactor tests PASS with no warnings.
|
|
78
|
-
|
|
79
|
-
- [ ] **Step 5: Commit**
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
git add src/output/types.ts src/output/compact.ts tests/output/compact.test.ts
|
|
83
|
-
git commit -m "feat(output): add deterministic failure previews"
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Task 2: Content-addressed artifact persistence
|
|
87
|
-
|
|
88
|
-
**Files:**
|
|
89
|
-
- Create: `src/output/artifact.ts`
|
|
90
|
-
- Create: `tests/output/artifact.test.ts`
|
|
91
|
-
|
|
92
|
-
- [ ] **Step 1: Write failing artifact tests**
|
|
93
|
-
|
|
94
|
-
Test exact content, stable SHA-256 identity, stable repeated paths, sanitized story labels, relative forward-slash paths, and user-only file mode where observable:
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
const artifact = writeOutputArtifact(dir, raw, { phase: 'verify', storyId: '../STORY 1' })
|
|
98
|
-
expect(readFileSync(join(dir, artifact.relativePath), 'utf8')).toBe(raw)
|
|
99
|
-
expect(artifact.sha256).toMatch(/^[a-f0-9]{64}$/)
|
|
100
|
-
expect(artifact.relativePath).toMatch(/^\.yoke\/artifacts\/STORY-1\/verify-[a-f0-9]{12}\.log$/)
|
|
101
|
-
expect(writeOutputArtifact(dir, raw, { phase: 'verify', storyId: '../STORY 1' })).toEqual(artifact)
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
- [ ] **Step 2: Verify RED**
|
|
105
|
-
|
|
106
|
-
Run: `npx vitest run tests/output/artifact.test.ts`
|
|
107
|
-
|
|
108
|
-
Expected: FAIL because the writer does not exist.
|
|
109
|
-
|
|
110
|
-
- [ ] **Step 3: Implement minimal persistence**
|
|
111
|
-
|
|
112
|
-
Use `createHash('sha256')`, sanitize labels to `[A-Za-z0-9._-]`, fall back to `session`, create `.yoke/artifacts/<label>/`, and write the raw UTF-8 bytes with mode `0o600`. Return:
|
|
113
|
-
|
|
114
|
-
```ts
|
|
115
|
-
export interface OutputArtifact {
|
|
116
|
-
readonly relativePath: string
|
|
117
|
-
readonly bytes: number
|
|
118
|
-
readonly sha256: string
|
|
119
|
-
readonly marker: string
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
- [ ] **Step 4: Verify GREEN**
|
|
124
|
-
|
|
125
|
-
Run: `npx vitest run tests/output/artifact.test.ts`
|
|
126
|
-
|
|
127
|
-
Expected: all artifact tests PASS.
|
|
128
|
-
|
|
129
|
-
- [ ] **Step 5: Commit**
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
git add src/output/artifact.ts tests/output/artifact.test.ts
|
|
133
|
-
git commit -m "feat(output): persist content-addressed gate artifacts"
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Task 3: Gate verifier integration
|
|
137
|
-
|
|
138
|
-
**Files:**
|
|
139
|
-
- Modify: `src/loop/verify.ts`
|
|
140
|
-
- Modify: `tests/loop/verify.test.ts`
|
|
141
|
-
|
|
142
|
-
- [ ] **Step 1: Write failing verifier integration tests**
|
|
143
|
-
|
|
144
|
-
Add tests proving:
|
|
145
|
-
|
|
146
|
-
```ts
|
|
147
|
-
const small = commandVerifier('node small-failure.js', { phase: 'verify', policy })(dir)
|
|
148
|
-
expect(small.summary).toContain('EARLY_ERROR')
|
|
149
|
-
expect(existsSync(join(dir, '.yoke', 'artifacts'))).toBe(false)
|
|
150
|
-
|
|
151
|
-
const large = commandVerifier('node large-failure.js', { phase: 'verify', policy })(dir)
|
|
152
|
-
expect(large.summary).toMatch(/\[full output: \.yoke\/artifacts\//)
|
|
153
|
-
expect(readFileSync(resolvedArtifactPath, 'utf8')).toContain('=== stdout ===')
|
|
154
|
-
expect(readFileSync(resolvedArtifactPath, 'utf8')).toContain('=== stderr ===')
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
Also cover success without artifacts, timeout labelling, mixed streams, custom writer failure, and source-compatible calls without options.
|
|
158
|
-
|
|
159
|
-
- [ ] **Step 2: Verify RED**
|
|
160
|
-
|
|
161
|
-
Run: `npx vitest run tests/loop/verify.test.ts`
|
|
162
|
-
|
|
163
|
-
Expected: new assertions FAIL because verifier options and artifacts are absent.
|
|
164
|
-
|
|
165
|
-
- [ ] **Step 3: Integrate compaction and artifact writing**
|
|
166
|
-
|
|
167
|
-
Add optional verifier options:
|
|
168
|
-
|
|
169
|
-
```ts
|
|
170
|
-
export interface CommandVerifierOptions {
|
|
171
|
-
readonly phase?: OutputPhase
|
|
172
|
-
readonly policy?: OutputPolicy
|
|
173
|
-
readonly artifactWriter?: typeof writeOutputArtifact
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
Capture stdout and stderr together with labelled headings. On failure, build the preview and write an artifact only when raw bytes meet the threshold. Catch writer errors and append a bounded `artifact unavailable` message without altering `passed: false`. Pass options through `commandsVerifier`.
|
|
178
|
-
|
|
179
|
-
- [ ] **Step 4: Verify GREEN and regression tests**
|
|
180
|
-
|
|
181
|
-
Run: `npx vitest run tests/loop/verify.test.ts tests/output/*.test.ts`
|
|
182
|
-
|
|
183
|
-
Expected: all selected tests PASS.
|
|
184
|
-
|
|
185
|
-
- [ ] **Step 5: Commit**
|
|
186
|
-
|
|
187
|
-
```bash
|
|
188
|
-
git add src/loop/verify.ts tests/loop/verify.test.ts
|
|
189
|
-
git commit -m "feat(loop): compact failed gate output"
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Task 4: Configuration, phase wiring, and ignored runtime state
|
|
193
|
-
|
|
194
|
-
**Files:**
|
|
195
|
-
- Modify: `src/retrofit/config.ts`
|
|
196
|
-
- Modify: `tests/retrofit/config.test.ts`
|
|
197
|
-
- Modify: `src/loop/run-command.ts`
|
|
198
|
-
- Modify: `src/retrofit/gitignore.ts`
|
|
199
|
-
- Modify: `tests/retrofit/gitignore.test.ts`
|
|
200
|
-
|
|
201
|
-
- [ ] **Step 1: Write failing configuration and ignore tests**
|
|
202
|
-
|
|
203
|
-
Add tests for absent defaults, valid overrides, non-positive values, `artifactThresholdBytes < previewBytes`, and `.yoke/artifacts/` in `YOKE_IGNORE_LINES`.
|
|
204
|
-
|
|
205
|
-
```ts
|
|
206
|
-
expect(resolveOutputPolicy(defaultConfig('1.4.0'))).toEqual(DEFAULT_OUTPUT_POLICY)
|
|
207
|
-
expect(() => YokeConfigSchema.parse({ ...defaultConfig('1'), output: { previewBytes: 4096, artifactThresholdBytes: 2048 } })).toThrow()
|
|
208
|
-
expect(YOKE_IGNORE_LINES).toContain('.yoke/artifacts/')
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
- [ ] **Step 2: Verify RED**
|
|
212
|
-
|
|
213
|
-
Run: `npx vitest run tests/retrofit/config.test.ts tests/retrofit/gitignore.test.ts`
|
|
214
|
-
|
|
215
|
-
Expected: FAIL because output config and ignore entry are absent.
|
|
216
|
-
|
|
217
|
-
- [ ] **Step 3: Implement schema and resolver**
|
|
218
|
-
|
|
219
|
-
Add an optional output schema with positive integer fields and a `superRefine` threshold invariant. Export `resolveOutputPolicy(config): OutputPolicy`, merging partial config over `DEFAULT_OUTPUT_POLICY` and validating the resolved pair.
|
|
220
|
-
|
|
221
|
-
- [ ] **Step 4: Wire every command gate phase**
|
|
222
|
-
|
|
223
|
-
In `runLoopCommand`, resolve one policy and create verifiers with explicit phases:
|
|
224
|
-
|
|
225
|
-
```ts
|
|
226
|
-
const outputPolicy = resolveOutputPolicy(config)
|
|
227
|
-
commandVerifier(command, { phase: 'verify', policy: outputPolicy })
|
|
228
|
-
commandVerifier(config.perf.command, { phase: 'perf', policy: outputPolicy })
|
|
229
|
-
commandVerifier(config.completion.command, { phase: 'completion', policy: outputPolicy })
|
|
230
|
-
commandsVerifier(criterion.verify, { phase: 'criterion', policy: outputPolicy })
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
Retain the existing dedicated `runAudit` path; it already returns bounded findings rather than raw command output.
|
|
234
|
-
|
|
235
|
-
- [ ] **Step 5: Verify GREEN and typecheck**
|
|
236
|
-
|
|
237
|
-
Run: `npx vitest run tests/retrofit/config.test.ts tests/retrofit/gitignore.test.ts tests/loop/verify.test.ts`
|
|
238
|
-
|
|
239
|
-
Run: `npm run lint`
|
|
240
|
-
|
|
241
|
-
Expected: all tests and TypeScript checks PASS.
|
|
242
|
-
|
|
243
|
-
- [ ] **Step 6: Commit**
|
|
244
|
-
|
|
245
|
-
```bash
|
|
246
|
-
git add src/retrofit/config.ts tests/retrofit/config.test.ts src/loop/run-command.ts src/retrofit/gitignore.ts tests/retrofit/gitignore.test.ts
|
|
247
|
-
git commit -m "feat(config): wire gate output budgets"
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### Task 5: Deterministic benchmark evidence
|
|
251
|
-
|
|
252
|
-
**Files:**
|
|
253
|
-
- Create: `bench/output-compaction.mjs`
|
|
254
|
-
- Create: `tests/bench/output-compaction.test.ts`
|
|
255
|
-
- Modify: `bench/README.md`
|
|
256
|
-
|
|
257
|
-
- [ ] **Step 1: Write the failing benchmark test**
|
|
258
|
-
|
|
259
|
-
Import `runOutputCompactionBenchmark` and assert that its fixed noisy fixture reports bounded preview bytes, a ratio above one, retained early error and final summary, and a digest round-trip.
|
|
260
|
-
|
|
261
|
-
- [ ] **Step 2: Verify RED**
|
|
262
|
-
|
|
263
|
-
Run: `npx vitest run tests/bench/output-compaction.test.ts`
|
|
264
|
-
|
|
265
|
-
Expected: FAIL because the benchmark module is absent.
|
|
266
|
-
|
|
267
|
-
- [ ] **Step 3: Implement benchmark module**
|
|
268
|
-
|
|
269
|
-
Build a deterministic failure fixture, call the production compactor and artifact writer, verify the stored digest, and return a JSON-serializable result. When executed directly, print formatted JSON and exit non-zero if any acceptance boolean is false.
|
|
270
|
-
|
|
271
|
-
- [ ] **Step 4: Verify GREEN and run benchmark**
|
|
272
|
-
|
|
273
|
-
Run: `npx vitest run tests/bench/output-compaction.test.ts`
|
|
274
|
-
|
|
275
|
-
Run: `node bench/output-compaction.mjs`
|
|
276
|
-
|
|
277
|
-
Expected: test PASS; benchmark reports retained signals, bounded preview, and digest round-trip.
|
|
278
|
-
|
|
279
|
-
- [ ] **Step 5: Commit**
|
|
280
|
-
|
|
281
|
-
```bash
|
|
282
|
-
git add bench/output-compaction.mjs tests/bench/output-compaction.test.ts bench/README.md
|
|
283
|
-
git commit -m "bench: measure gate output compaction"
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### Task 6: User documentation, release note, and final verification
|
|
287
|
-
|
|
288
|
-
**Files:**
|
|
289
|
-
- Modify: `README.md`
|
|
290
|
-
- Modify: `CHANGELOG.md`
|
|
291
|
-
- Modify: `docs/superpowers/specs/2026-08-16-artifact-backed-output-compaction-design.md`
|
|
292
|
-
|
|
293
|
-
- [ ] **Step 1: Document exact behavior and limitations**
|
|
294
|
-
|
|
295
|
-
Add README configuration and retrieval examples, state that only Yoke-controlled gate summaries are compacted, and warn that local raw artifacts may contain secrets. Add an `Unreleased` changelog section without changing version `1.4.0`. Update spec status to implemented after verification.
|
|
296
|
-
|
|
297
|
-
- [ ] **Step 2: Run targeted and complete verification**
|
|
298
|
-
|
|
299
|
-
Run in order:
|
|
300
|
-
|
|
301
|
-
```bash
|
|
302
|
-
npm test
|
|
303
|
-
npm run lint
|
|
304
|
-
npm run build
|
|
305
|
-
npm run docs:check
|
|
306
|
-
npm run package:check
|
|
307
|
-
npm audit --audit-level=high
|
|
308
|
-
node bench/output-compaction.mjs
|
|
309
|
-
git diff --check
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
Expected: every command exits zero; all Vitest tests pass; docs metadata remains synchronized with package `1.4.0`; package dry-run contains the new runtime modules and documentation; benchmark acceptance booleans are true.
|
|
313
|
-
|
|
314
|
-
- [ ] **Step 3: Run final provenance audit**
|
|
315
|
-
|
|
316
|
-
Run the global `audit-provenance` script on the changed README and design document. Report `located`, `verified`, `trusted`, `scan_complete`, and every unknown. Do not infer authorship.
|
|
317
|
-
|
|
318
|
-
- [ ] **Step 4: Commit final documentation**
|
|
319
|
-
|
|
320
|
-
```bash
|
|
321
|
-
git add README.md CHANGELOG.md docs/superpowers/specs/2026-08-16-artifact-backed-output-compaction-design.md
|
|
322
|
-
git commit -m "docs: explain artifact-backed output compaction"
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
- [ ] **Step 5: Verify repository state**
|
|
326
|
-
|
|
327
|
-
Run: `git status --short` and `git log --oneline --decorate -8`
|
|
328
|
-
|
|
329
|
-
Expected: clean feature worktree and a reviewable series of focused commits.
|
|
1
|
+
# Artifact-backed Output Compaction Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add deterministic, artifact-backed compaction for failed Yoke gate commands without changing provider internals or adding runtime dependencies.
|
|
6
|
+
|
|
7
|
+
**Architecture:** A pure output compactor selects high-signal failure lines within a UTF-8 byte budget. A separate content-addressed artifact writer preserves exact combined stdout/stderr. Gate verifiers compose both units, while config and loop wiring supply phase-specific options.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript ESM, Node.js built-ins, Zod, Vitest, existing Yoke benchmark scripts.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File map
|
|
14
|
+
|
|
15
|
+
- Create `src/output/compact.ts`: pure preview selection and byte bounding.
|
|
16
|
+
- Create `src/output/artifact.ts`: content-addressed local artifact persistence and marker formatting.
|
|
17
|
+
- Create `src/output/types.ts`: shared output policy, phase, and artifact contracts.
|
|
18
|
+
- Create `tests/output/compact.test.ts`: compactor behavior.
|
|
19
|
+
- Create `tests/output/artifact.test.ts`: artifact fidelity and identity.
|
|
20
|
+
- Modify `src/loop/verify.ts`: capture both streams, compact failures, and optionally persist artifacts.
|
|
21
|
+
- Modify `tests/loop/verify.test.ts`: verifier integration and regression coverage.
|
|
22
|
+
- Modify `src/retrofit/config.ts`: validate optional output policy and expose resolved defaults.
|
|
23
|
+
- Modify `tests/retrofit/config.test.ts`: output configuration coverage.
|
|
24
|
+
- Modify `src/loop/run-command.ts`: thread resolved phase-specific verifier options.
|
|
25
|
+
- Modify `src/retrofit/gitignore.ts` and `tests/retrofit/gitignore.test.ts`: ignore `.yoke/artifacts/`.
|
|
26
|
+
- Create `bench/output-compaction.mjs`: deterministic local compression and digest benchmark.
|
|
27
|
+
- Create `tests/bench/output-compaction.test.ts`: benchmark acceptance checks.
|
|
28
|
+
- Modify `bench/README.md`, `README.md`, and `CHANGELOG.md`: document scope, privacy, and honest claims.
|
|
29
|
+
|
|
30
|
+
### Task 1: Pure deterministic preview compactor
|
|
31
|
+
|
|
32
|
+
**Files:**
|
|
33
|
+
- Create: `src/output/types.ts`
|
|
34
|
+
- Create: `src/output/compact.ts`
|
|
35
|
+
- Create: `tests/output/compact.test.ts`
|
|
36
|
+
|
|
37
|
+
- [ ] **Step 1: Write failing compactor tests**
|
|
38
|
+
|
|
39
|
+
Add tests calling the wished-for API:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { compactCommandOutput } from '../../src/output/compact.js'
|
|
43
|
+
|
|
44
|
+
const result = compactCommandOutput(noisyOutput, { previewBytes: 256 })
|
|
45
|
+
expect(Buffer.byteLength(result.preview)).toBeLessThanOrEqual(256)
|
|
46
|
+
expect(result.preview).toContain('error TS2304')
|
|
47
|
+
expect(result.preview).toContain('Tests: 1 failed, 20 passed')
|
|
48
|
+
expect(result.preview).not.toContain('\u001b[')
|
|
49
|
+
expect(compactCommandOutput(noisyOutput, { previewBytes: 256 })).toEqual(result)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Cover error priority, warning selection, adjacent context, duplicate removal, final summary retention, empty input, ANSI/control cleanup, deterministic output, and multibyte UTF-8 bounds.
|
|
53
|
+
|
|
54
|
+
- [ ] **Step 2: Verify RED**
|
|
55
|
+
|
|
56
|
+
Run: `npx vitest run tests/output/compact.test.ts`
|
|
57
|
+
|
|
58
|
+
Expected: FAIL because `src/output/compact.ts` does not exist.
|
|
59
|
+
|
|
60
|
+
- [ ] **Step 3: Implement the minimal pure API**
|
|
61
|
+
|
|
62
|
+
Define these contracts:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
export type OutputPhase = 'criterion' | 'verify' | 'perf' | 'audit' | 'completion'
|
|
66
|
+
export interface OutputPolicy { readonly previewBytes: number; readonly artifactThresholdBytes: number }
|
|
67
|
+
export interface CompactedOutput { readonly preview: string; readonly originalBytes: number; readonly originalLines: number; readonly omitted: boolean }
|
|
68
|
+
export const DEFAULT_OUTPUT_POLICY: OutputPolicy = { previewBytes: 2_048, artifactThresholdBytes: 8_192 }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Implement `compactCommandOutput(raw, { previewBytes })` with signal-priority selection, ordered de-duplication, tail retention, and safe UTF-8 truncation. Keep the implementation dependency-free and deterministic.
|
|
72
|
+
|
|
73
|
+
- [ ] **Step 4: Verify GREEN and refactor**
|
|
74
|
+
|
|
75
|
+
Run: `npx vitest run tests/output/compact.test.ts`
|
|
76
|
+
|
|
77
|
+
Expected: all compactor tests PASS with no warnings.
|
|
78
|
+
|
|
79
|
+
- [ ] **Step 5: Commit**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git add src/output/types.ts src/output/compact.ts tests/output/compact.test.ts
|
|
83
|
+
git commit -m "feat(output): add deterministic failure previews"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Task 2: Content-addressed artifact persistence
|
|
87
|
+
|
|
88
|
+
**Files:**
|
|
89
|
+
- Create: `src/output/artifact.ts`
|
|
90
|
+
- Create: `tests/output/artifact.test.ts`
|
|
91
|
+
|
|
92
|
+
- [ ] **Step 1: Write failing artifact tests**
|
|
93
|
+
|
|
94
|
+
Test exact content, stable SHA-256 identity, stable repeated paths, sanitized story labels, relative forward-slash paths, and user-only file mode where observable:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const artifact = writeOutputArtifact(dir, raw, { phase: 'verify', storyId: '../STORY 1' })
|
|
98
|
+
expect(readFileSync(join(dir, artifact.relativePath), 'utf8')).toBe(raw)
|
|
99
|
+
expect(artifact.sha256).toMatch(/^[a-f0-9]{64}$/)
|
|
100
|
+
expect(artifact.relativePath).toMatch(/^\.yoke\/artifacts\/STORY-1\/verify-[a-f0-9]{12}\.log$/)
|
|
101
|
+
expect(writeOutputArtifact(dir, raw, { phase: 'verify', storyId: '../STORY 1' })).toEqual(artifact)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- [ ] **Step 2: Verify RED**
|
|
105
|
+
|
|
106
|
+
Run: `npx vitest run tests/output/artifact.test.ts`
|
|
107
|
+
|
|
108
|
+
Expected: FAIL because the writer does not exist.
|
|
109
|
+
|
|
110
|
+
- [ ] **Step 3: Implement minimal persistence**
|
|
111
|
+
|
|
112
|
+
Use `createHash('sha256')`, sanitize labels to `[A-Za-z0-9._-]`, fall back to `session`, create `.yoke/artifacts/<label>/`, and write the raw UTF-8 bytes with mode `0o600`. Return:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
export interface OutputArtifact {
|
|
116
|
+
readonly relativePath: string
|
|
117
|
+
readonly bytes: number
|
|
118
|
+
readonly sha256: string
|
|
119
|
+
readonly marker: string
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
- [ ] **Step 4: Verify GREEN**
|
|
124
|
+
|
|
125
|
+
Run: `npx vitest run tests/output/artifact.test.ts`
|
|
126
|
+
|
|
127
|
+
Expected: all artifact tests PASS.
|
|
128
|
+
|
|
129
|
+
- [ ] **Step 5: Commit**
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
git add src/output/artifact.ts tests/output/artifact.test.ts
|
|
133
|
+
git commit -m "feat(output): persist content-addressed gate artifacts"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Task 3: Gate verifier integration
|
|
137
|
+
|
|
138
|
+
**Files:**
|
|
139
|
+
- Modify: `src/loop/verify.ts`
|
|
140
|
+
- Modify: `tests/loop/verify.test.ts`
|
|
141
|
+
|
|
142
|
+
- [ ] **Step 1: Write failing verifier integration tests**
|
|
143
|
+
|
|
144
|
+
Add tests proving:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const small = commandVerifier('node small-failure.js', { phase: 'verify', policy })(dir)
|
|
148
|
+
expect(small.summary).toContain('EARLY_ERROR')
|
|
149
|
+
expect(existsSync(join(dir, '.yoke', 'artifacts'))).toBe(false)
|
|
150
|
+
|
|
151
|
+
const large = commandVerifier('node large-failure.js', { phase: 'verify', policy })(dir)
|
|
152
|
+
expect(large.summary).toMatch(/\[full output: \.yoke\/artifacts\//)
|
|
153
|
+
expect(readFileSync(resolvedArtifactPath, 'utf8')).toContain('=== stdout ===')
|
|
154
|
+
expect(readFileSync(resolvedArtifactPath, 'utf8')).toContain('=== stderr ===')
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Also cover success without artifacts, timeout labelling, mixed streams, custom writer failure, and source-compatible calls without options.
|
|
158
|
+
|
|
159
|
+
- [ ] **Step 2: Verify RED**
|
|
160
|
+
|
|
161
|
+
Run: `npx vitest run tests/loop/verify.test.ts`
|
|
162
|
+
|
|
163
|
+
Expected: new assertions FAIL because verifier options and artifacts are absent.
|
|
164
|
+
|
|
165
|
+
- [ ] **Step 3: Integrate compaction and artifact writing**
|
|
166
|
+
|
|
167
|
+
Add optional verifier options:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
export interface CommandVerifierOptions {
|
|
171
|
+
readonly phase?: OutputPhase
|
|
172
|
+
readonly policy?: OutputPolicy
|
|
173
|
+
readonly artifactWriter?: typeof writeOutputArtifact
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Capture stdout and stderr together with labelled headings. On failure, build the preview and write an artifact only when raw bytes meet the threshold. Catch writer errors and append a bounded `artifact unavailable` message without altering `passed: false`. Pass options through `commandsVerifier`.
|
|
178
|
+
|
|
179
|
+
- [ ] **Step 4: Verify GREEN and regression tests**
|
|
180
|
+
|
|
181
|
+
Run: `npx vitest run tests/loop/verify.test.ts tests/output/*.test.ts`
|
|
182
|
+
|
|
183
|
+
Expected: all selected tests PASS.
|
|
184
|
+
|
|
185
|
+
- [ ] **Step 5: Commit**
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
git add src/loop/verify.ts tests/loop/verify.test.ts
|
|
189
|
+
git commit -m "feat(loop): compact failed gate output"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Task 4: Configuration, phase wiring, and ignored runtime state
|
|
193
|
+
|
|
194
|
+
**Files:**
|
|
195
|
+
- Modify: `src/retrofit/config.ts`
|
|
196
|
+
- Modify: `tests/retrofit/config.test.ts`
|
|
197
|
+
- Modify: `src/loop/run-command.ts`
|
|
198
|
+
- Modify: `src/retrofit/gitignore.ts`
|
|
199
|
+
- Modify: `tests/retrofit/gitignore.test.ts`
|
|
200
|
+
|
|
201
|
+
- [ ] **Step 1: Write failing configuration and ignore tests**
|
|
202
|
+
|
|
203
|
+
Add tests for absent defaults, valid overrides, non-positive values, `artifactThresholdBytes < previewBytes`, and `.yoke/artifacts/` in `YOKE_IGNORE_LINES`.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
expect(resolveOutputPolicy(defaultConfig('1.4.0'))).toEqual(DEFAULT_OUTPUT_POLICY)
|
|
207
|
+
expect(() => YokeConfigSchema.parse({ ...defaultConfig('1'), output: { previewBytes: 4096, artifactThresholdBytes: 2048 } })).toThrow()
|
|
208
|
+
expect(YOKE_IGNORE_LINES).toContain('.yoke/artifacts/')
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
- [ ] **Step 2: Verify RED**
|
|
212
|
+
|
|
213
|
+
Run: `npx vitest run tests/retrofit/config.test.ts tests/retrofit/gitignore.test.ts`
|
|
214
|
+
|
|
215
|
+
Expected: FAIL because output config and ignore entry are absent.
|
|
216
|
+
|
|
217
|
+
- [ ] **Step 3: Implement schema and resolver**
|
|
218
|
+
|
|
219
|
+
Add an optional output schema with positive integer fields and a `superRefine` threshold invariant. Export `resolveOutputPolicy(config): OutputPolicy`, merging partial config over `DEFAULT_OUTPUT_POLICY` and validating the resolved pair.
|
|
220
|
+
|
|
221
|
+
- [ ] **Step 4: Wire every command gate phase**
|
|
222
|
+
|
|
223
|
+
In `runLoopCommand`, resolve one policy and create verifiers with explicit phases:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
const outputPolicy = resolveOutputPolicy(config)
|
|
227
|
+
commandVerifier(command, { phase: 'verify', policy: outputPolicy })
|
|
228
|
+
commandVerifier(config.perf.command, { phase: 'perf', policy: outputPolicy })
|
|
229
|
+
commandVerifier(config.completion.command, { phase: 'completion', policy: outputPolicy })
|
|
230
|
+
commandsVerifier(criterion.verify, { phase: 'criterion', policy: outputPolicy })
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Retain the existing dedicated `runAudit` path; it already returns bounded findings rather than raw command output.
|
|
234
|
+
|
|
235
|
+
- [ ] **Step 5: Verify GREEN and typecheck**
|
|
236
|
+
|
|
237
|
+
Run: `npx vitest run tests/retrofit/config.test.ts tests/retrofit/gitignore.test.ts tests/loop/verify.test.ts`
|
|
238
|
+
|
|
239
|
+
Run: `npm run lint`
|
|
240
|
+
|
|
241
|
+
Expected: all tests and TypeScript checks PASS.
|
|
242
|
+
|
|
243
|
+
- [ ] **Step 6: Commit**
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
git add src/retrofit/config.ts tests/retrofit/config.test.ts src/loop/run-command.ts src/retrofit/gitignore.ts tests/retrofit/gitignore.test.ts
|
|
247
|
+
git commit -m "feat(config): wire gate output budgets"
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Task 5: Deterministic benchmark evidence
|
|
251
|
+
|
|
252
|
+
**Files:**
|
|
253
|
+
- Create: `bench/output-compaction.mjs`
|
|
254
|
+
- Create: `tests/bench/output-compaction.test.ts`
|
|
255
|
+
- Modify: `bench/README.md`
|
|
256
|
+
|
|
257
|
+
- [ ] **Step 1: Write the failing benchmark test**
|
|
258
|
+
|
|
259
|
+
Import `runOutputCompactionBenchmark` and assert that its fixed noisy fixture reports bounded preview bytes, a ratio above one, retained early error and final summary, and a digest round-trip.
|
|
260
|
+
|
|
261
|
+
- [ ] **Step 2: Verify RED**
|
|
262
|
+
|
|
263
|
+
Run: `npx vitest run tests/bench/output-compaction.test.ts`
|
|
264
|
+
|
|
265
|
+
Expected: FAIL because the benchmark module is absent.
|
|
266
|
+
|
|
267
|
+
- [ ] **Step 3: Implement benchmark module**
|
|
268
|
+
|
|
269
|
+
Build a deterministic failure fixture, call the production compactor and artifact writer, verify the stored digest, and return a JSON-serializable result. When executed directly, print formatted JSON and exit non-zero if any acceptance boolean is false.
|
|
270
|
+
|
|
271
|
+
- [ ] **Step 4: Verify GREEN and run benchmark**
|
|
272
|
+
|
|
273
|
+
Run: `npx vitest run tests/bench/output-compaction.test.ts`
|
|
274
|
+
|
|
275
|
+
Run: `node bench/output-compaction.mjs`
|
|
276
|
+
|
|
277
|
+
Expected: test PASS; benchmark reports retained signals, bounded preview, and digest round-trip.
|
|
278
|
+
|
|
279
|
+
- [ ] **Step 5: Commit**
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
git add bench/output-compaction.mjs tests/bench/output-compaction.test.ts bench/README.md
|
|
283
|
+
git commit -m "bench: measure gate output compaction"
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Task 6: User documentation, release note, and final verification
|
|
287
|
+
|
|
288
|
+
**Files:**
|
|
289
|
+
- Modify: `README.md`
|
|
290
|
+
- Modify: `CHANGELOG.md`
|
|
291
|
+
- Modify: `docs/superpowers/specs/2026-08-16-artifact-backed-output-compaction-design.md`
|
|
292
|
+
|
|
293
|
+
- [ ] **Step 1: Document exact behavior and limitations**
|
|
294
|
+
|
|
295
|
+
Add README configuration and retrieval examples, state that only Yoke-controlled gate summaries are compacted, and warn that local raw artifacts may contain secrets. Add an `Unreleased` changelog section without changing version `1.4.0`. Update spec status to implemented after verification.
|
|
296
|
+
|
|
297
|
+
- [ ] **Step 2: Run targeted and complete verification**
|
|
298
|
+
|
|
299
|
+
Run in order:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
npm test
|
|
303
|
+
npm run lint
|
|
304
|
+
npm run build
|
|
305
|
+
npm run docs:check
|
|
306
|
+
npm run package:check
|
|
307
|
+
npm audit --audit-level=high
|
|
308
|
+
node bench/output-compaction.mjs
|
|
309
|
+
git diff --check
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Expected: every command exits zero; all Vitest tests pass; docs metadata remains synchronized with package `1.4.0`; package dry-run contains the new runtime modules and documentation; benchmark acceptance booleans are true.
|
|
313
|
+
|
|
314
|
+
- [ ] **Step 3: Run final provenance audit**
|
|
315
|
+
|
|
316
|
+
Run the global `audit-provenance` script on the changed README and design document. Report `located`, `verified`, `trusted`, `scan_complete`, and every unknown. Do not infer authorship.
|
|
317
|
+
|
|
318
|
+
- [ ] **Step 4: Commit final documentation**
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
git add README.md CHANGELOG.md docs/superpowers/specs/2026-08-16-artifact-backed-output-compaction-design.md
|
|
322
|
+
git commit -m "docs: explain artifact-backed output compaction"
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
- [ ] **Step 5: Verify repository state**
|
|
326
|
+
|
|
327
|
+
Run: `git status --short` and `git log --oneline --decorate -8`
|
|
328
|
+
|
|
329
|
+
Expected: clean feature worktree and a reviewable series of focused commits.
|