@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,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var index$1 = require('../index-s7v-ifd0.cjs');
3
+ var index$1 = require('../index-CHrVAIMY.cjs');
4
4
  var require$$0 = require('child_process');
5
5
  var require$$1 = require('path');
6
- var require$$2 = require('os');
7
- var require$$0$1 = require('fs');
6
+ var require$$3 = require('os');
7
+ var require$$2 = require('fs');
8
8
  require('events');
9
9
 
10
10
  var pipeline;
@@ -145,6 +145,500 @@ function requirePipeline () {
145
145
  return pipeline;
146
146
  }
147
147
 
148
+ var agentErrors;
149
+ var hasRequiredAgentErrors;
150
+
151
+ function requireAgentErrors () {
152
+ if (hasRequiredAgentErrors) return agentErrors;
153
+ hasRequiredAgentErrors = 1;
154
+ const { MgwError } = index$1.requireErrors();
155
+ const SEVERITY_LEVELS = Object.freeze({
156
+ critical: { name: "critical", weight: 4, description: "Agent produced dangerous or misleading results" },
157
+ high: { name: "high", weight: 3, description: "Agent failed to produce usable output" },
158
+ medium: { name: "medium", weight: 2, description: "Agent partially succeeded but gaps remain" },
159
+ low: { name: "low", weight: 1, description: "Minor issue, likely recoverable automatically" }
160
+ });
161
+ const AGENT_FAILURE_TYPES = Object.freeze({
162
+ timeout: Object.freeze({
163
+ code: "AGENT_ERR_TIMEOUT",
164
+ name: "timeout",
165
+ severity: "high",
166
+ description: "Agent exceeded turn limit or wall-clock timeout without completing its task",
167
+ recovery: "Retry with reduced scope \u2014 split the task into smaller sub-tasks or increase the turn budget",
168
+ retryable: true
169
+ }),
170
+ "malformed-output": Object.freeze({
171
+ code: "AGENT_ERR_MALFORMED_OUTPUT",
172
+ name: "malformed-output",
173
+ severity: "high",
174
+ description: "Agent returned output that cannot be parsed or does not match the expected format",
175
+ recovery: "Retry with explicit format instructions \u2014 add structured output examples to the prompt",
176
+ retryable: true
177
+ }),
178
+ "partial-completion": Object.freeze({
179
+ code: "AGENT_ERR_PARTIAL_COMPLETION",
180
+ name: "partial-completion",
181
+ severity: "medium",
182
+ description: "Agent completed some but not all assigned tasks \u2014 partial artifacts exist",
183
+ recovery: "Spawn a continuation agent for the remaining tasks using the partial output as context",
184
+ retryable: true
185
+ }),
186
+ hallucination: Object.freeze({
187
+ code: "AGENT_ERR_HALLUCINATION",
188
+ name: "hallucination",
189
+ severity: "critical",
190
+ description: "Agent claimed success but required artifacts are missing or do not match expectations",
191
+ recovery: "Reject all results and retry with a verification-first approach \u2014 require artifact proof before completion",
192
+ retryable: false
193
+ }),
194
+ "permission-denied": Object.freeze({
195
+ code: "AGENT_ERR_PERMISSION_DENIED",
196
+ name: "permission-denied",
197
+ severity: "high",
198
+ description: "Agent was blocked by a pre-commit hook, sandbox restriction, or file permission",
199
+ recovery: "Escalate to human operator \u2014 review sandbox configuration and hook settings",
200
+ retryable: false
201
+ })
202
+ });
203
+ const _codeToType = /* @__PURE__ */ new Map();
204
+ for (const [key, def] of Object.entries(AGENT_FAILURE_TYPES)) {
205
+ _codeToType.set(def.code, { key, ...def });
206
+ }
207
+ class AgentFailureError extends MgwError {
208
+ /**
209
+ * @param {string} message - Human-readable error description
210
+ * @param {object} [opts]
211
+ * @param {string} [opts.agentType] - GSD agent type (gsd-planner, gsd-executor, etc.)
212
+ * @param {string} [opts.failureType] - Failure type key from AGENT_FAILURE_TYPES
213
+ * @param {string[]} [opts.artifacts] - List of expected artifacts that are missing/malformed
214
+ * @param {string} [opts.stage] - Pipeline stage where failure occurred
215
+ * @param {number} [opts.issueNumber] - Related GitHub issue number
216
+ * @param {Error} [opts.cause] - Original error
217
+ */
218
+ constructor(message, opts) {
219
+ const o = opts || {};
220
+ const failureDef = o.failureType ? AGENT_FAILURE_TYPES[o.failureType] : null;
221
+ const code = failureDef ? failureDef.code : "AGENT_ERR_UNKNOWN";
222
+ super(message, { code, stage: o.stage, issueNumber: o.issueNumber, cause: o.cause });
223
+ this.name = "AgentFailureError";
224
+ this.agentType = o.agentType || null;
225
+ this.failureType = o.failureType || null;
226
+ this.artifacts = Array.isArray(o.artifacts) ? o.artifacts : [];
227
+ }
228
+ /**
229
+ * Get the full failure type definition for this error.
230
+ * @returns {object|null}
231
+ */
232
+ getFailureDefinition() {
233
+ if (!this.failureType) return null;
234
+ return AGENT_FAILURE_TYPES[this.failureType] || null;
235
+ }
236
+ /**
237
+ * Get the severity level for this error.
238
+ * @returns {string|null}
239
+ */
240
+ getSeverity() {
241
+ const def = this.getFailureDefinition();
242
+ return def ? def.severity : null;
243
+ }
244
+ /**
245
+ * Check whether this failure type is safe to auto-retry.
246
+ * @returns {boolean}
247
+ */
248
+ isRetryable() {
249
+ const def = this.getFailureDefinition();
250
+ return def ? def.retryable : false;
251
+ }
252
+ }
253
+ const CLASSIFICATION_PATTERNS = [
254
+ // Timeout patterns
255
+ { pattern: "turn limit", type: "timeout" },
256
+ { pattern: "max turns", type: "timeout" },
257
+ { pattern: "context window exhausted", type: "timeout" },
258
+ { pattern: "exceeded.*timeout", type: "timeout" },
259
+ { pattern: "agent timed out", type: "timeout" },
260
+ { pattern: "wall.?clock.*exceeded", type: "timeout" },
261
+ // Malformed output patterns
262
+ { pattern: "unparseable", type: "malformed-output" },
263
+ { pattern: "invalid json", type: "malformed-output" },
264
+ { pattern: "parse error", type: "malformed-output" },
265
+ { pattern: "unexpected token", type: "malformed-output" },
266
+ { pattern: "malformed output", type: "malformed-output" },
267
+ { pattern: "missing required field", type: "malformed-output" },
268
+ { pattern: "output format", type: "malformed-output" },
269
+ { pattern: "expected.*format", type: "malformed-output" },
270
+ // Partial completion patterns
271
+ { pattern: "partial completion", type: "partial-completion" },
272
+ { pattern: "incomplete.*tasks", type: "partial-completion" },
273
+ { pattern: "completed.*of.*tasks", type: "partial-completion" },
274
+ { pattern: "remaining tasks", type: "partial-completion" },
275
+ { pattern: "some tasks failed", type: "partial-completion" },
276
+ // Hallucination patterns (check before generic patterns)
277
+ { pattern: "artifacts? missing", type: "hallucination" },
278
+ { pattern: "claimed success.*missing", type: "hallucination" },
279
+ { pattern: "file.*not found.*after", type: "hallucination" },
280
+ { pattern: "verification failed.*not exist", type: "hallucination" },
281
+ { pattern: "hallucination", type: "hallucination" },
282
+ { pattern: "phantom", type: "hallucination" },
283
+ // Permission denied patterns
284
+ { pattern: "permission denied", type: "permission-denied" },
285
+ { pattern: "access denied", type: "permission-denied" },
286
+ { pattern: "hook.*failed", type: "permission-denied" },
287
+ { pattern: "pre.?commit.*rejected", type: "permission-denied" },
288
+ { pattern: "sandbox.*blocked", type: "permission-denied" },
289
+ { pattern: "sandbox.*violation", type: "permission-denied" },
290
+ { pattern: "eacces", type: "permission-denied" }
291
+ ];
292
+ function classifyAgentFailure(error, context) {
293
+ if (!error || typeof error !== "object") return null;
294
+ const message = (error.message || "").toLowerCase();
295
+ const code = (error.code || "").toLowerCase();
296
+ const ctx = context || {};
297
+ if (ctx.expectedArtifacts && ctx.actualArtifacts) {
298
+ const expected = new Set(ctx.expectedArtifacts);
299
+ const actual = new Set(ctx.actualArtifacts);
300
+ const missing = [...expected].filter((a) => !actual.has(a));
301
+ if (missing.length > 0 && ctx.tasksCompleted > 0) {
302
+ const def = AGENT_FAILURE_TYPES.hallucination;
303
+ return { type: "hallucination", code: def.code, severity: def.severity, confidence: "high" };
304
+ }
305
+ }
306
+ if (typeof ctx.tasksTotal === "number" && typeof ctx.tasksCompleted === "number") {
307
+ if (ctx.tasksCompleted > 0 && ctx.tasksCompleted < ctx.tasksTotal) {
308
+ const def = AGENT_FAILURE_TYPES["partial-completion"];
309
+ return { type: "partial-completion", code: def.code, severity: def.severity, confidence: "high" };
310
+ }
311
+ }
312
+ if (code === "eacces" || code === "eperm") {
313
+ const def = AGENT_FAILURE_TYPES["permission-denied"];
314
+ return { type: "permission-denied", code: def.code, severity: def.severity, confidence: "high" };
315
+ }
316
+ for (const { pattern, type } of CLASSIFICATION_PATTERNS) {
317
+ const regex = new RegExp(pattern, "i");
318
+ if (regex.test(message)) {
319
+ const def = AGENT_FAILURE_TYPES[type];
320
+ return { type, code: def.code, severity: def.severity, confidence: "medium" };
321
+ }
322
+ }
323
+ return null;
324
+ }
325
+ function getRecoveryAction(failureType) {
326
+ const def = AGENT_FAILURE_TYPES[failureType];
327
+ if (!def) return null;
328
+ return {
329
+ action: def.recovery,
330
+ retryable: def.retryable,
331
+ severity: def.severity
332
+ };
333
+ }
334
+ function isRetryable(failureType) {
335
+ const def = AGENT_FAILURE_TYPES[failureType];
336
+ return def ? def.retryable : false;
337
+ }
338
+ function compareSeverity(a, b) {
339
+ const weightA = (SEVERITY_LEVELS[a] || { weight: 0 }).weight;
340
+ const weightB = (SEVERITY_LEVELS[b] || { weight: 0 }).weight;
341
+ return weightA - weightB;
342
+ }
343
+ function getFailureByCode(errorCode) {
344
+ return _codeToType.get(errorCode) || null;
345
+ }
346
+ agentErrors = {
347
+ // Constants
348
+ AGENT_FAILURE_TYPES,
349
+ SEVERITY_LEVELS,
350
+ // Error class
351
+ AgentFailureError,
352
+ // Classification
353
+ classifyAgentFailure,
354
+ // Recovery
355
+ getRecoveryAction,
356
+ isRetryable,
357
+ // Utilities
358
+ compareSeverity,
359
+ getFailureByCode
360
+ };
361
+ return agentErrors;
362
+ }
363
+
364
+ var modelFallback;
365
+ var hasRequiredModelFallback;
366
+
367
+ function requireModelFallback () {
368
+ if (hasRequiredModelFallback) return modelFallback;
369
+ hasRequiredModelFallback = 1;
370
+ const fs = require$$2;
371
+ const path = require$$1;
372
+ const { MgwError } = index$1.requireErrors();
373
+ let classifyAgentFailure = null;
374
+ let _AGENT_FAILURE_TYPES = null;
375
+ try {
376
+ const agentErrors = requireAgentErrors();
377
+ classifyAgentFailure = agentErrors.classifyAgentFailure;
378
+ _AGENT_FAILURE_TYPES = agentErrors.AGENT_FAILURE_TYPES;
379
+ } catch (_) {
380
+ }
381
+ let classifyFailure = null;
382
+ try {
383
+ const retry = index$1.requireRetry();
384
+ classifyFailure = retry.classifyFailure;
385
+ } catch (_) {
386
+ }
387
+ const DEFAULT_FALLBACK_CHAINS = Object.freeze({
388
+ "gsd-planner": Object.freeze(["inherit", "sonnet", "haiku"]),
389
+ "gsd-executor": Object.freeze(["sonnet", "haiku"]),
390
+ "gsd-verifier": Object.freeze(["sonnet", "haiku"]),
391
+ "gsd-plan-checker": Object.freeze(["sonnet", "haiku"]),
392
+ "general-purpose": Object.freeze(["sonnet", "haiku"])
393
+ });
394
+ const NON_FALLBACK_FAILURE_TYPES = /* @__PURE__ */ new Set([
395
+ "hallucination",
396
+ // Unsafe — different model might hallucinate differently
397
+ "permission-denied"
398
+ // Environment issue — model change won't help
399
+ ]);
400
+ class ModelFallbackError extends MgwError {
401
+ /**
402
+ * @param {string} message
403
+ * @param {object} [opts]
404
+ * @param {string} [opts.failureType] - Last classified failure type
405
+ * @param {string} [opts.agentType] - GSD agent type
406
+ * @param {string[]} [opts.modelsAttempted] - Models that were tried
407
+ * @param {number} [opts.fallback_attempts] - Number of fallback attempts made
408
+ * @param {Error} [opts.cause] - Last error from final model attempt
409
+ * @param {string} [opts.stage] - Pipeline stage
410
+ * @param {number} [opts.issueNumber] - Related GitHub issue number
411
+ */
412
+ constructor(message, opts) {
413
+ const o = opts || {};
414
+ super(message, {
415
+ code: "MODEL_FALLBACK_EXHAUSTED",
416
+ stage: o.stage,
417
+ issueNumber: o.issueNumber,
418
+ cause: o.cause
419
+ });
420
+ this.name = "ModelFallbackError";
421
+ this.failureType = o.failureType || null;
422
+ this.agentType = o.agentType || null;
423
+ this.modelsAttempted = Array.isArray(o.modelsAttempted) ? o.modelsAttempted : [];
424
+ this.fallback_attempts = typeof o.fallback_attempts === "number" ? o.fallback_attempts : 0;
425
+ }
426
+ }
427
+ class ModelFallbackEngine {
428
+ /**
429
+ * @param {object} [opts]
430
+ * @param {boolean} [opts.enabled] - Master enable/disable switch (overrides config)
431
+ * @param {object} [opts.chains] - Per-agent-type chain overrides
432
+ * Format: { 'gsd-planner': ['inherit', 'sonnet'] }
433
+ * @param {string} [opts.configPath] - Path to .mgw/config.json (default: auto-detect)
434
+ */
435
+ constructor(opts) {
436
+ const o = opts || {};
437
+ const fileConfig = ModelFallbackEngine.loadConfig(o.configPath || null);
438
+ if (typeof o.enabled === "boolean") {
439
+ this._enabled = o.enabled;
440
+ } else if (typeof fileConfig.enabled === "boolean") {
441
+ this._enabled = fileConfig.enabled;
442
+ } else {
443
+ this._enabled = false;
444
+ }
445
+ this._chains = Object.assign(
446
+ {},
447
+ DEFAULT_FALLBACK_CHAINS,
448
+ fileConfig.chains || {},
449
+ o.chains || {}
450
+ );
451
+ }
452
+ // -------------------------------------------------------------------------
453
+ // Public API
454
+ // -------------------------------------------------------------------------
455
+ /**
456
+ * Check whether model fallback is enabled.
457
+ * @returns {boolean}
458
+ */
459
+ get enabled() {
460
+ return this._enabled;
461
+ }
462
+ /**
463
+ * Resolve the ordered fallback chain for an agent type.
464
+ *
465
+ * If model_fallback is disabled, returns an array with only the primary
466
+ * model (no fallback — preserves backward-compatible behavior).
467
+ *
468
+ * @param {string} agentType - GSD agent type (e.g. 'gsd-planner')
469
+ * @returns {string[]} Ordered model chain (first = primary, rest = fallbacks)
470
+ */
471
+ resolveFallbackChain(agentType) {
472
+ const chain = this._chains[agentType] || this._chains["general-purpose"] || ["sonnet"];
473
+ if (!this._enabled) {
474
+ return [chain[0]];
475
+ }
476
+ return [...chain];
477
+ }
478
+ /**
479
+ * Execute an async function with model-tier fallback.
480
+ *
481
+ * Takes a function that receives a model name and attempts execution
482
+ * with each model in the fallback chain until one succeeds or the
483
+ * chain is exhausted.
484
+ *
485
+ * @param {(modelName: string) => Promise<*>} fn - Async function that accepts model name
486
+ * @param {object} [opts]
487
+ * @param {string} [opts.agentType] - GSD agent type for chain lookup
488
+ * @param {function} [opts.onFallback] - Callback on fallback: (fromModel, toModel, attempt, error) => void
489
+ * @param {AbortSignal} [opts.signal] - AbortSignal to cancel fallback attempts
490
+ * @returns {Promise<{ result: *, model: string, fallback_attempts: number, total_models_tried: number }>}
491
+ * @throws {ModelFallbackError} If all models in chain are exhausted
492
+ * @throws {Error} Original error if failure type is non-fallback-eligible
493
+ */
494
+ async executeWithFallback(fn, opts) {
495
+ const o = opts || {};
496
+ const agentType = o.agentType || "general-purpose";
497
+ const onFallback = typeof o.onFallback === "function" ? o.onFallback : null;
498
+ const signal = o.signal || null;
499
+ const chain = this.resolveFallbackChain(agentType);
500
+ const modelsAttempted = [];
501
+ let lastError = null;
502
+ let fallbackAttempts = 0;
503
+ for (let i = 0; i < chain.length; i++) {
504
+ const model = chain[i];
505
+ if (signal && signal.aborted) {
506
+ throw lastError || new MgwError("Fallback aborted by signal", { code: "FALLBACK_ABORTED" });
507
+ }
508
+ modelsAttempted.push(model);
509
+ try {
510
+ const result = await fn(model);
511
+ return {
512
+ result,
513
+ model,
514
+ fallback_attempts: fallbackAttempts,
515
+ total_models_tried: modelsAttempted.length
516
+ };
517
+ } catch (err) {
518
+ lastError = err;
519
+ const failureType = this._classifyForFallback(err, agentType);
520
+ if (NON_FALLBACK_FAILURE_TYPES.has(failureType)) {
521
+ throw err;
522
+ }
523
+ if (i < chain.length - 1) {
524
+ fallbackAttempts++;
525
+ if (onFallback) {
526
+ try {
527
+ onFallback(model, chain[i + 1], fallbackAttempts, err);
528
+ } catch (_) {
529
+ }
530
+ }
531
+ }
532
+ }
533
+ }
534
+ throw new ModelFallbackError(
535
+ `Model fallback exhausted for ${agentType}: tried ${modelsAttempted.join(", ")}`,
536
+ {
537
+ failureType: this._classifyForFallback(lastError, agentType),
538
+ agentType,
539
+ modelsAttempted,
540
+ fallback_attempts: fallbackAttempts,
541
+ cause: lastError
542
+ }
543
+ );
544
+ }
545
+ // -------------------------------------------------------------------------
546
+ // Internal: error classification for fallback decisions
547
+ // -------------------------------------------------------------------------
548
+ /**
549
+ * Classify an error to determine if model fallback should be attempted.
550
+ *
551
+ * Uses agent-errors.cjs classification first (if available), then falls
552
+ * back to generic retry.cjs classification.
553
+ *
554
+ * @param {Error} err - The error to classify
555
+ * @param {string} [agentType] - GSD agent type for context
556
+ * @returns {string} Failure type key (e.g. 'timeout', 'permanent', 'transient')
557
+ * @private
558
+ */
559
+ _classifyForFallback(err, agentType) {
560
+ if (classifyAgentFailure) {
561
+ const agentResult = classifyAgentFailure(err, { agentType });
562
+ if (agentResult && agentResult.type) {
563
+ return agentResult.type;
564
+ }
565
+ }
566
+ if (classifyFailure) {
567
+ const genericResult = classifyFailure(err);
568
+ if (genericResult && genericResult.class) {
569
+ if (genericResult.class === "transient") return "timeout";
570
+ if (genericResult.class === "needs-info") return "needs-info";
571
+ return "permanent";
572
+ }
573
+ }
574
+ return "permanent";
575
+ }
576
+ // -------------------------------------------------------------------------
577
+ // Static: config loading
578
+ // -------------------------------------------------------------------------
579
+ /**
580
+ * Load model fallback configuration from .mgw/config.json.
581
+ *
582
+ * Looks for a `retry` section with `model_fallback` and `fallback_chains`:
583
+ * ```json
584
+ * {
585
+ * "retry": {
586
+ * "model_fallback": true,
587
+ * "fallback_chains": {
588
+ * "gsd-planner": ["inherit", "sonnet"],
589
+ * "gsd-executor": ["sonnet", "haiku"]
590
+ * }
591
+ * }
592
+ * }
593
+ * ```
594
+ *
595
+ * @param {string} [configPath] - Explicit path to config.json. If null,
596
+ * searches for .mgw/config.json relative to cwd.
597
+ * @returns {{ enabled?: boolean, chains?: object }}
598
+ */
599
+ static loadConfig(configPath) {
600
+ const empty = {};
601
+ try {
602
+ const cfgPath = configPath || path.join(process.cwd(), ".mgw", "config.json");
603
+ if (!fs.existsSync(cfgPath)) return empty;
604
+ const raw = fs.readFileSync(cfgPath, "utf-8");
605
+ const config = JSON.parse(raw);
606
+ if (!config || typeof config !== "object") return empty;
607
+ const retry = config.retry;
608
+ if (!retry || typeof retry !== "object") return empty;
609
+ const result = {};
610
+ if (typeof retry.model_fallback === "boolean") {
611
+ result.enabled = retry.model_fallback;
612
+ }
613
+ if (retry.fallback_chains && typeof retry.fallback_chains === "object") {
614
+ const chains = {};
615
+ for (const [agentType, chain] of Object.entries(retry.fallback_chains)) {
616
+ if (Array.isArray(chain) && chain.every((m) => typeof m === "string")) {
617
+ chains[agentType] = chain;
618
+ }
619
+ }
620
+ if (Object.keys(chains).length > 0) {
621
+ result.chains = chains;
622
+ }
623
+ }
624
+ return result;
625
+ } catch (_) {
626
+ return empty;
627
+ }
628
+ }
629
+ }
630
+ modelFallback = {
631
+ // Constants
632
+ DEFAULT_FALLBACK_CHAINS,
633
+ NON_FALLBACK_FAILURE_TYPES,
634
+ // Error class
635
+ ModelFallbackError,
636
+ // Engine
637
+ ModelFallbackEngine
638
+ };
639
+ return modelFallback;
640
+ }
641
+
148
642
  var gsdAdapter;
