@magnusekdahl/parallix 1.2.1 → 1.3.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 (79) hide show
  1. package/index.js +190 -185
  2. package/lib/commands/active.js +502 -579
  3. package/lib/commands/active.ts +665 -0
  4. package/lib/commands/checkpoint.js +98 -74
  5. package/lib/commands/checkpoint.ts +79 -0
  6. package/lib/commands/config.js +61 -31
  7. package/lib/commands/config.ts +47 -0
  8. package/lib/commands/coverage-gate.js +271 -246
  9. package/lib/commands/coverage-gate.ts +362 -0
  10. package/lib/commands/diff.js +140 -119
  11. package/lib/commands/diff.ts +125 -0
  12. package/lib/commands/draft.js +770 -923
  13. package/lib/commands/draft.ts +1021 -0
  14. package/lib/commands/handoff.js +588 -608
  15. package/lib/commands/handoff.ts +694 -0
  16. package/lib/commands/integrate.js +1331 -1468
  17. package/lib/commands/integrate.ts +1688 -0
  18. package/lib/commands/mission-start.js +279 -237
  19. package/lib/commands/mission-start.ts +263 -0
  20. package/lib/commands/rebase.js +606 -582
  21. package/lib/commands/rebase.ts +635 -0
  22. package/lib/commands/repair-handoff.js +181 -179
  23. package/lib/commands/repair-handoff.ts +228 -0
  24. package/lib/commands/resolve-conflict.js +121 -102
  25. package/lib/commands/resolve-conflict.ts +112 -0
  26. package/lib/commands/review.js +3 -4
  27. package/lib/commands/review.ts +14 -0
  28. package/lib/commands/setup-review.js +4 -5
  29. package/lib/commands/setup-review.ts +14 -0
  30. package/lib/commands/setup.js +3 -3
  31. package/lib/commands/setup.ts +2 -0
  32. package/lib/commands/stats-backfill.js +394 -359
  33. package/lib/commands/stats-backfill.ts +418 -0
  34. package/lib/commands/stats.js +1424 -1665
  35. package/lib/commands/stats.ts +2186 -0
  36. package/lib/commands/status.js +219 -200
  37. package/lib/commands/status.ts +219 -0
  38. package/lib/commands/verify.js +3 -1
  39. package/lib/commands/verify.ts +2 -0
  40. package/lib/core/nels.js +112 -127
  41. package/lib/core/nels.ts +189 -0
  42. package/lib/core/subagent-limit.js +15 -16
  43. package/lib/core/subagent-limit.ts +25 -0
  44. package/lib/index.js +142 -59
  45. package/lib/index.ts +134 -0
  46. package/lib/review/rebase.js +142 -145
  47. package/lib/review/rebase.ts +188 -0
  48. package/lib/review/review-adapter.js +99 -87
  49. package/lib/review/review-adapter.ts +198 -0
  50. package/lib/review/review-artifacts.js +486 -629
  51. package/lib/review/review-artifacts.ts +622 -0
  52. package/lib/review/review-commands.js +1220 -1373
  53. package/lib/review/review-commands.ts +1579 -0
  54. package/lib/review/review-events.js +544 -886
  55. package/lib/review/review-events.ts +954 -0
  56. package/lib/review/review-loop.js +945 -1020
  57. package/lib/review/review-loop.ts +1174 -0
  58. package/lib/review/review-polling.js +86 -115
  59. package/lib/review/review-polling.ts +194 -0
  60. package/lib/review/review-prompts.js +154 -189
  61. package/lib/review/review-prompts.ts +182 -0
  62. package/lib/review/review-state.js +246 -225
  63. package/lib/review/review-state.ts +353 -0
  64. package/lib/review/review.js +63 -84
  65. package/lib/review/review.ts +138 -0
  66. package/lib/tools/backlog.js +694 -680
  67. package/lib/tools/backlog.ts +835 -0
  68. package/lib/tools/forgejo.js +1270 -1405
  69. package/lib/tools/forgejo.ts +1793 -0
  70. package/lib/tools/gatekeeper.js +106 -96
  71. package/lib/tools/gatekeeper.ts +124 -0
  72. package/lib/tools/redgreen.js +200 -181
  73. package/lib/tools/redgreen.ts +221 -0
  74. package/lib/tools/sessions.js +58 -52
  75. package/lib/tools/sessions.ts +81 -0
  76. package/lib/tools/setup-review.js +902 -975
  77. package/lib/tools/setup-review.ts +1152 -0
  78. package/package.json +4 -4
  79. package/px.js +226 -208
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * Reviewer-family persistence for the autonomous review loop.
3
4
  *
