@snipcodeit/mgw 0.4.0 → 0.6.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.
@@ -141,6 +141,10 @@ function requireState () {
141
141
  issueState.dead_letter = false;
142
142
  issueChanged = true;
143
143
  }
144
+ if (!issueState.hasOwnProperty("checkpoint")) {
145
+ issueState.checkpoint = null;
146
+ issueChanged = true;
147
+ }
144
148
  if (issueChanged) {
145
149
  try {
146
150
  fs.writeFileSync(filePath, JSON.stringify(issueState, null, 2), "utf-8");
@@ -165,6 +169,148 @@ function requireState () {
165
169
  }
166
170
  return -1;
167
171
  }
172
+ const CHECKPOINT_SCHEMA_VERSION = 1;
173
+ const CHECKPOINT_STEP_ORDER = ["triage", "plan", "execute", "verify", "pr"];
174
+ function initCheckpoint(pipelineStep) {
175
+ const now = (/* @__PURE__ */ new Date()).toISOString();
176
+ return {
177
+ schema_version: CHECKPOINT_SCHEMA_VERSION,
178
+ pipeline_step: pipelineStep || "triage",
179
+ step_progress: {},
180
+ last_agent_output: null,
181
+ artifacts: [],
182
+ resume: {
183
+ action: null,
184
+ context: {}
185
+ },
186
+ started_at: now,
187
+ updated_at: now,
188
+ step_history: []
189
+ };
190
+ }
191
+ function detectCheckpoint(issueNumber) {
192
+ const issueState = loadActiveIssue(issueNumber);
193
+ if (!issueState) return null;
194
+ const cp = issueState.checkpoint;
195
+ if (!cp || typeof cp !== "object") return null;
196
+ const step = cp.pipeline_step || "triage";
197
+ const stepIndex = CHECKPOINT_STEP_ORDER.indexOf(step);
198
+ if (stepIndex <= 0) return null;
199
+ return {
200
+ pipeline_step: cp.pipeline_step,
201
+ step_progress: cp.step_progress || {},
202
+ artifacts: cp.artifacts || [],
203
+ resume: cp.resume || { action: null, context: {} },
204
+ started_at: cp.started_at || null,
205
+ updated_at: cp.updated_at || null,
206
+ step_history: cp.step_history || []
207
+ };
208
+ }
209
+ function resumeFromCheckpoint(issueNumber) {
210
+ const cp = detectCheckpoint(issueNumber);
211
+ if (!cp) return null;
212
+ const action = cp.resume && cp.resume.action || null;
213
+ const actionToStage = {
214
+ "run-plan-checker": "planning",
215
+ "spawn-executor": "executing",
216
+ "continue-execution": "executing",
217
+ "spawn-verifier": "verifying",
218
+ "create-pr": "pr-pending",
219
+ "begin-execution": "planning"
220
+ };
221
+ const resumeStage = actionToStage[action] || "planning";
222
+ const completedSteps = (cp.step_history || []).map((entry) => entry.step).filter(Boolean);
223
+ return {
224
+ checkpoint: cp,
225
+ resumeStage,
226
+ resumeAction: action || "unknown",
227
+ completedSteps
228
+ };
229
+ }
230
+ function atomicWriteJson(filePath, data) {
231
+ const tmpPath = filePath + ".tmp";
232
+ const content = JSON.stringify(data, null, 2);
233
+ fs.writeFileSync(tmpPath, content, "utf-8");
234
+ fs.renameSync(tmpPath, filePath);
235
+ }
236
+ function clearCheckpoint(issueNumber) {
237
+ const activeDir = getActiveDir();
238
+ if (!fs.existsSync(activeDir)) {
239
+ throw new Error(`No active directory found. Cannot clear checkpoint for #${issueNumber}.`);
240
+ }
241
+ const prefix = String(issueNumber) + "-";
242
+ let entries;
243
+ try {
244
+ entries = fs.readdirSync(activeDir);
245
+ } catch (err) {
246
+ throw new Error(`Cannot read active directory: ${err.message}`, { cause: err });
247
+ }
248
+ const match = entries.find((f) => f.startsWith(prefix) && f.endsWith(".json"));
249
+ if (!match) {
250
+ throw new Error(`No state file found for issue #${issueNumber}.`);
251
+ }
252
+ const filePath = path.join(activeDir, match);
253
+ let issueState;
254
+ try {
255
+ issueState = JSON.parse(fs.readFileSync(filePath, "utf-8"));
256
+ } catch (err) {
257
+ throw new Error(`Cannot parse state file for #${issueNumber}: ${err.message}`, { cause: err });
258
+ }
259
+ const hadCheckpoint = issueState.checkpoint != null;
260
+ issueState.checkpoint = null;
261
+ atomicWriteJson(filePath, issueState);
262
+ return { cleared: hadCheckpoint };
263
+ }
264
+ function updateCheckpoint(issueNumber, data) {
265
+ const activeDir = getActiveDir();
266
+ if (!fs.existsSync(activeDir)) {
267
+ throw new Error(`No active directory found. Cannot update checkpoint for #${issueNumber}.`);
268
+ }
269
+ const prefix = String(issueNumber) + "-";
270
+ let entries;
271
+ try {
272
+ entries = fs.readdirSync(activeDir);
273
+ } catch (err) {
274
+ throw new Error(`Cannot read active directory: ${err.message}`, { cause: err });
275
+ }
276
+ const match = entries.find((f) => f.startsWith(prefix) && f.endsWith(".json"));
277
+ if (!match) {
278
+ throw new Error(`No state file found for issue #${issueNumber}.`);
279
+ }
280
+ const filePath = path.join(activeDir, match);
281
+ let issueState;
282
+ try {
283
+ issueState = JSON.parse(fs.readFileSync(filePath, "utf-8"));
284
+ } catch (err) {
285
+ throw new Error(`Cannot parse state file for #${issueNumber}: ${err.message}`, { cause: err });
286
+ }
287
+ if (!issueState.checkpoint || typeof issueState.checkpoint !== "object") {
288
+ issueState.checkpoint = initCheckpoint();
289
+ }
290
+ const cp = issueState.checkpoint;
291
+ if (data.pipeline_step !== void 0) {
292
+ cp.pipeline_step = data.pipeline_step;
293
+ }
294
+ if (data.last_agent_output !== void 0) {
295
+ cp.last_agent_output = data.last_agent_output;
296
+ }
297
+ if (data.step_progress && typeof data.step_progress === "object") {
298
+ cp.step_progress = Object.assign({}, cp.step_progress, data.step_progress);
299
+ }
300
+ if (data.resume && typeof data.resume === "object") {
301
+ cp.resume = data.resume;
302
+ }
303
+ if (Array.isArray(data.artifacts) && data.artifacts.length > 0) {
304
+ cp.artifacts = (cp.artifacts || []).concat(data.artifacts);
305
+ }
306
+ if (Array.isArray(data.step_history) && data.step_history.length > 0) {
307
+ cp.step_history = (cp.step_history || []).concat(data.step_history);
308
+ }
309
+ cp.updated_at = (/* @__PURE__ */ new Date()).toISOString();
310
+ issueState.checkpoint = cp;
311
+ atomicWriteJson(filePath, issueState);
312
+ return { updated: true, checkpoint: cp };
313
+ }
168
314
  const VALID_LINK_TYPES = /* @__PURE__ */ new Set(["related", "implements", "tracks", "maps-to", "blocked-by"]);
169
315
  function loadCrossRefs() {
170
316
  const filePath = path.join(getMgwDir(), "cross-refs.json");
@@ -318,6 +464,47 @@ function requireState () {
318
464
  }
319
465
  return sorted;
320
466
  }
467
+ function detectProjectState(options = {}) {
468
+ const repoRoot = options.repoRoot || process.cwd();
469
+ const G = typeof options.githubMilestoneCount === "number" ? options.githubMilestoneCount : 0;
470
+ const P = fs.existsSync(path.join(repoRoot, ".planning", "PROJECT.md"));
471
+ const R = fs.existsSync(path.join(repoRoot, ".planning", "ROADMAP.md"));
472
+ const S = fs.existsSync(path.join(repoRoot, ".planning", "STATE.md"));
473
+ const M = fs.existsSync(path.join(repoRoot, ".mgw", "project.json"));
474
+ const signals = { P, R, S, M, G };
475
+ if (M && G > 0) {
476
+ let projectData;
477
+ try {
478
+ const raw = fs.readFileSync(path.join(repoRoot, ".mgw", "project.json"), "utf-8");
479
+ projectData = JSON.parse(raw);
480
+ } catch (_e) {
481
+ return { stateClass: "Diverged", signals };
482
+ }
483
+ const milestones = Array.isArray(projectData.milestones) ? projectData.milestones : [];
484
+ const currentMilestone = typeof projectData.current_milestone === "number" ? projectData.current_milestone : 1;
485
+ const allComplete = milestones.length > 0 && currentMilestone > milestones.length;
486
+ if (allComplete) {
487
+ return { stateClass: "Extend", signals };
488
+ }
489
+ const localCount = milestones.length;
490
+ const countDiff = Math.abs(localCount - G);
491
+ if (countDiff <= 1) {
492
+ return { stateClass: "Aligned", signals };
493
+ } else {
494
+ return { stateClass: "Diverged", signals };
495
+ }
496
+ }
497
+ if (!M && G === 0) {
498
+ if (P && (R || S)) {
499
+ return { stateClass: "GSD-Mid-Exec", signals };
500
+ }
501
+ if (P) {
502
+ return { stateClass: "GSD-Only", signals };
503
+ }
504
+ return { stateClass: "Fresh", signals };
505
+ }
506
+ return { stateClass: "Fresh", signals };
507
+ }
321
508
  state = {
322
509
  getMgwDir,
323
510
  getActiveDir,
@@ -328,11 +515,20 @@ function requireState () {
328
515
  mergeProjectState,
329
516
  migrateProjectState,
330
517
  resolveActiveMilestoneIndex,
518
+ CHECKPOINT_SCHEMA_VERSION,
519
+ CHECKPOINT_STEP_ORDER,
520
+ initCheckpoint,
521
+ atomicWriteJson,
522
+ detectCheckpoint,
523
+ resumeFromCheckpoint,
524
+ clearCheckpoint,
525
+ updateCheckpoint,
331
526
  loadCrossRefs,
332
527
  VALID_LINK_TYPES,
333
528
  parseDependencies,
334
529
  storeDependencies,
335
- topologicalSort
530
+ topologicalSort,
531
+ detectProjectState
336
532
  };
337
533
  return state;
338
534
  }