@snipcodeit/mgw 0.2.2 → 0.4.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,12 +1,150 @@
1
1
  'use strict';
2
2
 
3
- var index$1 = require('../index-BiwU0uWA.cjs');
3
+ var index$1 = require('../index-B-_JvYpz.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
+ var pipeline;
11
+ var hasRequiredPipeline;
12
+
13
+ function requirePipeline () {
14
+ if (hasRequiredPipeline) return pipeline;
15
+ hasRequiredPipeline = 1;
16
+ const STAGES = {
17
+ NEW: "new",
18
+ TRIAGED: "triaged",
19
+ NEEDS_INFO: "needs-info",
20
+ NEEDS_SECURITY_REVIEW: "needs-security-review",
21
+ DISCUSSING: "discussing",
22
+ APPROVED: "approved",
23
+ PLANNING: "planning",
24
+ DIAGNOSING: "diagnosing",
25
+ EXECUTING: "executing",
26
+ VERIFYING: "verifying",
27
+ PR_CREATED: "pr-created",
28
+ DONE: "done",
29
+ FAILED: "failed",
30
+ BLOCKED: "blocked"
31
+ };
32
+ const STAGE_SET = new Set(Object.values(STAGES));
33
+ const VALID_TRANSITIONS = {
34
+ [STAGES.NEW]: [STAGES.TRIAGED],
35
+ [STAGES.TRIAGED]: [STAGES.NEEDS_INFO, STAGES.NEEDS_SECURITY_REVIEW, STAGES.DISCUSSING, STAGES.APPROVED, STAGES.PLANNING, STAGES.DIAGNOSING],
36
+ [STAGES.NEEDS_INFO]: [STAGES.TRIAGED],
37
+ [STAGES.NEEDS_SECURITY_REVIEW]: [STAGES.TRIAGED, STAGES.APPROVED],
38
+ [STAGES.DISCUSSING]: [STAGES.TRIAGED, STAGES.APPROVED],
39
+ [STAGES.APPROVED]: [STAGES.PLANNING],
40
+ [STAGES.PLANNING]: [STAGES.EXECUTING],
41
+ [STAGES.DIAGNOSING]: [STAGES.PLANNING],
42
+ [STAGES.EXECUTING]: [STAGES.VERIFYING],
43
+ [STAGES.VERIFYING]: [STAGES.PR_CREATED, STAGES.EXECUTING],
44
+ [STAGES.PR_CREATED]: [STAGES.DONE],
45
+ [STAGES.DONE]: [],
46
+ [STAGES.FAILED]: [STAGES.NEW, STAGES.TRIAGED, STAGES.PLANNING, STAGES.EXECUTING],
47
+ [STAGES.BLOCKED]: [STAGES.NEW, STAGES.TRIAGED, STAGES.PLANNING, STAGES.EXECUTING]
48
+ };
49
+ for (const stage of Object.keys(VALID_TRANSITIONS)) {
50
+ if (stage !== STAGES.DONE && stage !== STAGES.FAILED && stage !== STAGES.BLOCKED) {
51
+ VALID_TRANSITIONS[stage].push(STAGES.FAILED, STAGES.BLOCKED);
52
+ }
53
+ }
54
+ const STAGE_ICONS = {
55
+ [STAGES.NEW]: "\u25CB",
56
+ [STAGES.TRIAGED]: "\u25C7",
57
+ [STAGES.NEEDS_INFO]: "?",
58
+ [STAGES.NEEDS_SECURITY_REVIEW]: "\u2691",
59
+ [STAGES.DISCUSSING]: "\u{1F4AC}",
60
+ [STAGES.APPROVED]: "\u2714",
61
+ [STAGES.PLANNING]: "\u25C6",
62
+ [STAGES.DIAGNOSING]: "\u{1F50D}",
63
+ [STAGES.EXECUTING]: "\u25C6",
64
+ [STAGES.VERIFYING]: "\u25C6",
65
+ [STAGES.PR_CREATED]: "\u2713",
66
+ [STAGES.DONE]: "\u2713",
67
+ [STAGES.FAILED]: "\u2717",
68
+ [STAGES.BLOCKED]: "\u2298"
69
+ };
70
+ const STAGE_LABELS = {
71
+ [STAGES.NEW]: "New",
72
+ [STAGES.TRIAGED]: "Triaged",
73
+ [STAGES.NEEDS_INFO]: "Needs Info",
74
+ [STAGES.NEEDS_SECURITY_REVIEW]: "Needs Security Review",
75
+ [STAGES.DISCUSSING]: "Discussing",
76
+ [STAGES.APPROVED]: "Approved",
77
+ [STAGES.PLANNING]: "Planning",
78
+ [STAGES.DIAGNOSING]: "Diagnosing",
79
+ [STAGES.EXECUTING]: "Executing",
80
+ [STAGES.VERIFYING]: "Verifying",
81
+ [STAGES.PR_CREATED]: "PR Created",
82
+ [STAGES.DONE]: "Done",
83
+ [STAGES.FAILED]: "Failed",
84
+ [STAGES.BLOCKED]: "Blocked"
85
+ };
86
+ function isValidStage(stage) {
87
+ return STAGE_SET.has(stage);
88
+ }
89
+ function isValidTransition(from, to) {
90
+ if (!isValidStage(from) || !isValidStage(to)) return false;
91
+ const allowed = VALID_TRANSITIONS[from];
92
+ return Array.isArray(allowed) && allowed.includes(to);
93
+ }
94
+ const _hooks = [];
95
+ function onTransition(fn) {
96
+ _hooks.push(fn);
97
+ return () => {
98
+ const idx = _hooks.indexOf(fn);
99
+ if (idx !== -1) _hooks.splice(idx, 1);
100
+ };
101
+ }
102
+ function transitionStage(issueState, newStage, context) {
103
+ const currentStage = issueState && issueState.pipeline_stage || STAGES.NEW;
104
+ if (!isValidStage(newStage)) {
105
+ throw new Error(`Invalid target stage: "${newStage}"`);
106
+ }
107
+ if (currentStage === newStage) {
108
+ throw new Error(`Already at stage "${newStage}" \u2014 self-transitions are not allowed`);
109
+ }
110
+ if (!isValidTransition(currentStage, newStage)) {
111
+ throw new Error(
112
+ `Invalid transition: "${currentStage}" \u2192 "${newStage}". Allowed targets: [${(VALID_TRANSITIONS[currentStage] || []).join(", ")}]`
113
+ );
114
+ }
115
+ const newState = Object.assign({}, issueState, {
116
+ pipeline_stage: newStage,
117
+ last_transition: (/* @__PURE__ */ new Date()).toISOString(),
118
+ previous_stage: currentStage
119
+ });
120
+ const ctx = context || {};
121
+ for (const hook of _hooks) {
122
+ try {
123
+ hook(currentStage, newStage, ctx);
124
+ } catch (err) {
125
+ process.stderr.write(`[pipeline] hook error: ${err.message}
126
+ `);
127
+ }
128
+ }
129
+ return newState;
130
+ }
131
+ function clearHooks() {
132
+ _hooks.length = 0;
133
+ }
134
+ pipeline = {
135
+ STAGES,
136
+ VALID_TRANSITIONS,
137
+ STAGE_ICONS,
138
+ STAGE_LABELS,
139
+ isValidStage,
140
+ isValidTransition,
141
+ transitionStage,
142
+ onTransition,
143
+ clearHooks
144
+ };
145
+ return pipeline;
146
+ }
147
+
10
148
  var gsdAdapter;
11
149
  var hasRequiredGsdAdapter;
12
150
 
@@ -15,24 +153,55 @@ function requireGsdAdapter () {
15
153
  hasRequiredGsdAdapter = 1;
16
154
  const { execSync } = require$$0;
17
155
  const path = require$$1;
18
- const os = require$$2;
19
- const fs = require$$0$1;
156
+ const os = require$$3;
157
+ const fs = require$$2;
158
+ const { TimeoutError, GsdToolError } = index$1.requireErrors();
159
+ const { STAGES } = requirePipeline();
160
+ function resolveGsdRoot() {
161
+ if (process.env.GSD_TOOLS_PATH) {
162
+ return process.env.GSD_TOOLS_PATH;
163
+ }
164
+ const configPath = path.join(process.cwd(), ".mgw", "config.json");
165
+ if (fs.existsSync(configPath)) {
166
+ try {
167
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
168
+ if (config.gsd_path) {
169
+ return config.gsd_path;
170
+ }
171
+ } catch {
172
+ }
173
+ }
174
+ return path.join(os.homedir(), ".claude", "get-shit-done");
175
+ }
20
176
  function getGsdToolsPath() {
21
- const standard = path.join(
22
- os.homedir(),
23
- ".claude",
24
- "get-shit-done",
25
- "bin",
26
- "gsd-tools.cjs"
27
- );
28
- if (fs.existsSync(standard)) {
29
- return standard;
177
+ const root = resolveGsdRoot();
178
+ const toolPath = path.join(root, "bin", "gsd-tools.cjs");
179
+ if (fs.existsSync(toolPath)) {
180
+ return toolPath;
181
+ }
182
+ const checked = [];
183
+ if (process.env.GSD_TOOLS_PATH) {
184
+ checked.push(` GSD_TOOLS_PATH: ${path.join(process.env.GSD_TOOLS_PATH, "bin", "gsd-tools.cjs")}`);
185
+ }
186
+ const configPath = path.join(process.cwd(), ".mgw", "config.json");
187
+ if (fs.existsSync(configPath)) {
188
+ try {
189
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
190
+ if (config.gsd_path) {
191
+ checked.push(` .mgw/config.json gsd_path: ${path.join(config.gsd_path, "bin", "gsd-tools.cjs")}`);
192
+ }
193
+ } catch {
194
+ }
30
195
  }
196
+ const defaultPath = path.join(os.homedir(), ".claude", "get-shit-done", "bin", "gsd-tools.cjs");
197
+ checked.push(` default: ${defaultPath}`);
31
198
  throw new Error(
32
- `GSD tools not found at ${standard}.
33
- Ensure the get-shit-done framework is installed at ~/.claude/get-shit-done/`
199
+ `GSD tools not found. Checked:
200
+ ${checked.join("\n")}
201
+ Set GSD_TOOLS_PATH or add gsd_path to .mgw/config.json`
34
202
  );
35
203
  }
204
+ const GSD_TIMEOUT_MS = 15e3;
36
205
  function invokeGsdTool(command, args) {
37
206
  const toolPath = getGsdToolsPath();
38
207
  const argsStr = Array.isArray(args) ? args.map((a) => JSON.stringify(String(a))).join(" ") : "";
@@ -41,13 +210,21 @@ Ensure the get-shit-done framework is installed at ~/.claude/get-shit-done/`
41
210
  try {
42
211
  raw = execSync(cmd, {
43
212
  encoding: "utf-8",
44
- stdio: ["pipe", "pipe", "pipe"]
213
+ stdio: ["pipe", "pipe", "pipe"],
214
+ timeout: GSD_TIMEOUT_MS
45
215
  }).trim();
46
216
  } catch (err) {
217
+ if (err.killed) {
218
+ throw new TimeoutError(
219
+ `GSD tool timed out after ${GSD_TIMEOUT_MS / 1e3}s: ${command}`,
220
+ { timeoutMs: GSD_TIMEOUT_MS, operation: `gsd-tools ${command}` }
221
+ );
222
+ }
47
223
  const stderr = err.stderr ? err.stderr.trim() : "";
48
- throw new Error(
224
+ throw new GsdToolError(
49
225
  `GSD tool command failed: ${command}
50
- ` + (stderr ? `stderr: ${stderr}` : `exit code: ${err.status}`)
226
+ ` + (stderr ? `stderr: ${stderr}` : `exit code: ${err.status}`),
227
+ { command, cause: err }
51
228
  );
52
229
  }
53
230
  if (!raw) return null;
@@ -89,13 +266,13 @@ Ensure the get-shit-done framework is installed at ~/.claude/get-shit-done/`
89
266
  if (/gsd-route:diagnose|needs-diagnosis/.test(labelStr)) {
90
267
  return "diagnose";
91
268
  }
92
- if (stage === "diagnosing") {
269
+ if (stage === STAGES.DIAGNOSING) {
93
270
  return "diagnose";
94
271
  }
95
- if (stage === "executing") {
272
+ if (stage === STAGES.EXECUTING) {
96
273
  return "execute-only";
97
274
  }
98
- if (stage === "verifying") {
275
+ if (stage === STAGES.VERIFYING) {
99
276
  return "verify-only";
100
277
  }
101
278
  if (projectState && projectState.milestones) ;
@@ -140,6 +317,7 @@ Ensure the get-shit-done framework is installed at ~/.claude/get-shit-done/`
140
317
  return { activeMilestone, currentPhase, planCount };
141
318
  }
142
319
  gsdAdapter = {
320
+ resolveGsdRoot,
143
321
  getGsdToolsPath,
144
322
  invokeGsdTool,
145
323
  getTimestamp,
@@ -171,7 +349,7 @@ function requireTemplateLoader () {
171
349
  if (hasRequiredTemplateLoader) return templateLoader.exports;
172
350
  hasRequiredTemplateLoader = 1;
173
351
  (function (module) {
174
- const fs = require$$0$1;
352
+ const fs = require$$2;
175
353
  const path = require$$1;
176
354
  const VALID_GSD_ROUTES = [
177
355
  "quick",
@@ -344,15 +522,15 @@ Checked: ` + path.join(__dirname, "..", "templates") + "\nChecked: " + path.join
344
522
  const nextMatch = nextSectionMatch.exec(content);
345
523
  const sectionEnd = nextMatch ? nextMatch.index : content.length;
346
524
  const sectionText = content.slice(sectionStart, sectionEnd);
347
- const goalMatch = sectionText.match(/\*\*Goal[:\*]*\*?\*?[:\s]+([^\n]+)/);
525
+ const goalMatch = sectionText.match(/\*\*Goal[:*]*\*?\*?[:\s]+([^\n]+)/);
348
526
  const goal = goalMatch ? goalMatch[1].trim().replace(/\*+$/, "") : "";
349
- const reqMatch = sectionText.match(/\*\*Requirements?[:\*]*\*?\*?[:\s]+([\s\S]*?)(?=\n\*\*|\n###|$)/);
527
+ const reqMatch = sectionText.match(/\*\*Requirements?[:*]*\*?\*?[:\s]+([\s\S]*?)(?=\n\*\*|\n###|$)/);
350
528
  let requirements = [];
351
529
  if (reqMatch) {
352
530
  const reqText = reqMatch[1].trim();
353
531
  requirements = reqText.split(/[,\n]+/).map((r) => r.trim().replace(/^[-*\s]+/, "").trim()).filter((r) => r.length > 0);
354
532
  }
355
- const scMatch = sectionText.match(/\*\*Success Criteria[:\*]*\*?\*?[:\s]+([\s\S]*?)(?=\n\*\*|\n###|$)/);
533
+ const scMatch = sectionText.match(/\*\*Success Criteria[:*]*\*?\*?[:\s]+([\s\S]*?)(?=\n\*\*|\n###|$)/);
356
534
  let success_criteria = [];
357
535
  if (scMatch) {
358
536
  const scText = scMatch[1];
@@ -477,122 +655,19 @@ function requireTemplates () {
477
655
  return templates;
478
656
  }
479
657
 
480
- var retry;
481
- var hasRequiredRetry;
658
+ var claude;
659
+ var hasRequiredClaude;
482
660
 
483
- function requireRetry () {
484
- if (hasRequiredRetry) return retry;
485
- hasRequiredRetry = 1;
486
- const MAX_RETRIES = 3;
487
- const BACKOFF_BASE_MS = 5e3;
488
- const BACKOFF_MAX_MS = 3e5;
489
- const TRANSIENT_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
490
- const TRANSIENT_MESSAGE_PATTERNS = [
491
- "network timeout",
492
- "econnreset",
493
- "econnrefused",
494
- "etimedout",
495
- "socket hang up",
496
- "worktree lock",
497
- "model overload",
498
- "rate limit",
499
- "too many requests",
500
- "service unavailable",
501
- "bad gateway",
502
- "gateway timeout"
503
- ];
504
- const NEEDS_INFO_MESSAGE_PATTERNS = [
505
- "ambiguous",
506
- "missing required field",
507
- "contradictory requirements",
508
- "issue body"
509
- ];
510
- function classifyFailure(error) {
511
- if (!error || typeof error !== "object") {
512
- return { class: "permanent", reason: "no error object provided" };
513
- }
514
- const status = error.status;
515
- const message = (error.message || "").toLowerCase();
516
- const code = (error.code || "").toLowerCase();
517
- if (typeof status === "number") {
518
- if (status === 429) {
519
- return { class: "transient", reason: "rate limit (HTTP 429)" };
520
- }
521
- if (TRANSIENT_STATUS_CODES.has(status)) {
522
- return { class: "transient", reason: `server error (HTTP ${status})` };
523
- }
524
- if (status === 403) {
525
- return { class: "permanent", reason: "forbidden (HTTP 403 \u2014 non-rate-limit)" };
526
- }
527
- if (status >= 400 && status < 500) {
528
- return { class: "permanent", reason: `client error (HTTP ${status})` };
529
- }
530
- }
531
- if (code) {
532
- const networkCodes = /* @__PURE__ */ new Set([
533
- "econnreset",
534
- "econnrefused",
535
- "etimedout",
536
- "enotfound",
537
- "epipe"
538
- ]);
539
- if (networkCodes.has(code)) {
540
- return { class: "transient", reason: `network error (${code.toUpperCase()})` };
541
- }
542
- if (code === "enoent") {
543
- return { class: "permanent", reason: "file not found (ENOENT) \u2014 GSD tools may be missing" };
544
- }
545
- }
546
- for (const pattern of TRANSIENT_MESSAGE_PATTERNS) {
547
- if (message.includes(pattern)) {
548
- return { class: "transient", reason: `transient condition detected: "${pattern}"` };
549
- }
550
- }
551
- for (const pattern of NEEDS_INFO_MESSAGE_PATTERNS) {
552
- if (message.includes(pattern)) {
553
- return { class: "needs-info", reason: `issue requires clarification: "${pattern}"` };
554
- }
555
- }
556
- return {
557
- class: "permanent",
558
- reason: "unknown error \u2014 classified as permanent to prevent runaway retries"
559
- };
560
- }
561
- function canRetry(issueState) {
562
- if (!issueState || typeof issueState !== "object") return false;
563
- if (issueState.dead_letter === true) return false;
564
- const count = typeof issueState.retry_count === "number" ? issueState.retry_count : 0;
565
- return count < MAX_RETRIES;
566
- }
567
- function incrementRetry(issueState) {
568
- const current = typeof issueState.retry_count === "number" ? issueState.retry_count : 0;
569
- return Object.assign({}, issueState, { retry_count: current + 1 });
570
- }
571
- function resetRetryState(issueState) {
572
- return Object.assign({}, issueState, {
573
- retry_count: 0,
574
- last_failure_class: null,
575
- dead_letter: false
576
- });
577
- }
578
- function getBackoffMs(retryCount) {
579
- const count = Math.max(0, Math.floor(retryCount));
580
- const base = Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * Math.pow(2, count));
581
- return Math.floor(Math.random() * (base + 1));
582
- }
583
- retry = {
584
- // Constants
585
- MAX_RETRIES,
586
- BACKOFF_BASE_MS,
587
- BACKOFF_MAX_MS,
588
- // Core functions
589
- classifyFailure,
590
- canRetry,
591
- incrementRetry,
592
- resetRetryState,
593
- getBackoffMs
661
+ function requireClaude () {
662
+ if (hasRequiredClaude) return claude;
663
+ hasRequiredClaude = 1;
664
+ const provider = index$1.requireProviderClaude();
665
+ claude = {
666
+ assertClaudeAvailable: provider.assertAvailable,
667
+ invokeClaude: provider.invoke,
668
+ getCommandsDir: provider.getCommandsDir
594
669
  };
595
- return retry;
670
+ return claude;
596
671
  }
597
672
 
598
673
  var progress;
@@ -601,7 +676,8 @@ var hasRequiredProgress;
601
676
  function requireProgress () {
602
677
  if (hasRequiredProgress) return progress;
603
678
  hasRequiredProgress = 1;
604
- const { IS_TTY, IS_CI, USE_COLOR } = index$1.requireOutput();
679
+ const { USE_COLOR } = index$1.requireOutput();
680
+ const { STAGES } = requirePipeline();
605
681
  const SUPPORTS_COLOR = USE_COLOR;
606
682
  const C = {
607
683
  reset: "\x1B[0m",
@@ -646,16 +722,16 @@ function requireProgress () {
646
722
  }
647
723
  function stageIcon(stage) {
648
724
  switch (stage) {
649
- case "done":
650
- case "pr-created":
725
+ case STAGES.DONE:
726
+ case STAGES.PR_CREATED:
651
727
  return { icon: "\u2713", colored: col(C.green, "\u2713") };
652
- case "executing":
653
- case "planning":
654
- case "verifying":
728
+ case STAGES.EXECUTING:
729
+ case STAGES.PLANNING:
730
+ case STAGES.VERIFYING:
655
731
  return { icon: "\u25C6", colored: col(C.blue, "\u25C6") };
656
- case "failed":
732
+ case STAGES.FAILED:
657
733
  return { icon: "\u2717", colored: col(C.red, "\u2717") };
658
- case "blocked":
734
+ case STAGES.BLOCKED:
659
735
  return { icon: "\u2298", colored: col(C.yellow, "\u2298") };
660
736
  default:
661
737
  return { icon: "\u25CB", colored: col(C.dim, "\u25CB") };
@@ -673,8 +749,8 @@ ${header}
673
749
  `);
674
750
  if (issues && issues.length > 0) {
675
751
  const parts = issues.map(({ number, pipeline_stage }) => {
676
- const s = number === currentIssue && pipeline_stage !== "done" ? "executing" : pipeline_stage || "new";
677
- const { colored, icon } = stageIcon(s);
752
+ const s = number === currentIssue && pipeline_stage !== STAGES.DONE ? STAGES.EXECUTING : pipeline_stage || STAGES.NEW;
753
+ const { colored } = stageIcon(s);
678
754
  const numStr = SUPPORTS_COLOR ? `${C.dim}#${number}${C.reset}` : `#${number}`;
679
755
  return `${numStr}${colored}`;
680
756
  });
@@ -692,25 +768,448 @@ ${header}
692
768
  return progress;
693
769
  }
694
770
 
771
+ var issueContext;
772
+ var hasRequiredIssueContext;
773
+
774
+ function requireIssueContext () {
775
+ if (hasRequiredIssueContext) return issueContext;
776
+ hasRequiredIssueContext = 1;
777
+ const { execSync } = require$$0;
778
+ const fs = require$$2;
779
+ const path = require$$1;
780
+ const GH_TIMEOUT_MS = 3e4;
781
+ const BUDGET = {
782
+ vision: 2e3,
783
+ priorSummary: 500,
784
+ maxPriorSummaries: 5,
785
+ currentPlan: 4e3,
786
+ milestone: 1e3
787
+ };
788
+ const CACHE_TTL_MINUTES = 30;
789
+ function run(cmd) {
790
+ return execSync(cmd, {
791
+ encoding: "utf-8",
792
+ stdio: ["pipe", "pipe", "pipe"],
793
+ timeout: GH_TIMEOUT_MS
794
+ }).trim();
795
+ }
796
+ function getMgwDir() {
797
+ return path.join(process.cwd(), ".mgw");
798
+ }
799
+ function getCacheDir() {
800
+ const dir = path.join(getMgwDir(), "context-cache");
801
+ fs.mkdirSync(dir, { recursive: true });
802
+ return dir;
803
+ }
804
+ function truncate(str, maxLen) {
805
+ if (!str || str.length <= maxLen) return str || "";
806
+ return str.slice(0, maxLen) + "...";
807
+ }
808
+ function parseMetadata(commentBody) {
809
+ const result = { type: null, phase: null, milestone: null, timestamp: null };
810
+ if (!commentBody) return result;
811
+ const match = commentBody.match(/<!--\s*(mgw:[^\n]*?)-->/);
812
+ if (!match) return result;
813
+ const header = match[1];
814
+ const typeMatch = header.match(/mgw:type=(\S+)/);
815
+ const phaseMatch = header.match(/mgw:phase=(\S+)/);
816
+ const milestoneMatch = header.match(/mgw:milestone=(\S+)/);
817
+ const timestampMatch = header.match(/mgw:timestamp=(\S+)/);
818
+ if (typeMatch) result.type = typeMatch[1];
819
+ if (phaseMatch) result.phase = parseInt(phaseMatch[1], 10);
820
+ if (milestoneMatch) result.milestone = parseInt(milestoneMatch[1], 10);
821
+ if (timestampMatch) result.timestamp = timestampMatch[1];
822
+ return result;
823
+ }
824
+ function formatWithMetadata(content, meta) {
825
+ const m = meta || {};
826
+ const ts = m.timestamp || (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
827
+ const parts = [];
828
+ if (m.type) parts.push(`mgw:type=${m.type}`);
829
+ if (m.phase != null) parts.push(`mgw:phase=${m.phase}`);
830
+ if (m.milestone != null) parts.push(`mgw:milestone=${m.milestone}`);
831
+ parts.push(`mgw:timestamp=${ts}`);
832
+ const header = `<!-- ${parts.join(" ")} -->`;
833
+ return `${header}
834
+ ${content}`;
835
+ }
836
+ async function postPlanningComment(issueNumber, type, content, meta) {
837
+ const formatted = formatWithMetadata(content, { ...meta, type });
838
+ const tmpFile = path.join(require$$3.tmpdir(), `mgw-comment-${Date.now()}.md`);
839
+ try {
840
+ fs.writeFileSync(tmpFile, formatted, "utf-8");
841
+ run(`gh issue comment ${issueNumber} --body-file ${JSON.stringify(tmpFile)}`);
842
+ } finally {
843
+ try {
844
+ fs.unlinkSync(tmpFile);
845
+ } catch (_) {
846
+ }
847
+ }
848
+ }
849
+ async function findPlanningComments(issueNumber, type) {
850
+ const raw = run(
851
+ `gh issue view ${issueNumber} --json comments --jq '.comments'`
852
+ );
853
+ const comments = JSON.parse(raw);
854
+ const results = [];
855
+ for (const c of comments) {
856
+ const meta = parseMetadata(c.body);
857
+ if (meta.type === type) {
858
+ results.push({
859
+ body: c.body,
860
+ meta,
861
+ createdAt: c.createdAt || ""
862
+ });
863
+ }
864
+ }
865
+ return results;
866
+ }
867
+ async function findLatestComment(issueNumber, type) {
868
+ const comments = await findPlanningComments(issueNumber, type);
869
+ if (comments.length === 0) return null;
870
+ comments.sort((a, b) => {
871
+ const tsA = a.meta.timestamp || a.createdAt || "";
872
+ const tsB = b.meta.timestamp || b.createdAt || "";
873
+ return tsA.localeCompare(tsB);
874
+ });
875
+ return comments[comments.length - 1];
876
+ }
877
+ async function assembleMilestoneContext(milestoneNum) {
878
+ const cached = readCache(milestoneNum);
879
+ if (cached) return Object.values(cached.summaries);
880
+ const raw = run(
881
+ `gh issue list --milestone ${JSON.stringify(String(milestoneNum))} --state closed --json number,title --limit 100`
882
+ );
883
+ const issues = JSON.parse(raw);
884
+ const summaries = [];
885
+ for (const issue of issues) {
886
+ try {
887
+ const comment = await findLatestComment(issue.number, "summary");
888
+ if (comment) {
889
+ const bodyWithoutHeader = comment.body.replace(/<!--[\s\S]*?-->\n?/, "").trim();
890
+ summaries.push({
891
+ issueNumber: issue.number,
892
+ title: issue.title,
893
+ summary: truncate(bodyWithoutHeader, BUDGET.priorSummary)
894
+ });
895
+ }
896
+ } catch (_) {
897
+ }
898
+ }
899
+ writeCache(milestoneNum, summaries);
900
+ return summaries;
901
+ }
902
+ async function assembleIssueContext(issueNumber) {
903
+ const raw = run(
904
+ `gh issue view ${issueNumber} --json number,title,body,milestone,labels,state`
905
+ );
906
+ const issue = JSON.parse(raw);
907
+ let milestoneContext = [];
908
+ if (issue.milestone && issue.milestone.number) {
909
+ try {
910
+ milestoneContext = await assembleMilestoneContext(issue.milestone.number);
911
+ } catch (_) {
912
+ }
913
+ }
914
+ let planComment = null;
915
+ let summaryComment = null;
916
+ try {
917
+ planComment = await findLatestComment(issueNumber, "plan");
918
+ } catch (_) {
919
+ }
920
+ try {
921
+ summaryComment = await findLatestComment(issueNumber, "summary");
922
+ } catch (_) {
923
+ }
924
+ return { issue, milestoneContext, planComment, summaryComment };
925
+ }
926
+ async function fetchProjectVision() {
927
+ try {
928
+ const projectJsonPath = path.join(getMgwDir(), "project.json");
929
+ if (fs.existsSync(projectJsonPath)) {
930
+ const project = JSON.parse(fs.readFileSync(projectJsonPath, "utf-8"));
931
+ const projectNumber = project.project && project.project.project_board && project.project.project_board.number || "";
932
+ if (projectNumber) {
933
+ const owner = run("gh repo view --json owner -q .owner.login");
934
+ const readme = run(
935
+ `gh project view ${projectNumber} --owner ${owner} --json readme -q .readme`
936
+ );
937
+ if (readme && readme.length > 10) {
938
+ const visionMatch = readme.match(/##\s*Vision\s*\n([\s\S]*?)(?=\n##\s|\n$|$)/);
939
+ if (visionMatch && visionMatch[1].trim()) {
940
+ return visionMatch[1].trim();
941
+ }
942
+ const lines = readme.split("\n");
943
+ const bodyLines = lines.filter((l) => !l.startsWith("# "));
944
+ const body = bodyLines.join("\n").trim();
945
+ if (body) return body;
946
+ }
947
+ }
948
+ }
949
+ } catch (_) {
950
+ }
951
+ try {
952
+ const projectJsonPath = path.join(getMgwDir(), "project.json");
953
+ if (fs.existsSync(projectJsonPath)) {
954
+ const project = JSON.parse(fs.readFileSync(projectJsonPath, "utf-8"));
955
+ const description = project.project && project.project.description || "";
956
+ if (description) return description;
957
+ const projectName = project.project && project.project.name || "";
958
+ if (projectName) return `Project: ${projectName}`;
959
+ }
960
+ } catch (_) {
961
+ }
962
+ try {
963
+ const visionBriefPath = path.join(getMgwDir(), "vision-brief.json");
964
+ if (fs.existsSync(visionBriefPath)) {
965
+ const brief = JSON.parse(fs.readFileSync(visionBriefPath, "utf-8"));
966
+ return brief.vision_summary || brief.description || "";
967
+ }
968
+ } catch (_) {
969
+ }
970
+ return "";
971
+ }
972
+ async function buildGSDPromptContext(opts) {
973
+ const o = opts || {};
974
+ const sections = [];
975
+ if (o.includeVision) {
976
+ try {
977
+ const vision = await fetchProjectVision();
978
+ if (vision) {
979
+ sections.push(`<vision>
980
+ ${truncate(vision, BUDGET.vision)}
981
+ </vision>`);
982
+ }
983
+ } catch (_) {
984
+ }
985
+ }
986
+ if (o.milestone) {
987
+ try {
988
+ const milestoneRaw = run(
989
+ `gh api repos/$(gh repo view --json nameWithOwner -q .nameWithOwner)/milestones/${o.milestone} --jq '{title: .title, description: .description}'`
990
+ );
991
+ const milestoneData = JSON.parse(milestoneRaw);
992
+ const milestoneInfo = truncate(
993
+ `${milestoneData.title}
994
+ ${milestoneData.description || ""}`,
995
+ BUDGET.milestone
996
+ );
997
+ sections.push(`<milestone>
998
+ ${milestoneInfo}
999
+ </milestone>`);
1000
+ } catch (_) {
1001
+ }
1002
+ }
1003
+ if (o.includePriorSummaries && o.milestone) {
1004
+ try {
1005
+ const summaries = await assembleMilestoneContext(o.milestone);
1006
+ const prior = summaries.filter((s) => o.issueNumber == null || s.issueNumber !== o.issueNumber).slice(-BUDGET.maxPriorSummaries);
1007
+ if (prior.length > 0) {
1008
+ const priorText = prior.map((s) => `### Issue #${s.issueNumber}: ${s.title}
1009
+ ${s.summary}`).join("\n\n");
1010
+ sections.push(`<prior_phases>
1011
+ ${priorText}
1012
+ </prior_phases>`);
1013
+ }
1014
+ } catch (_) {
1015
+ }
1016
+ }
1017
+ if (o.includeCurrentPlan && o.issueNumber) {
1018
+ try {
1019
+ const planComment = await findLatestComment(o.issueNumber, "plan");
1020
+ if (planComment) {
1021
+ const planBody = planComment.body.replace(/<!--[\s\S]*?-->\n?/, "").trim();
1022
+ sections.push(`<current_phase>
1023
+ ${truncate(planBody, BUDGET.currentPlan)}
1024
+ </current_phase>`);
1025
+ }
1026
+ } catch (_) {
1027
+ }
1028
+ }
1029
+ if (sections.length === 0) return "";
1030
+ return `<mgw_context>
1031
+
1032
+ ${sections.join("\n\n")}
1033
+
1034
+ </mgw_context>`;
1035
+ }
1036
+ async function safeContext(opts) {
1037
+ try {
1038
+ return await buildGSDPromptContext(opts);
1039
+ } catch (_) {
1040
+ return "";
1041
+ }
1042
+ }
1043
+ function readCache(milestoneNum) {
1044
+ try {
1045
+ const cachePath = path.join(getCacheDir(), `milestone-${milestoneNum}.json`);
1046
+ if (!fs.existsSync(cachePath)) return null;
1047
+ const data = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
1048
+ const cachedAt = new Date(data.cached_at);
1049
+ const now = /* @__PURE__ */ new Date();
1050
+ const ageMinutes = (now - cachedAt) / (1e3 * 60);
1051
+ if (ageMinutes > (data.ttl_minutes || CACHE_TTL_MINUTES)) return null;
1052
+ return data;
1053
+ } catch (_) {
1054
+ return null;
1055
+ }
1056
+ }
1057
+ function writeCache(milestoneNum, summaries) {
1058
+ try {
1059
+ const cachePath = path.join(getCacheDir(), `milestone-${milestoneNum}.json`);
1060
+ const summaryMap = {};
1061
+ for (const s of summaries) {
1062
+ summaryMap[String(s.issueNumber)] = s;
1063
+ }
1064
+ const data = {
1065
+ milestone: Number(milestoneNum),
1066
+ cached_at: (/* @__PURE__ */ new Date()).toISOString(),
1067
+ ttl_minutes: CACHE_TTL_MINUTES,
1068
+ summaries: summaryMap
1069
+ };
1070
+ fs.writeFileSync(cachePath, JSON.stringify(data, null, 2), "utf-8");
1071
+ } catch (_) {
1072
+ }
1073
+ }
1074
+ async function rebuildContextCache() {
1075
+ const cacheDir = getCacheDir();
1076
+ try {
1077
+ const files = fs.readdirSync(cacheDir);
1078
+ for (const f of files) {
1079
+ if (f.startsWith("milestone-") && f.endsWith(".json")) {
1080
+ fs.unlinkSync(path.join(cacheDir, f));
1081
+ }
1082
+ }
1083
+ } catch (_) {
1084
+ }
1085
+ let milestones;
1086
+ try {
1087
+ const repo = run("gh repo view --json nameWithOwner -q .nameWithOwner");
1088
+ const raw = run(`gh api repos/${repo}/milestones?state=all --jq '.[].number'`);
1089
+ milestones = raw.split("\n").filter(Boolean).map(Number);
1090
+ } catch (_) {
1091
+ milestones = [];
1092
+ }
1093
+ let totalIssues = 0;
1094
+ for (const num of milestones) {
1095
+ try {
1096
+ const summaries = await assembleMilestoneContext(num);
1097
+ totalIssues += summaries.length;
1098
+ } catch (_) {
1099
+ }
1100
+ }
1101
+ return { issueCount: totalIssues, milestoneCount: milestones.length };
1102
+ }
1103
+ async function updateProjectReadme() {
1104
+ try {
1105
+ const projectJsonPath = path.join(getMgwDir(), "project.json");
1106
+ if (!fs.existsSync(projectJsonPath)) return false;
1107
+ const project = JSON.parse(fs.readFileSync(projectJsonPath, "utf-8"));
1108
+ const board = project.project && project.project.project_board || {};
1109
+ const projectNumber = board.number;
1110
+ if (!projectNumber) return false;
1111
+ const owner = run("gh repo view --json owner -q .owner.login");
1112
+ const projectName = project.project && project.project.name || "";
1113
+ let visionSummary = "";
1114
+ try {
1115
+ const visionBriefPath = path.join(getMgwDir(), "vision-brief.json");
1116
+ if (fs.existsSync(visionBriefPath)) {
1117
+ const brief = JSON.parse(fs.readFileSync(visionBriefPath, "utf-8"));
1118
+ visionSummary = (brief.vision_summary || brief.description || "").slice(0, 500);
1119
+ }
1120
+ } catch (_) {
1121
+ }
1122
+ if (!visionSummary) {
1123
+ visionSummary = project.project && project.project.description || "Project initialized via MGW.";
1124
+ }
1125
+ const milestones = project.milestones || [];
1126
+ const tableLines = ["| # | Milestone | Issues | Status |", "|---|-----------|--------|--------|"];
1127
+ for (let i = 0; i < milestones.length; i++) {
1128
+ const m = milestones[i];
1129
+ const name = m.name || m.title || "Unnamed";
1130
+ const count = (m.issues || []).length;
1131
+ const doneCount = (m.issues || []).filter((iss) => iss.pipeline_stage === "done").length;
1132
+ const state = m.gsd_state || "planned";
1133
+ const progress = count > 0 ? ` (${doneCount}/${count})` : "";
1134
+ const stateLabel = state.charAt(0).toUpperCase() + state.slice(1);
1135
+ tableLines.push(`| ${i + 1} | ${name} | ${count}${progress} | ${stateLabel} |`);
1136
+ }
1137
+ const boardUrl = board.url || "";
1138
+ const readmeBody = `# ${projectName}
1139
+
1140
+ ## Vision
1141
+ ${visionSummary}
1142
+
1143
+ ## Milestones
1144
+ ${tableLines.join("\n")}
1145
+
1146
+ ## Links
1147
+ - [Board](${boardUrl})`;
1148
+ const tmpFile = path.join(require("os").tmpdir(), `mgw-readme-${Date.now()}.md`);
1149
+ try {
1150
+ fs.writeFileSync(tmpFile, readmeBody, "utf-8");
1151
+ run(`gh project edit ${projectNumber} --owner ${owner} --readme "$(cat ${JSON.stringify(tmpFile)})"`);
1152
+ } finally {
1153
+ try {
1154
+ fs.unlinkSync(tmpFile);
1155
+ } catch (_) {
1156
+ }
1157
+ }
1158
+ return true;
1159
+ } catch (_) {
1160
+ return false;
1161
+ }
1162
+ }
1163
+ issueContext = {
1164
+ parseMetadata,
1165
+ formatWithMetadata,
1166
+ postPlanningComment,
1167
+ findPlanningComments,
1168
+ findLatestComment,
1169
+ assembleMilestoneContext,
1170
+ assembleIssueContext,
1171
+ buildGSDPromptContext,
1172
+ safeContext,
1173
+ rebuildContextCache,
1174
+ fetchProjectVision,
1175
+ updateProjectReadme
1176
+ };
1177
+ return issueContext;
1178
+ }
1179
+
695
1180
  var lib;
696
1181
  var hasRequiredLib;
697
1182
 
698
1183
  function requireLib () {
699
1184
  if (hasRequiredLib) return lib;
700
1185
  hasRequiredLib = 1;
701
- lib = {
1186
+ const _exports = {
702
1187
  ...index$1.requireState(),
703
1188
  ...index$1.requireGithub(),
704
1189
  ...requireGsd(),
705
1190
  ...requireGsdAdapter(),
706
1191
  ...requireTemplates(),
707
1192
  ...index$1.requireOutput(),
708
- ...index$1.requireClaude(),
709
- ...requireRetry(),
1193
+ ...requireClaude(),
1194
+ ...index$1.requireProviderManager(),
1195
+ ...index$1.requireRetry(),
710
1196
  ...index$1.requireSpinner(),
711
1197
  ...requireProgress(),
712
- ...index$1.requireTui()
1198
+ ...requirePipeline(),
1199
+ ...index$1.requireErrors(),
1200
+ ...index$1.requireLogger(),
1201
+ ...requireIssueContext()
713
1202
  };
1203
+ Object.defineProperty(_exports, "createIssuesBrowser", {
1204
+ configurable: true,
1205
+ enumerable: true,
1206
+ get() {
1207
+ const value = index$1.requireTui().createIssuesBrowser;
1208
+ Object.defineProperty(_exports, "createIssuesBrowser", { value, enumerable: true });
1209
+ return value;
1210
+ }
1211
+ });
1212
+ lib = _exports;
714
1213
  return lib;
715
1214
  }
716
1215
 
@@ -718,4 +1217,3 @@ var libExports = requireLib();
718
1217
  var index = /*@__PURE__*/index$1.getDefaultExportFromCjs(libExports);
719
1218
 
720
1219
  module.exports = index;
721
- 0&&(module.exports={getMgwDir,getActiveDir,getCompletedDir,loadProjectState,writeProjectState,loadActiveIssue,mergeProjectState,migrateProjectState,resolveActiveMilestoneIndex,getRepo,getIssue,listIssues,getMilestone,getRateLimit,closeMilestone,createRelease,getProjectNodeId,findExistingBoard,getProjectFields,createProject,addItemToProject,postMilestoneStartAnnouncement,getGsdToolsPath,invokeGsdTool,getTimestamp,generateSlug,resolveModel,historyDigest,roadmapAnalyze,selectGsdRoute,getGsdState,validate,getSchema,VALID_GSD_ROUTES,IS_TTY,IS_CI,USE_COLOR,COLORS,colorize,statusLine,log,error,verbose,debug,formatJson,assertClaudeAvailable,invokeClaude,getCommandsDir,MAX_RETRIES,BACKOFF_BASE_MS,BACKOFF_MAX_MS,classifyFailure,canRetry,incrementRetry,resetRetryState,getBackoffMs,createSpinner,withSpinner,SUPPORTS_SPINNER,renderProgressBar,printMilestoneProgress,stageIcon,SUPPORTS_COLOR,createIssuesBrowser});