@@ -6,13 +7,52 @@
6
7
  *
7
8
  * Owned by the Node workflow harness (ADR 0037 / task-089).
8
9
  */
9
-
10
- const fs = require('fs');
11
- const path = require('path');
12
- const { git } = require('../core/git');
13
- const { findMissionDir, resolveWorktree } = require('../core/mission-utils');
14
- const fmt = require('../core/fmt');
15
-
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.ReviewState = exports.VALID_PHASES = void 0;
45
+ exports.reviewStateFile = reviewStateFile;
46
+ exports.readReviewState = readReviewState;
47
+ exports.resolveReviewIdentity = resolveReviewIdentity;
48
+ exports.normalizeReviewPhase = normalizeReviewPhase;
49
+ exports.writeReviewState = writeReviewState;
50
+ exports.resetReviewState = resetReviewState;
51
+ const fs = __importStar(require("fs"));
52
+ const path = __importStar(require("path"));
53
+ const git_js_1 = require("../core/git.js");
54
+ const mission_utils_js_1 = require("../core/mission-utils.js");
55
+ const fmt_js_1 = require("../core/fmt.js");
16
56
  /**
17
57
  * Return the path to the review-state file for a given slug.
18
58
  * Resolves using the same mission-dir discovery as other workflow commands.
@@ -22,11 +62,12 @@ const fmt = require('../core/fmt');
22
62
  * @returns {string|null} Absolute path, or null if mission dir not found
23
63
  */
24
64
  function reviewStateFile(slug, rootDir = process.cwd()) {
25
- const missionDir = findMissionDir(slug, rootDir);
26
- if (!missionDir) {return null;}
27
- return path.join(missionDir, 'review-state.json');
65
+ const missionDir = (0, mission_utils_js_1.findMissionDir)(slug, rootDir);
66
+ if (!missionDir) {
67
+ return null;
68
+ }
69
+ return path.join(missionDir, 'review-state.json');
28
70
  }
29
-
30
71
  /**
31
72
  * Read the persisted review state for a mission.
32
73
  *
@@ -35,21 +76,22 @@ function reviewStateFile(slug, rootDir = process.cwd()) {
35
76
  * @returns {ReviewState|null}
36
77
  */