149
643
  var hasRequiredGsdAdapter;
150
644
 
@@ -153,24 +647,52 @@ function requireGsdAdapter () {
153
647
  hasRequiredGsdAdapter = 1;
154
648
  const { execSync } = require$$0;
155
649
  const path = require$$1;
156
- const os = require$$2;
157
- const fs = require$$0$1;
650
+ const os = require$$3;
651
+ const fs = require$$2;
158
652
  const { TimeoutError, GsdToolError } = index$1.requireErrors();
159
653
  const { STAGES } = requirePipeline();
654
+ function resolveGsdRoot() {
655
+ if (process.env.GSD_TOOLS_PATH) {
656
+ return process.env.GSD_TOOLS_PATH;
657
+ }
658
+ const configPath = path.join(process.cwd(), ".mgw", "config.json");
659
+ if (fs.existsSync(configPath)) {
660
+ try {
661
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
662
+ if (config.gsd_path) {
663
+ return config.gsd_path;
664
+ }
665
+ } catch {
666
+ }
667
+ }
668
+ return path.join(os.homedir(), ".claude", "get-shit-done");
669
+ }
160
670
  function getGsdToolsPath() {
161
- const standard = path.join(
162
- os.homedir(),
163
- ".claude",
164
- "get-shit-done",
165
- "bin",
166
- "gsd-tools.cjs"
167
- );
168
- if (fs.existsSync(standard)) {
169
- return standard;
671
+ const root = resolveGsdRoot();
672
+ const toolPath = path.join(root, "bin", "gsd-tools.cjs");
673
+ if (fs.existsSync(toolPath)) {
674
+ return toolPath;
675
+ }
676
+ const checked = [];
677
+ if (process.env.GSD_TOOLS_PATH) {
678
+ checked.push(` GSD_TOOLS_PATH: ${path.join(process.env.GSD_TOOLS_PATH, "bin", "gsd-tools.cjs")}`);
170
679
  }
680
+ const configPath = path.join(process.cwd(), ".mgw", "config.json");
681
+ if (fs.existsSync(configPath)) {
682
+ try {
683
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
684
+ if (config.gsd_path) {
685
+ checked.push(` .mgw/config.json gsd_path: ${path.join(config.gsd_path, "bin", "gsd-tools.cjs")}`);
686
+ }
687
+ } catch {
688
+ }
689
+ }
690
+ const defaultPath = path.join(os.homedir(), ".claude", "get-shit-done", "bin", "gsd-tools.cjs");
691
+ checked.push(` default: ${defaultPath}`);
171
692
  throw new Error(
172
- `GSD tools not found at ${standard}.
173
- Ensure the get-shit-done framework is installed at ~/.claude/get-shit-done/`
693
+ `GSD tools not found. Checked:
694
+ ${checked.join("\n")}
695
+ Set GSD_TOOLS_PATH or add gsd_path to .mgw/config.json`
174
696
  );
175
697
  }
176
698
  const GSD_TIMEOUT_MS = 15e3;
@@ -288,7 +810,34 @@ Ensure the get-shit-done framework is installed at ~/.claude/get-shit-done/`
288
810
  }
289
811
  return { activeMilestone, currentPhase, planCount };
290
812
  }
813
+ let ModelFallbackEngine = null;
814
+ try {
815
+ const modelFallback = requireModelFallback();
816
+ ModelFallbackEngine = modelFallback.ModelFallbackEngine;
817
+ } catch (_) {
818
+ }
819
+ let _fallbackEngineSingleton = null;
820
+ function getModelFallbackEngine() {
821
+ if (!ModelFallbackEngine) return null;
822
+ if (!_fallbackEngineSingleton) {
823
+ _fallbackEngineSingleton = new ModelFallbackEngine();
824
+ }
825
+ return _fallbackEngineSingleton;
826
+ }
827
+ function resolveFallbackChain(agentType) {
828
+ const engine = getModelFallbackEngine();
829
+ if (engine && engine.enabled) {
830
+ return engine.resolveFallbackChain(agentType);
831
+ }
832
+ try {
833
+ const primary = resolveModel(agentType);
834
+ return [primary];
835
+ } catch (_) {
836
+ return ["sonnet"];
837
+ }
838
+ }
291
839
  gsdAdapter = {
840
+ resolveGsdRoot,
292
841
  getGsdToolsPath,
293
842
  invokeGsdTool,
294
843
  getTimestamp,
@@ -297,7 +846,9 @@ Ensure the get-shit-done framework is installed at ~/.claude/get-shit-done/`
297
846
  historyDigest,
298
847
  roadmapAnalyze,
299
848
  selectGsdRoute,
300
- getGsdState
849
+ getGsdState,
850
+ getModelFallbackEngine,
851
+ resolveFallbackChain
301
852
  };
302
853
  return gsdAdapter;
303
854
  }
