@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.
- package/README.md +1 -0
- package/bin/mgw-install.cjs +121 -24
- package/commands/board/create.md +192 -0
- package/commands/board/sync.md +44 -0
- package/commands/board/views.md +23 -0
- package/commands/context.md +183 -0
- package/commands/handoff.md +169 -0
- package/commands/issue.md +62 -0
- package/commands/milestone.md +37 -5
- package/commands/project.md +19 -0
- package/commands/run/execute.md +604 -27
- package/commands/run/pr-create.md +88 -0
- package/commands/run/triage.md +199 -2
- package/commands/run/worktree.md +41 -0
- package/commands/sync.md +69 -0
- package/commands/workflows/gsd.md +71 -0
- package/commands/workflows/state.md +340 -1
- package/dist/bin/mgw.cjs +14 -11
- package/dist/{index-s7v-ifd0.cjs → index-CHrVAIMY.cjs} +414 -23
- package/dist/lib/index.cjs +997 -20
- package/package.json +9 -4
package/dist/lib/index.cjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var index$1 = require('../index-
|
|
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$$
|
|
7
|
-
var require$$
|
|
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$$
|
|
157
|
-
const fs = require$$
|
|
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
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
168
|
-
|
|
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
|
|
173
|
-
|
|
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$$
|
|
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
|
-
...
|
|
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,
|