37
78
  function readReviewState(slug, rootDir = process.cwd()) {
38
- const statePath = reviewStateFile(slug, rootDir);
39
- if (!statePath || !fs.existsSync(statePath)) {return null;}
40
-
41
- try {
42
- const raw = fs.readFileSync(statePath, 'utf8');
43
- const parsed = JSON.parse(raw);
44
- if (parsed && parsed.reviewer && parsed.implementer) {
45
- return new ReviewState(slug, parsed);
79
+ const statePath = reviewStateFile(slug, rootDir);
80
+ if (!statePath || !fs.existsSync(statePath)) {
81
+ return null;
82
+ }
83
+ try {
84
+ const raw = fs.readFileSync(statePath, 'utf8');
85
+ const parsed = JSON.parse(raw);
86
+ if (parsed && parsed.reviewer && parsed.implementer) {
87
+ return new ReviewState(slug, parsed);
88
+ }
89
+ return null;
90
+ }
91
+ catch {
92
+ return null;
46
93
  }
47
- return null;
48
- } catch (_) {
49
- return null;
50
- }
51
94
  }
52
-
53
95
  /**
54
96
  * Resolve the review-provider identity for a review workflow path.
55
97
  *
@@ -61,197 +103,188 @@ function readReviewState(slug, rootDir = process.cwd()) {
61
103
  * @param {{readReviewStateFn?: Function}} [options]
62
104
  * @returns {{ identityUser: string|null, commentIdentityUser: string|null, forgejoUser: string|null, commentForgejoUser: string|null, reviewState: ReviewState|null, source: 'review-state'|null }}
63
105
  */
64
- function resolveReviewIdentity(slug, rootDir = process.cwd(), /** @type {{readReviewStateFn?: Function}} */ options = {}) {
65
- const readReviewStateFn = options.readReviewStateFn || readReviewState;
66
- const reviewState = readReviewStateFn(slug, rootDir);
67
- const reviewerUser = reviewState ? (reviewState.reviewer || reviewState.implementer || null) : null;
68
- const implementerUser = reviewState ? (reviewState.implementer || reviewState.reviewer || null) : null;
69
-
70
- return {
71
- identityUser: reviewerUser || null,
72
- commentIdentityUser: implementerUser || null,
73
- // Legacy aliases kept for compatibility while generic review orchestration
74
- // moves to provider-neutral option names.
75
- forgejoUser: reviewerUser || null,
76
- commentForgejoUser: implementerUser || null,
77
- reviewState,
78
- source: reviewerUser ? 'review-state' : null
79
- };
106
+ function resolveReviewIdentity(slug, rootDir = process.cwd(), options = {}) {
107
+ const readReviewStateFn = options.readReviewStateFn || readReviewState;
108
+ const reviewState = readReviewStateFn(slug, rootDir);
109
+ const reviewerUser = reviewState ? (reviewState.reviewer || reviewState.implementer || null) : null;
110
+ const implementerUser = reviewState ? (reviewState.implementer || reviewState.reviewer || null) : null;
111
+ return {
112
+ identityUser: reviewerUser || null,
113
+ commentIdentityUser: implementerUser || null,
114
+ forgejoUser: reviewerUser || null,
115
+ commentForgejoUser: implementerUser || null,
116
+ reviewState,
117
+ source: reviewerUser ? 'review-state' : null
118
+ };
80
119
  }
81
-
82
120
  /**
83
121
  * Manager class for mission review state.
84
122
  * Expanded to include phase, disposition, and metadata for canonical state ownership.
85
123
  */
86
- const VALID_PHASES = ['reviewing', 'fixing', 'pending-approval', 'approved'];
124
+ exports.VALID_PHASES = ['reviewing', 'fixing', 'pending-approval', 'approved'];
87
125
  const PHASE_ALIASES = new Map([
88
- ['review', 'reviewing'],
89
- ['rewiewing', 'reviewing'],
90
- ['fix', 'fixing'],
91
- ['pending_approval', 'pending-approval'],
92
- ['pending approval', 'pending-approval']
126
+ ['review', 'reviewing'],
127
+ ['rewiewing', 'reviewing'],
128
+ ['fix', 'fixing'],
129
+ ['pending_approval', 'pending-approval'],
130
+ ['pending approval', 'pending-approval']
93
131
  ]);
94
-
95
132
  const PHASE_TRANSITIONS = {
96
- 'reviewing': ['fixing', 'approved'],
97
- 'fixing': ['reviewing', 'pending-approval'],
98
- 'pending-approval': ['reviewing'],
99
- 'approved': []
133
+ 'reviewing': ['fixing', 'approved'],
134
+ 'fixing': ['reviewing', 'pending-approval'],
135
+ 'pending-approval': ['reviewing'],
136
+ 'approved': []
100
137
  };
101
-
102
138
  /** @param {string} disposition */
103
139
  function inferPhaseFromDisposition(disposition) {
104
- switch (String(disposition || '').trim().toUpperCase()) {
105
- case 'APPROVED':
106
- return 'approved';
107
- case 'REQUEST_CHANGES':
108
- case 'COMMENT':
109
- case 'PUSHBACK_ALL':
110
- case 'BLOCKED':
111
- case 'PARKED':
112
- return 'fixing';
113
- case 'CHANGES_MADE':
114
- return 'reviewing';
115
- default:
116
- return 'reviewing';
117
- }
140
+ switch (String(disposition || '').trim().toUpperCase()) {
141
+ case 'APPROVED':
142
+ return 'approved';
143
+ case 'REQUEST_CHANGES':
144
+ case 'COMMENT':
145
+ case 'PUSHBACK_ALL':
146
+ case 'BLOCKED':
147
+ case 'PARKED':
148
+ return 'fixing';
149
+ case 'CHANGES_MADE':
150
+ return 'reviewing';
151
+ default:
152
+ return 'reviewing';
153
+ }
118
154
  }
119
-
120
155
  /** @param {string} phase @param {string} disposition */
121
156
  function normalizeReviewPhase(phase, disposition) {
122
- if (!phase) {
123
- return { phase: 'reviewing', original: null, normalized: false };
124
- }
125
-
126
- const raw = String(phase).trim();
127
- const canonical = raw.toLowerCase().replace(/[_\s]+/g, '-');
128
- if (VALID_PHASES.includes(canonical)) {
129
- return { phase: canonical, original: raw, normalized: canonical !== raw };
130
- }
131
-
132
- const alias = PHASE_ALIASES.get(raw.toLowerCase()) || PHASE_ALIASES.get(canonical);
133
- if (alias) {
134
- return { phase: alias, original: raw, normalized: true };
135
- }
136
-
137
- return {
138
- phase: inferPhaseFromDisposition(disposition),
139
- original: raw,
140
- normalized: true
141
- };
157
+ if (!phase) {
158
+ return { phase: 'reviewing', original: null, normalized: false };
159
+ }
160
+ const raw = String(phase).trim();
161
+ const canonical = raw.toLowerCase().replace(/[_\s]+/g, '-');
162
+ if (exports.VALID_PHASES.includes(canonical)) {
163
+ return { phase: canonical, original: raw, normalized: canonical !== raw };
164
+ }
165
+ const alias = PHASE_ALIASES.get(raw.toLowerCase()) || PHASE_ALIASES.get(canonical);
166
+ if (alias) {
167
+ return { phase: alias, original: raw, normalized: true };
168
+ }
169
+ return {
170
+ phase: inferPhaseFromDisposition(disposition),
171
+ original: raw,
172
+ normalized: true
173
+ };
142
174
  }
143
-
144
- /**
145
- * @typedef {{reviewer?: string, implementer?: string, round?: number, startedAt?: string, phase?: string, disposition?: string | null, reviewerRetryCount?: number, implementerRetryCount?: number, metadata?: {[key: string]: any}}} ReviewStateData
146
- */
147
-
148
175
  class ReviewState {
149
- /**
150
- * @param {string} slug
151
- * @param {ReviewStateData} [data]
152
- */
153
- constructor(slug, /** @type {ReviewStateData} */ data = {}) {
154
- const phaseInfo = normalizeReviewPhase(/** @type {string} */ (data.phase || ''), /** @type {string} */ (data.disposition || ''));
155
- this.slug = slug;
156
- this.reviewer = data.reviewer;
157
- this.implementer = data.implementer;
158
- this.round = data.round || 1;
159
- this.startedAt = data.startedAt || new Date().toISOString();
160
- this.phase = phaseInfo.phase;
161
- this.disposition = data.disposition || null;
162
- this.reviewerRetryCount = data.reviewerRetryCount || 0;
163
- this.implementerRetryCount = data.implementerRetryCount || 0;
164
- this.metadata = data.metadata || {};
165
- this.phaseOriginal = phaseInfo.normalized ? phaseInfo.original : null;
166
- }
167
-
168
- /**
169
- * @param {string} slug
170
- * @param {ReviewState|ReviewStateData} dataOrInstance
171
- * @returns {ReviewState}
172
- */
173
- static from(slug, dataOrInstance) {
174
- if (dataOrInstance instanceof ReviewState) {
175
- if (dataOrInstance.slug !== slug) {
176
- throw new Error(`ReviewState slug mismatch: expected "${slug}", got "${dataOrInstance.slug}"`);
177
- }
178
- return dataOrInstance;
176
+ slug;
177
+ reviewer;
178
+ implementer;
179
+ round;
180
+ startedAt;
181
+ phase;
182
+ disposition;
183
+ reviewerRetryCount;
184
+ implementerRetryCount;
185
+ metadata;
186
+ phaseOriginal;
187
+ /**
188
+ * @param {string} slug
189
+ * @param {ReviewStateData} [data]
190
+ */
191
+ constructor(slug, data = {}) {
192
+ const phaseInfo = normalizeReviewPhase(data.phase || '', data.disposition || '');
193
+ this.slug = slug;
194
+ this.reviewer = data.reviewer;
195
+ this.implementer = data.implementer;
196
+ this.round = data.round || 1;
197
+ this.startedAt = data.startedAt || new Date().toISOString();
198
+ this.phase = phaseInfo.phase;
199
+ this.disposition = data.disposition || null;
200
+ this.reviewerRetryCount = data.reviewerRetryCount || 0;
201
+ this.implementerRetryCount = data.implementerRetryCount || 0;
202
+ this.metadata = data.metadata || {};
203
+ this.phaseOriginal = phaseInfo.normalized ? phaseInfo.original : null;
179
204
  }
180
- return new ReviewState(slug, dataOrInstance || {});
181
- }
182
-
183
- /**
184
- * @param {string} nextPhase
185
- * @returns {ReviewState}
186
- */
187
- transitionTo(nextPhase) {
188
- if (!VALID_PHASES.includes(nextPhase)) {
189
- throw new Error(`Invalid phase: "${nextPhase}". Valid: ${VALID_PHASES.join(', ')}`);
205
+ /**
206
+ * @param {string} slug
207
+ * @param {ReviewState|ReviewStateData} dataOrInstance
208
+ * @returns {ReviewState}
209
+ */
210
+ static from(slug, dataOrInstance) {
211
+ if (dataOrInstance instanceof ReviewState) {
212
+ if (dataOrInstance.slug !== slug) {
213
+ throw new Error(`ReviewState slug mismatch: expected "${slug}", got "${dataOrInstance.slug}"`);
214
+ }
215
+ return dataOrInstance;
216
+ }
217
+ return new ReviewState(slug, dataOrInstance || {});
190
218
  }
191
- const transitions = /** @type {{[key: string]: string[]}} */ (PHASE_TRANSITIONS);
192
- const allowedArr = transitions[this.phase];
193
- if (!allowedArr || !allowedArr.includes(nextPhase)) {
194
- throw new Error(`Cannot transition from "${this.phase}" to "${nextPhase}". Allowed: ${(allowedArr || []).join(', ') || 'none'}`);
219
+ /**
220
+ * @param {string} nextPhase
221
+ * @returns {ReviewState}
222
+ */
223
+ transitionTo(nextPhase) {
224
+ if (!exports.VALID_PHASES.includes(nextPhase)) {
225
+ throw new Error(`Invalid phase: "${nextPhase}". Valid: ${exports.VALID_PHASES.join(', ')}`);
226
+ }
227
+ const allowedArr = PHASE_TRANSITIONS[this.phase];
228
+ if (!allowedArr || !allowedArr.includes(nextPhase)) {
229
+ throw new Error(`Cannot transition from "${this.phase}" to "${nextPhase}". Allowed: ${(allowedArr || []).join(', ') || 'none'}`);
230
+ }
231
+ this.phase = nextPhase;
232
+ return this;
195
233
  }
196
- this.phase = nextPhase;
197
- return this;
198
- }
199
-
200
- advanceRound() {
201
- this.round += 1;
202
- this.phase = 'reviewing';
203
- this.disposition = null;
204
- this.reviewerRetryCount = 0;
205
- this.implementerRetryCount = 0;
206
- this.startedAt = new Date().toISOString();
207
- return this;
208
- }
209
-
210
- /**
211
- * @returns {{reviewer?: string, implementer?: string, round?: number, startedAt?: string, phase?: string, disposition?: string, reviewerRetryCount?: number, implementerRetryCount?: number, metadata?: {[key: string]: any}}}
212
- */
213
- toJSON() {
214
- /** @type {{reviewer?: string, implementer?: string, round?: number, startedAt?: string, phase?: string, disposition?: string, reviewerRetryCount?: number, implementerRetryCount?: number, metadata?: {[key: string]: any}}} */
215
- const payload = {
216
- reviewer: this.reviewer,
217
- implementer: this.implementer,
218
- round: this.round,
219
- startedAt: this.startedAt,
220
- phase: this.phase,
221
- disposition: this.disposition || undefined
222
- };
223
-
224
- if (this.reviewerRetryCount > 0) {payload.reviewerRetryCount = this.reviewerRetryCount;}
225
- if (this.implementerRetryCount > 0) {payload.implementerRetryCount = this.implementerRetryCount;}
226
- if (Object.keys(this.metadata || {}).length > 0) {payload.metadata = this.metadata;}
227
-
228
- return payload;
229
- }
230
-
231
- save(worktree = resolveWorktree(this.slug) || process.cwd(), gitFn = git) {
232
- const statePath = reviewStateFile(this.slug, worktree);
233
- if (!statePath) {return false;}
234
-
235
- const payload = this.toJSON();
236
- fs.writeFileSync(statePath, JSON.stringify(payload, null, 2) + '\n', 'utf8');
237
-
238
- const relPath = path.relative(worktree, statePath);
239
- gitFn(['-C', worktree, 'add', relPath]);
240
- const msg = `review-state(${this.slug}): round ${this.round} (${this.phase}) [${this.reviewer} -> ${this.implementer}]${this.disposition ? ` disposition=${this.disposition}` : ''}`;
241
- const result = gitFn(['-C', worktree, 'commit', '-m', msg]);
242
-
243
- if (result.status !== 0) {
244
- const statusResult = gitFn(['-C', worktree, 'status', '--porcelain', relPath]);
245
- if (statusResult.stdout.trim() === '') {
234
+ advanceRound() {
235
+ this.round += 1;
236
+ this.phase = 'reviewing';
237
+ this.disposition = null;
238
+ this.reviewerRetryCount = 0;
239
+ this.implementerRetryCount = 0;
240
+ this.startedAt = new Date().toISOString();
241
+ return this;
242
+ }
243
+ /**
244
+ * @returns {{reviewer?: string, implementer?: string, round?: number, startedAt?: string, phase?: string, disposition?: string, reviewerRetryCount?: number, implementerRetryCount?: number, metadata?: {[key: string]: unknown}}}
245
+ */
246
+ toJSON() {
247
+ const payload = {
248
+ reviewer: this.reviewer,
249
+ implementer: this.implementer,
250
+ round: this.round,
251
+ startedAt: this.startedAt,
252
+ phase: this.phase,
253
+ disposition: this.disposition || undefined
254
+ };
255
+ if (this.reviewerRetryCount > 0) {
256
+ payload.reviewerRetryCount = this.reviewerRetryCount;
257
+ }
258
+ if (this.implementerRetryCount > 0) {
259
+ payload.implementerRetryCount = this.implementerRetryCount;
260
+ }
261
+ if (Object.keys(this.metadata || {}).length > 0) {
262
+ payload.metadata = this.metadata;
263
+ }
264
+ return payload;
265
+ }
266
+ save(worktree = (0, mission_utils_js_1.resolveWorktree)(this.slug) || process.cwd(), gitFn = git_js_1.git) {
267
+ const statePath = reviewStateFile(this.slug, worktree);
268
+ if (!statePath) {
269
+ return false;
270
+ }
271
+ const payload = this.toJSON();
272
+ fs.writeFileSync(statePath, JSON.stringify(payload, null, 2) + '\n', 'utf8');
273
+ const relPath = path.relative(worktree, statePath);
274
+ const result = gitFn(['-C', worktree, 'add', relPath]);
275
+ const msg = `review-state(${this.slug}): round ${this.round} (${this.phase}) [${this.reviewer} -> ${this.implementer}]${this.disposition ? ` disposition=${this.disposition}` : ''}`;
276
+ const commitResult = gitFn(['-C', worktree, 'commit', '-m', msg]);
277
+ if (commitResult.status !== 0) {
278
+ const statusResult = gitFn(['-C', worktree, 'status', '--porcelain', relPath]);
279
+ if (statusResult.stdout.trim() === '') {
280
+ return true;
281
+ }
282
+ fmt_js_1.log.warn(`Failed to commit review state update: ${commitResult.stderr}`);
283
+ }
246
284
  return true;
247
- }
248
- fmt.log.warn(`Failed to commit review state update: ${result.stderr}`);
249
285
  }
250
-
251
- return true;
252
- }
253
286
  }
254
-
287
+ exports.ReviewState = ReviewState;
255
288
  /**
256
289
  * Write and commit the review state for a mission.
257
290
  * Commits to the current branch so the state is visible after session restarts.
@@ -260,11 +293,10 @@ class ReviewState {
260
293
  * @param {ReviewState|object} state
261
294
  * @returns {boolean} true if written successfully
262
295
  */
263
- function writeReviewState(slug, state, worktree = resolveWorktree(slug) || process.cwd(), gitFn = git) {
264
- const instance = state instanceof ReviewState ? state : new ReviewState(slug, state);
265
- return instance.save(worktree, gitFn);
296
+ function writeReviewState(slug, state, worktree = (0, mission_utils_js_1.resolveWorktree)(slug) || process.cwd(), gitFn = git_js_1.git) {
297
+ const instance = state instanceof ReviewState ? state : new ReviewState(slug, state);
298
+ return instance.save(worktree, gitFn);
266
299
  }
267
-
268
300
  /**
269
301
  * Delete the review state file for a mission (used by --reset).
270
302
  * Commits the deletion if the file was tracked.
@@ -272,33 +304,22 @@ function writeReviewState(slug, state, worktree = resolveWorktree(slug) || proce
272
304
  * @param {string} slug
273
305
  * @returns {boolean} true if a file was deleted
274
306
  */
275
- function resetReviewState(slug, worktree = resolveWorktree(slug) || process.cwd(), gitFn = git) {
276
- const statePath = reviewStateFile(slug, worktree);
277
- if (!statePath || !fs.existsSync(statePath)) {return false;}
278
-
279
- fs.unlinkSync(statePath);
280
-
281
- const relPath = path.relative(worktree, statePath);
282
- try {
283
- gitFn(['-C', worktree, 'rm', '--cached', relPath]);
284
- const result = gitFn(['-C', worktree, 'commit', '-m', `review-state(${slug}): reset (--reset flag)`]);
285
- if (result.status !== 0) {
286
- fmt.log.warn(`Failed to commit review state reset: ${result.stderr}`);
307
+ function resetReviewState(slug, worktree = (0, mission_utils_js_1.resolveWorktree)(slug) || process.cwd(), gitFn = git_js_1.git) {
308
+ const statePath = reviewStateFile(slug, worktree);
309
+ if (!statePath || !fs.existsSync(statePath)) {
310
+ return false;
311
+ }
312
+ fs.unlinkSync(statePath);
313
+ const relPath = path.relative(worktree, statePath);
314
+ try {
315
+ gitFn(['-C', worktree, 'rm', '--cached', relPath]);
316
+ const result = gitFn(['-C', worktree, 'commit', '-m', `review-state(${slug}): reset (--reset flag)`]);
317
+ if (result.status !== 0) {
318
+ fmt_js_1.log.warn(`Failed to commit review state reset: ${result.stderr}`);
319
+ }
287
320
  }
288
- } catch (_) {
289
- // file may not have been tracked yet
290
- }
291
-
292
- return true;
321
+ catch {
322
+ // file may not have been tracked yet
323
+ }
324
+ return true;
293
325
  }
294
-
295
- module.exports = {
296
- ReviewState,
297
- VALID_PHASES,
298
- normalizeReviewPhase,
299
- reviewStateFile,
300
- readReviewState,
301
- resolveReviewIdentity,
302
- writeReviewState,
303
- resetReviewState
304
- };