@@ -320,7 +871,7 @@ function requireTemplateLoader () {
320
871
  if (hasRequiredTemplateLoader) return templateLoader.exports;
321
872
  hasRequiredTemplateLoader = 1;
322
873
  (function (module) {
323
- const fs = require$$0$1;
874
+ const fs = require$$2;
324
875
  const path = require$$1;
325
876
  const VALID_GSD_ROUTES = [
326
877
  "quick",
@@ -626,6 +1177,21 @@ function requireTemplates () {
626
1177
  return templates;
627
1178
  }
628
1179
 
1180
+ var claude;
1181
+ var hasRequiredClaude;
1182
+
1183
+ function requireClaude () {
1184
+ if (hasRequiredClaude) return claude;
1185
+ hasRequiredClaude = 1;
1186
+ const provider = index$1.requireProviderClaude();
1187
+ claude = {
1188
+ assertClaudeAvailable: provider.assertAvailable,
1189
+ invokeClaude: provider.invoke,
1190
+ getCommandsDir: provider.getCommandsDir
1191
+ };
1192
+ return claude;
1193
+ }
1194
+
629
1195
  var progress;
630
1196
  var hasRequiredProgress;
631
1197
 
@@ -724,6 +1290,415 @@ ${header}
724
1290
  return progress;
725
1291
  }
726
1292
 
1293
+ var issueContext;
1294
+ var hasRequiredIssueContext;
1295
+
1296
+ function requireIssueContext () {
1297
+ if (hasRequiredIssueContext) return issueContext;
1298
+ hasRequiredIssueContext = 1;
1299
+ const { execSync } = require$$0;
1300
+ const fs = require$$2;
1301
+ const path = require$$1;
1302
+ const GH_TIMEOUT_MS = 3e4;
1303
+ const BUDGET = {
1304
+ vision: 2e3,
1305
+ priorSummary: 500,
1306
+ maxPriorSummaries: 5,
1307
+ currentPlan: 4e3,
1308
+ milestone: 1e3
1309
+ };
1310
+ const CACHE_TTL_MINUTES = 30;
1311
+ function run(cmd) {
1312
+ return execSync(cmd, {
1313
+ encoding: "utf-8",
1314
+ stdio: ["pipe", "pipe", "pipe"],
1315
+ timeout: GH_TIMEOUT_MS
1316
+ }).trim();
1317
+ }
1318
+ function getMgwDir() {
1319
+ return path.join(process.cwd(), ".mgw");
1320
+ }
1321
+ function getCacheDir() {
1322
+ const dir = path.join(getMgwDir(), "context-cache");
1323
+ fs.mkdirSync(dir, { recursive: true });
1324
+ return dir;
1325
+ }
1326
+ function truncate(str, maxLen) {
1327
+ if (!str || str.length <= maxLen) return str || "";
1328
+ return str.slice(0, maxLen) + "...";
1329
+ }
1330
+ function parseMetadata(commentBody) {
1331
+ const result = { type: null, phase: null, milestone: null, timestamp: null };
1332
+ if (!commentBody) return result;
1333
+ const match = commentBody.match(/<!--\s*(mgw:[^\n]*?)-->/);
1334
+ if (!match) return result;
1335
+ const header = match[1];
1336
+ const typeMatch = header.match(/mgw:type=(\S+)/);
1337
+ const phaseMatch = header.match(/mgw:phase=(\S+)/);
1338
+ const milestoneMatch = header.match(/mgw:milestone=(\S+)/);
1339
+ const timestampMatch = header.match(/mgw:timestamp=(\S+)/);
1340
+ if (typeMatch) result.type = typeMatch[1];
1341
+ if (phaseMatch) result.phase = parseInt(phaseMatch[1], 10);
1342
+ if (milestoneMatch) result.milestone = parseInt(milestoneMatch[1], 10);
1343
+ if (timestampMatch) result.timestamp = timestampMatch[1];
1344
+ return result;
1345
+ }
1346
+ function formatWithMetadata(content, meta) {
1347
+ const m = meta || {};
1348
+ const ts = m.timestamp || (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
1349
+ const parts = [];
1350
+ if (m.type) parts.push(`mgw:type=${m.type}`);
1351
+ if (m.phase != null) parts.push(`mgw:phase=${m.phase}`);
1352
+ if (m.milestone != null) parts.push(`mgw:milestone=${m.milestone}`);
1353
+ parts.push(`mgw:timestamp=${ts}`);
1354
+ const header = `<!-- ${parts.join(" ")} -->`;
1355
+ return `${header}
1356
+ ${content}`;
1357
+ }
1358
+ async function postPlanningComment(issueNumber, type, content, meta) {
1359
+ const formatted = formatWithMetadata(content, { ...meta, type });
1360
+ const tmpFile = path.join(require$$3.tmpdir(), `mgw-comment-${Date.now()}.md`);
1361
+ try {
1362
+ fs.writeFileSync(tmpFile, formatted, "utf-8");
1363
+ run(`gh issue comment ${issueNumber} --body-file ${JSON.stringify(tmpFile)}`);
1364
+ } finally {
1365
+ try {
1366
+ fs.unlinkSync(tmpFile);
1367
+ } catch (_) {
1368
+ }
1369
+ }
1370
+ }
1371
+ async function findPlanningComments(issueNumber, type) {
1372
+ const raw = run(
1373
+ `gh issue view ${issueNumber} --json comments --jq '.comments'`
1374
+ );
1375
+ const comments = JSON.parse(raw);
1376
+ const results = [];
1377
+ for (const c of comments) {
1378
+ const meta = parseMetadata(c.body);
1379
+ if (meta.type === type) {
1380
+ results.push({
1381
+ body: c.body,
1382
+ meta,
1383
+ createdAt: c.createdAt || ""
1384
+ });
1385
+ }
1386
+ }
1387
+ return results;
1388
+ }
1389
+ async function findLatestComment(issueNumber, type) {
1390
+ const comments = await findPlanningComments(issueNumber, type);
1391
+ if (comments.length === 0) return null;
1392
+ comments.sort((a, b) => {
1393
+ const tsA = a.meta.timestamp || a.createdAt || "";
1394
+ const tsB = b.meta.timestamp || b.createdAt || "";
1395
+ return tsA.localeCompare(tsB);
1396
+ });
1397
+ return comments[comments.length - 1];
1398
+ }
1399
+ async function assembleMilestoneContext(milestoneNum) {
1400
+ const cached = readCache(milestoneNum);
1401
+ if (cached) return Object.values(cached.summaries);
1402
+ const raw = run(
1403
+ `gh issue list --milestone ${JSON.stringify(String(milestoneNum))} --state closed --json number,title --limit 100`
1404
+ );
1405
+ const issues = JSON.parse(raw);
1406
+ const summaries = [];
1407
+ for (const issue of issues) {
1408
+ try {
1409
+ const comment = await findLatestComment(issue.number, "summary");
1410
+ if (comment) {
1411
+ const bodyWithoutHeader = comment.body.replace(/<!--[\s\S]*?-->\n?/, "").trim();
1412
+ summaries.push({
1413
+ issueNumber: issue.number,
1414
+ title: issue.title,
1415
+ summary: truncate(bodyWithoutHeader, BUDGET.priorSummary)
1416
+ });
1417
+ }
1418
+ } catch (_) {
1419
+ }
1420
+ }
1421
+ writeCache(milestoneNum, summaries);
1422
+ return summaries;
1423
+ }
1424
+ async function assembleIssueContext(issueNumber) {
1425
+ const raw = run(
1426
+ `gh issue view ${issueNumber} --json number,title,body,milestone,labels,state`
1427
+ );
1428
+ const issue = JSON.parse(raw);
1429
+ let milestoneContext = [];
1430
+ if (issue.milestone && issue.milestone.number) {
1431
+ try {
1432
+ milestoneContext = await assembleMilestoneContext(issue.milestone.number);
1433
+ } catch (_) {
1434
+ }
1435
+ }
1436
+ let planComment = null;
1437
+ let summaryComment = null;
1438
+ try {
1439
+ planComment = await findLatestComment(issueNumber, "plan");
1440
+ } catch (_) {
1441
+ }
1442
+ try {
1443
+ summaryComment = await findLatestComment(issueNumber, "summary");
1444
+ } catch (_) {
1445
+ }
1446
+ return { issue, milestoneContext, planComment, summaryComment };
1447
+ }
1448
+ async function fetchProjectVision() {
1449
+ try {
1450
+ const projectJsonPath = path.join(getMgwDir(), "project.json");
1451
+ if (fs.existsSync(projectJsonPath)) {
1452
+ const project = JSON.parse(fs.readFileSync(projectJsonPath, "utf-8"));
1453
+ const projectNumber = project.project && project.project.project_board && project.project.project_board.number || "";
1454
+ if (projectNumber) {
1455
+ const owner = run("gh repo view --json owner -q .owner.login");
1456
+ const readme = run(
1457
+ `gh project view ${projectNumber} --owner ${owner} --json readme -q .readme`
1458
+ );
1459
+ if (readme && readme.length > 10) {
1460
+ const visionMatch = readme.match(/##\s*Vision\s*\n([\s\S]*?)(?=\n##\s|\n$|$)/);
1461
+ if (visionMatch && visionMatch[1].trim()) {
1462
+ return visionMatch[1].trim();
1463
+ }
1464
+ const lines = readme.split("\n");
1465
+ const bodyLines = lines.filter((l) => !l.startsWith("# "));
1466
+ const body = bodyLines.join("\n").trim();
1467
+ if (body) return body;
1468
+ }
1469
+ }
1470
+ }
1471
+ } catch (_) {
1472
+ }
1473
+ try {
1474
+ const projectJsonPath = path.join(getMgwDir(), "project.json");
1475
+ if (fs.existsSync(projectJsonPath)) {
1476
+ const project = JSON.parse(fs.readFileSync(projectJsonPath, "utf-8"));
1477
+ const description = project.project && project.project.description || "";
1478
+ if (description) return description;
1479
+ const projectName = project.project && project.project.name || "";
1480
+ if (projectName) return `Project: ${projectName}`;
1481
+ }
1482
+ } catch (_) {
1483
+ }
1484
+ try {
1485
+ const visionBriefPath = path.join(getMgwDir(), "vision-brief.json");
1486
+ if (fs.existsSync(visionBriefPath)) {
1487
+ const brief = JSON.parse(fs.readFileSync(visionBriefPath, "utf-8"));
1488
+ return brief.vision_summary || brief.description || "";
1489
+ }
1490
+ } catch (_) {
1491
+ }
1492
+ return "";
1493
+ }
1494
+ async function buildGSDPromptContext(opts) {
1495
+ const o = opts || {};
1496
+ const sections = [];
1497
+ if (o.includeVision) {
1498
+ try {
1499
+ const vision = await fetchProjectVision();
1500
+ if (vision) {
1501
+ sections.push(`<vision>
1502
+ ${truncate(vision, BUDGET.vision)}
1503
+ </vision>`);
1504
+ }
1505
+ } catch (_) {
1506
+ }
1507
+ }
1508
+ if (o.milestone) {
1509
+ try {
1510
+ const milestoneRaw = run(
1511
+ `gh api repos/$(gh repo view --json nameWithOwner -q .nameWithOwner)/milestones/${o.milestone} --jq '{title: .title, description: .description}'`
1512
+ );
1513
+ const milestoneData = JSON.parse(milestoneRaw);
1514
+ const milestoneInfo = truncate(
1515
+ `${milestoneData.title}
1516
+ ${milestoneData.description || ""}`,
1517
+ BUDGET.milestone
1518
+ );
1519
+ sections.push(`<milestone>
1520
+ ${milestoneInfo}
1521
+ </milestone>`);
1522
+ } catch (_) {
1523
+ }
1524
+ }
1525
+ if (o.includePriorSummaries && o.milestone) {
1526
+ try {
1527
+ const summaries = await assembleMilestoneContext(o.milestone);
1528
+ const prior = summaries.filter((s) => o.issueNumber == null || s.issueNumber !== o.issueNumber).slice(-BUDGET.maxPriorSummaries);
1529
+ if (prior.length > 0) {
1530
+ const priorText = prior.map((s) => `### Issue #${s.issueNumber}: ${s.title}
1531
+ ${s.summary}`).join("\n\n");
1532
+ sections.push(`<prior_phases>
1533
+ ${priorText}
1534
+ </prior_phases>`);
1535
+ }
1536
+ } catch (_) {
1537
+ }
1538
+ }
1539
+ if (o.includeCurrentPlan && o.issueNumber) {
1540
+ try {
1541
+ const planComment = await findLatestComment(o.issueNumber, "plan");
1542
+ if (planComment) {
1543
+ const planBody = planComment.body.replace(/<!--[\s\S]*?-->\n?/, "").trim();
1544
+ sections.push(`<current_phase>
1545
+ ${truncate(planBody, BUDGET.currentPlan)}
1546
+ </current_phase>`);
1547
+ }
1548
+ } catch (_) {
1549
+ }
1550
+ }
1551
+ if (sections.length === 0) return "";
1552
+ return `<mgw_context>
1553
+
1554
+ ${sections.join("\n\n")}
1555
+
1556
+ </mgw_context>`;
1557
+ }
1558
+ async function safeContext(opts) {
1559
+ try {
1560
+ return await buildGSDPromptContext(opts);
1561
+ } catch (_) {
1562
+ return "";
1563
+ }
1564
+ }
1565
+ function readCache(milestoneNum) {
1566
+ try {
1567
+ const cachePath = path.join(getCacheDir(), `milestone-${milestoneNum}.json`);
1568
+ if (!fs.existsSync(cachePath)) return null;
1569
+ const data = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
1570
+ const cachedAt = new Date(data.cached_at);
1571
+ const now = /* @__PURE__ */ new Date();
1572
+ const ageMinutes = (now - cachedAt) / (1e3 * 60);
1573
+ if (ageMinutes > (data.ttl_minutes || CACHE_TTL_MINUTES)) return null;
1574
+ return data;
1575
+ } catch (_) {
1576
+ return null;
1577
+ }
1578
+ }
1579
+ function writeCache(milestoneNum, summaries) {
1580
+ try {
1581
+ const cachePath = path.join(getCacheDir(), `milestone-${milestoneNum}.json`);
1582
+ const summaryMap = {};
1583
+ for (const s of summaries) {
1584
+ summaryMap[String(s.issueNumber)] = s;
1585
+ }
1586
+ const data = {
1587
+ milestone: Number(milestoneNum),
1588
+ cached_at: (/* @__PURE__ */ new Date()).toISOString(),
1589
+ ttl_minutes: CACHE_TTL_MINUTES,
1590
+ summaries: summaryMap
1591
+ };
1592
+ fs.writeFileSync(cachePath, JSON.stringify(data, null, 2), "utf-8");
1593
+ } catch (_) {
1594
+ }
1595
+ }
1596
+ async function rebuildContextCache() {
1597
+ const cacheDir = getCacheDir();
1598
+ try {
1599
+ const files = fs.readdirSync(cacheDir);
1600
+ for (const f of files) {
1601
+ if (f.startsWith("milestone-") && f.endsWith(".json")) {
1602
+ fs.unlinkSync(path.join(cacheDir, f));
1603
+ }
1604
+ }
1605
+ } catch (_) {
1606
+ }
1607
+ let milestones;
1608
+ try {
1609
+ const repo = run("gh repo view --json nameWithOwner -q .nameWithOwner");
1610
+ const raw = run(`gh api repos/${repo}/milestones?state=all --jq '.[].number'`);
1611
+ milestones = raw.split("\n").filter(Boolean).map(Number);
1612
+ } catch (_) {
1613
+ milestones = [];
1614
+ }
1615
+ let totalIssues = 0;
1616
+ for (const num of milestones) {
1617
+ try {
1618
+ const summaries = await assembleMilestoneContext(num);
1619
+ totalIssues += summaries.length;
1620
+ } catch (_) {
1621
+ }
1622
+ }
1623
+ return { issueCount: totalIssues, milestoneCount: milestones.length };
1624
+ }
1625
+ async function updateProjectReadme() {
1626
+ try {
1627
+ const projectJsonPath = path.join(getMgwDir(), "project.json");
1628
+ if (!fs.existsSync(projectJsonPath)) return false;
1629
+ const project = JSON.parse(fs.readFileSync(projectJsonPath, "utf-8"));
1630
+ const board = project.project && project.project.project_board || {};
1631
+ const projectNumber = board.number;
1632
+ if (!projectNumber) return false;
1633
+ const owner = run("gh repo view --json owner -q .owner.login");
1634
+ const projectName = project.project && project.project.name || "";
1635
+ let visionSummary = "";
1636
+ try {
1637
+ const visionBriefPath = path.join(getMgwDir(), "vision-brief.json");
1638
+ if (fs.existsSync(visionBriefPath)) {
1639
+ const brief = JSON.parse(fs.readFileSync(visionBriefPath, "utf-8"));
1640
+ visionSummary = (brief.vision_summary || brief.description || "").slice(0, 500);
1641
+ }
1642
+ } catch (_) {
1643
+ }
1644
+ if (!visionSummary) {
1645
+ visionSummary = project.project && project.project.description || "Project initialized via MGW.";
1646
+ }
1647
+ const milestones = project.milestones || [];
1648
+ const tableLines = ["| # | Milestone | Issues | Status |", "|---|-----------|--------|--------|"];
1649
+ for (let i = 0; i < milestones.length; i++) {
1650
+ const m = milestones[i];
1651
+ const name = m.name || m.title || "Unnamed";
1652
+ const count = (m.issues || []).length;
1653
+ const doneCount = (m.issues || []).filter((iss) => iss.pipeline_stage === "done").length;
1654
+ const state = m.gsd_state || "planned";
1655
+ const progress = count > 0 ? ` (${doneCount}/${count})` : "";
1656
+ const stateLabel = state.charAt(0).toUpperCase() + state.slice(1);
1657
+ tableLines.push(`| ${i + 1} | ${name} | ${count}${progress} | ${stateLabel} |`);
1658
+ }
1659
+ const boardUrl = board.url || "";
1660
+ const readmeBody = `# ${projectName}
1661
+
1662
+ ## Vision
1663
+ ${visionSummary}
1664
+
1665
+ ## Milestones
1666
+ ${tableLines.join("\n")}
1667
+
1668
+ ## Links
1669
+ - [Board](${boardUrl})`;
1670
+ const tmpFile = path.join(require("os").tmpdir(), `mgw-readme-${Date.now()}.md`);
1671
+ try {
1672
+ fs.writeFileSync(tmpFile, readmeBody, "utf-8");
1673
+ run(`gh project edit ${projectNumber} --owner ${owner} --readme "$(cat ${JSON.stringify(tmpFile)})"`);
1674
+ } finally {
1675
+ try {
1676
+ fs.unlinkSync(tmpFile);
1677
+ } catch (_) {
1678
+ }
1679
+ }
1680
+ return true;
1681
+ } catch (_) {
1682
+ return false;
1683
+ }
1684
+ }
1685
+ issueContext = {
1686
+ parseMetadata,
1687
+ formatWithMetadata,
1688
+ postPlanningComment,
1689
+ findPlanningComments,
1690
+ findLatestComment,
1691
+ assembleMilestoneContext,
1692
+ assembleIssueContext,
1693
+ buildGSDPromptContext,
1694
+ safeContext,
1695
+ rebuildContextCache,
1696
+ fetchProjectVision,
1697
+ updateProjectReadme
1698
+ };
1699
+ return issueContext;
1700
+ }
1701
+
727
1702
  var lib;
728
1703
  var hasRequiredLib;
729
1704
 
@@ -737,13 +1712,15 @@ function requireLib () {
737
1712
  ...requireGsdAdapter(),
738
1713
  ...requireTemplates(),
739
1714
  ...index$1.requireOutput(),
740
- ...index$1.requireClaude(),
1715
+ ...requireClaude(),
1716
+ ...index$1.requireProviderManager(),
741
1717
  ...index$1.requireRetry(),
742
1718
  ...index$1.requireSpinner(),
743
1719
  ...requireProgress(),
744
1720
  ...requirePipeline(),
745
1721
  ...index$1.requireErrors(),
746
- ...index$1.requireLogger()
1722
+ ...index$1.requireLogger(),
1723
+ ...requireIssueContext()
747
1724
  };
748
1725
  Object.defineProperty(_exports, "createIssuesBrowser", {
749
1726
  configurable: true,