@snipcodeit/mgw 0.3.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.
@@ -1,9 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var require$$0 = require('fs');
3
+ var require$$2 = require('fs');
4
4
  var require$$1 = require('path');
5
- var require$$0$1 = require('child_process');
6
- var require$$0$2 = require('events');
5
+ var require$$0 = require('child_process');
6
+ var require$$3 = require('os');
7
+ var require$$0$1 = require('events');
7
8
 
8
9
  function getDefaultExportFromCjs(x) {
9
10
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
@@ -15,7 +16,7 @@ var hasRequiredState;
15
16
  function requireState () {
16
17
  if (hasRequiredState) return state;
17
18
  hasRequiredState = 1;
18
- const fs = require$$0;
19
+ const fs = require$$2;
19
20
  const path = require$$1;
20
21
  function getMgwDir() {
21
22
  return path.join(process.cwd(), ".mgw");
@@ -140,6 +141,10 @@ function requireState () {
140
141
  issueState.dead_letter = false;
141
142
  issueChanged = true;
142
143
  }
144
+ if (!issueState.hasOwnProperty("checkpoint")) {
145
+ issueState.checkpoint = null;
146
+ issueChanged = true;
147
+ }
143
148
  if (issueChanged) {
144
149
  try {
145
150
  fs.writeFileSync(filePath, JSON.stringify(issueState, null, 2), "utf-8");
@@ -164,6 +169,148 @@ function requireState () {
164
169
  }
165
170
  return -1;
166
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
+ }
167
314
  const VALID_LINK_TYPES = /* @__PURE__ */ new Set(["related", "implements", "tracks", "maps-to", "blocked-by"]);
168
315
  function loadCrossRefs() {
169
316
  const filePath = path.join(getMgwDir(), "cross-refs.json");
@@ -317,6 +464,47 @@ function requireState () {
317
464
  }
318
465
  return sorted;
319
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
+ }
320
508
  state = {
321
509
  getMgwDir,
322
510
  getActiveDir,
@@ -327,11 +515,20 @@ function requireState () {
327
515
  mergeProjectState,
328
516
  migrateProjectState,
329
517
  resolveActiveMilestoneIndex,
518
+ CHECKPOINT_SCHEMA_VERSION,
519
+ CHECKPOINT_STEP_ORDER,
520
+ initCheckpoint,
521
+ atomicWriteJson,
522
+ detectCheckpoint,
523
+ resumeFromCheckpoint,
524
+ clearCheckpoint,
525
+ updateCheckpoint,
330
526
  loadCrossRefs,
331
527
  VALID_LINK_TYPES,
332
528
  parseDependencies,
333
529
  storeDependencies,
334
- topologicalSort
530
+ topologicalSort,
531
+ detectProjectState
335
532
  };
336
533
  return state;
337
534
  }
@@ -586,7 +783,7 @@ var hasRequiredGithub;
586
783
  function requireGithub () {
587
784
  if (hasRequiredGithub) return github;
588
785
  hasRequiredGithub = 1;
589
- const { execSync } = require$$0$1;
786
+ const { execSync } = require$$0;
590
787
  const { TimeoutError, GitHubApiError } = requireErrors();
591
788
  const { withRetry } = requireRetry();
592
789
  const GH_TIMEOUT_MS = 3e4;
@@ -957,16 +1154,17 @@ function requireOutput () {
957
1154
  return output;
958
1155
  }
959
1156
 
960
- var claude;
961
- var hasRequiredClaude;
1157
+ var providerClaude;
1158
+ var hasRequiredProviderClaude;
962
1159
 
963
- function requireClaude () {
964
- if (hasRequiredClaude) return claude;
965
- hasRequiredClaude = 1;
966
- const { execSync, spawn } = require$$0$1;
1160
+ function requireProviderClaude () {
1161
+ if (hasRequiredProviderClaude) return providerClaude;
1162
+ hasRequiredProviderClaude = 1;
1163
+ const { execSync, spawn } = require$$0;
967
1164
  const path = require$$1;
968
- const fs = require$$0;
1165
+ const fs = require$$2;
969
1166
  const { ClaudeNotAvailableError, TimeoutError } = requireErrors();
1167
+ const PROVIDER_ID = "claude";
970
1168
  function getCommandsDir() {
971
1169
  const dir = path.join(__dirname, "..", "commands");
972
1170
  if (!fs.existsSync(dir)) {
@@ -977,7 +1175,7 @@ This may indicate a corrupted installation. Try reinstalling mgw.`
977
1175
  }
978
1176
  return dir;
979
1177
  }
980
- function assertClaudeAvailable() {
1178
+ function assertAvailable() {
981
1179
  try {
982
1180
  execSync("claude --version", {
983
1181
  encoding: "utf-8",
@@ -1021,7 +1219,7 @@ This may indicate a corrupted installation. Try reinstalling mgw.`
1021
1219
  );
1022
1220
  }
1023
1221
  }
1024
- function invokeClaude(commandFile, userPrompt, opts) {
1222
+ function invoke(commandFile, userPrompt, opts) {
1025
1223
  const o = opts || {};
1026
1224
  const args = ["-p"];
1027
1225
  if (commandFile) {
@@ -1071,12 +1269,204 @@ This may indicate a corrupted installation. Try reinstalling mgw.`
1071
1269
  });
1072
1270
  });
1073
1271
  }
1074
- claude = {
1075
- assertClaudeAvailable,
1076
- invokeClaude,
1077
- getCommandsDir
1272
+ providerClaude = { PROVIDER_ID, assertAvailable, invoke, getCommandsDir };
1273
+ return providerClaude;
1274
+ }
1275
+
1276
+ var providerGemini;
1277
+ var hasRequiredProviderGemini;
1278
+
1279
+ function requireProviderGemini () {
1280
+ if (hasRequiredProviderGemini) return providerGemini;
1281
+ hasRequiredProviderGemini = 1;
1282
+ const { execSync, spawn } = require$$0;
1283
+ const path = require$$1;
1284
+ const fs = require$$2;
1285
+ const os = require$$3;
1286
+ const PROVIDER_ID = "gemini";
1287
+ function getCommandsDir() {
1288
+ const dir = path.join(os.homedir(), ".gemini", "commands", "mgw");
1289
+ if (!fs.existsSync(dir)) {
1290
+ throw new Error(
1291
+ `Commands directory not found at: ${dir}
1292
+ Run: node bin/mgw-install.cjs --provider gemini
1293
+ (or reinstall the mgw package to trigger postinstall)`
1294
+ );
1295
+ }
1296
+ return dir;
1297
+ }
1298
+ function assertAvailable() {
1299
+ try {
1300
+ execSync("gemini --version", {
1301
+ encoding: "utf-8",
1302
+ stdio: ["pipe", "pipe", "pipe"]
1303
+ });
1304
+ } catch (err) {
1305
+ if (err.code === "ENOENT") {
1306
+ console.error(
1307
+ "Error: gemini CLI is not installed.\n\nInstall it with:\n npm install -g @google/gemini-cli\n\nThen run:\n gemini auth"
1308
+ );
1309
+ } else {
1310
+ console.error(
1311
+ "Error: gemini CLI check failed.\nEnsure gemini is installed and on your PATH."
1312
+ );
1313
+ }
1314
+ process.exit(1);
1315
+ }
1316
+ }
1317
+ function invoke(commandFile, userPrompt, opts) {
1318
+ const o = opts || {};
1319
+ if (o.dryRun) {
1320
+ const dryArgs = ["-p"];
1321
+ if (o.model) dryArgs.push("--model", o.model);
1322
+ dryArgs.push(commandFile ? "<system>" + commandFile + "</system> " + (userPrompt || "run") : userPrompt || "run");
1323
+ console.log("Would invoke: gemini " + dryArgs.join(" "));
1324
+ return Promise.resolve({ exitCode: 0, output: "" });
1325
+ }
1326
+ let effectivePrompt = userPrompt || "run";
1327
+ if (commandFile) {
1328
+ const fileContents = fs.readFileSync(commandFile, "utf-8");
1329
+ effectivePrompt = "<system>\n" + fileContents + "\n</system>\n\n" + effectivePrompt;
1330
+ }
1331
+ const args = ["-p"];
1332
+ if (o.model) {
1333
+ args.push("--model", o.model);
1334
+ }
1335
+ args.push(effectivePrompt);
1336
+ return new Promise((resolve, reject) => {
1337
+ const stdio = o.quiet ? ["pipe", "pipe", "pipe"] : ["inherit", "inherit", "inherit"];
1338
+ const child = spawn("gemini", args, { stdio });
1339
+ let output = "";
1340
+ if (o.quiet) {
1341
+ child.stdout.on("data", function(chunk) {
1342
+ output += chunk.toString();
1343
+ });
1344
+ child.stderr.on("data", function(chunk) {
1345
+ output += chunk.toString();
1346
+ });
1347
+ }
1348
+ child.on("error", function(err) {
1349
+ if (err.code === "ENOENT") {
1350
+ reject(new Error("gemini CLI not found. Install with: npm install -g @google/gemini-cli"));
1351
+ } else {
1352
+ reject(err);
1353
+ }
1354
+ });
1355
+ child.on("close", function(code) {
1356
+ resolve({ exitCode: code || 0, output });
1357
+ });
1358
+ });
1359
+ }
1360
+ providerGemini = { PROVIDER_ID, assertAvailable, invoke, getCommandsDir };
1361
+ return providerGemini;
1362
+ }
1363
+
1364
+ var providerOpencode;
1365
+ var hasRequiredProviderOpencode;
1366
+
1367
+ function requireProviderOpencode () {
1368
+ if (hasRequiredProviderOpencode) return providerOpencode;
1369
+ hasRequiredProviderOpencode = 1;
1370
+ const { execSync, spawn } = require$$0;
1371
+ const path = require$$1;
1372
+ const fs = require$$2;
1373
+ const os = require$$3;
1374
+ const PROVIDER_ID = "opencode";
1375
+ function getCommandsDir() {
1376
+ const dir = path.join(os.homedir(), ".opencode", "commands", "mgw");
1377
+ if (!fs.existsSync(dir)) {
1378
+ throw new Error(
1379
+ "Commands directory not found at: " + dir + "\nRun: node bin/mgw-install.cjs --provider opencode\n(or reinstall the mgw package to trigger postinstall)"
1380
+ );
1381
+ }
1382
+ return dir;
1383
+ }
1384
+ function assertAvailable() {
1385
+ try {
1386
+ execSync("opencode --version", {
1387
+ encoding: "utf-8",
1388
+ stdio: ["pipe", "pipe", "pipe"]
1389
+ });
1390
+ } catch (err) {
1391
+ if (err.code === "ENOENT") {
1392
+ console.error(
1393
+ "Error: opencode CLI is not installed.\n\nInstall it with:\n npm install -g opencode-ai\n\n(or see https://opencode.ai for installation instructions)"
1394
+ );
1395
+ } else {
1396
+ console.error(
1397
+ "Error: opencode CLI check failed.\nEnsure opencode is installed and on your PATH."
1398
+ );
1399
+ }
1400
+ process.exit(1);
1401
+ }
1402
+ }
1403
+ function invoke(commandFile, userPrompt, opts) {
1404
+ const o = opts || {};
1405
+ const args = ["run"];
1406
+ if (commandFile) {
1407
+ args.push("--system-prompt", commandFile);
1408
+ }
1409
+ if (o.model) {
1410
+ args.push("--model", o.model);
1411
+ }
1412
+ args.push(userPrompt || "run");
1413
+ if (o.dryRun) {
1414
+ console.log("Would invoke: opencode " + args.join(" "));
1415
+ return Promise.resolve({ exitCode: 0, output: "" });
1416
+ }
1417
+ return new Promise(function(resolve, reject) {
1418
+ const stdio = o.quiet ? ["pipe", "pipe", "pipe"] : ["inherit", "inherit", "inherit"];
1419
+ const child = spawn("opencode", args, { stdio });
1420
+ let output = "";
1421
+ if (o.quiet) {
1422
+ child.stdout.on("data", function(chunk) {
1423
+ output += chunk.toString();
1424
+ });
1425
+ child.stderr.on("data", function(chunk) {
1426
+ output += chunk.toString();
1427
+ });
1428
+ }
1429
+ child.on("error", function(err) {
1430
+ if (err.code === "ENOENT") {
1431
+ reject(new Error("opencode CLI not found. See https://opencode.ai for installation instructions."));
1432
+ } else {
1433
+ reject(err);
1434
+ }
1435
+ });
1436
+ child.on("close", function(code) {
1437
+ resolve({ exitCode: code || 0, output });
1438
+ });
1439
+ });
1440
+ }
1441
+ providerOpencode = { PROVIDER_ID, assertAvailable, invoke, getCommandsDir };
1442
+ return providerOpencode;
1443
+ }
1444
+
1445
+ var providerManager;
1446
+ var hasRequiredProviderManager;
1447
+
1448
+ function requireProviderManager () {
1449
+ if (hasRequiredProviderManager) return providerManager;
1450
+ hasRequiredProviderManager = 1;
1451
+ const registry = {
1452
+ claude: requireProviderClaude(),
1453
+ gemini: requireProviderGemini(),
1454
+ opencode: requireProviderOpencode()
1078
1455
  };
1079
- return claude;
1456
+ function getProvider(providerId) {
1457
+ const id = providerId || "claude";
1458
+ const provider = registry[id];
1459
+ if (!provider) {
1460
+ const available = Object.keys(registry).join(", ");
1461
+ throw new Error(`Unknown provider: "${id}". Available: ${available}`);
1462
+ }
1463
+ return provider;
1464
+ }
1465
+ function listProviders() {
1466
+ return Object.keys(registry);
1467
+ }
1468
+ providerManager = { ProviderManager: { getProvider, listProviders } };
1469
+ return providerManager;
1080
1470
  }
1081
1471
 
1082
1472
  var spinner;
@@ -1187,7 +1577,7 @@ function requireLogger () {
1187
1577
  if (hasRequiredLogger) return logger;
1188
1578
  hasRequiredLogger = 1;
1189
1579
  const path = require$$1;
1190
- const fs = require$$0;
1580
+ const fs = require$$2;
1191
1581
  function getLogDir(repoRoot) {
1192
1582
  const root = repoRoot || process.cwd();
1193
1583
  const logDir = path.join(root, ".mgw", "logs");
@@ -1807,7 +2197,7 @@ var hasRequiredKeyboard;
1807
2197
  function requireKeyboard () {
1808
2198
  if (hasRequiredKeyboard) return keyboard;
1809
2199
  hasRequiredKeyboard = 1;
1810
- const { EventEmitter } = require$$0$2;
2200
+ const { EventEmitter } = require$$0$1;
1811
2201
  const DEFAULT_BINDINGS = {
1812
2202
  // Scroll
1813
2203
  "j": "scroll-down",
@@ -2426,11 +2816,12 @@ function requireTui () {
2426
2816
  }
2427
2817
 
2428
2818
  exports.getDefaultExportFromCjs = getDefaultExportFromCjs;
2429
- exports.requireClaude = requireClaude;
2430
2819
  exports.requireErrors = requireErrors;
2431
2820
  exports.requireGithub = requireGithub;
2432
2821
  exports.requireLogger = requireLogger;
2433
2822
  exports.requireOutput = requireOutput;
2823
+ exports.requireProviderClaude = requireProviderClaude;
2824
+ exports.requireProviderManager = requireProviderManager;
2434
2825
  exports.requireRetry = requireRetry;
2435
2826
  exports.requireSpinner = requireSpinner;
2436
2827
  exports.requireState = requireState;