@snipcodeit/mgw 0.2.2 → 0.3.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-s7v-ifd0.cjs');
4
4
  var require$$0 = require('child_process');
5
5
  var require$$1 = require('path');
6
6
  var require$$2 = require('os');
7
7
  var require$$0$1 = 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
 
@@ -17,6 +155,8 @@ function requireGsdAdapter () {
17
155
  const path = require$$1;
18
156
  const os = require$$2;
19
157
  const fs = require$$0$1;
158
+ const { TimeoutError, GsdToolError } = index$1.requireErrors();
159
+ const { STAGES } = requirePipeline();
20
160
  function getGsdToolsPath() {
21
161
  const standard = path.join(
22
162
  os.homedir(),
@@ -33,6 +173,7 @@ function requireGsdAdapter () {
33
173
  Ensure the get-shit-done framework is installed at ~/.claude/get-shit-done/`
34
174
  );
35
175
  }
176
+ const GSD_TIMEOUT_MS = 15e3;
36
177
  function invokeGsdTool(command, args) {
37
178
  const toolPath = getGsdToolsPath();
38
179
  const argsStr = Array.isArray(args) ? args.map((a) => JSON.stringify(String(a))).join(" ") : "";
@@ -41,13 +182,21 @@ Ensure the get-shit-done framework is installed at ~/.claude/get-shit-done/`
41
182
  try {
42
183
  raw = execSync(cmd, {
43
184
  encoding: "utf-8",
44
- stdio: ["pipe", "pipe", "pipe"]
185
+ stdio: ["pipe", "pipe", "pipe"],
186
+ timeout: GSD_TIMEOUT_MS
45
187
  }).trim();
46
188
  } catch (err) {
189
+ if (err.killed) {
190
+ throw new TimeoutError(
191
+ `GSD tool timed out after ${GSD_TIMEOUT_MS / 1e3}s: ${command}`,
192
+ { timeoutMs: GSD_TIMEOUT_MS, operation: `gsd-tools ${command}` }
193
+ );
194
+ }
47
195
  const stderr = err.stderr ? err.stderr.trim() : "";
48
- throw new Error(
196
+ throw new GsdToolError(
49
197
  `GSD tool command failed: ${command}
50
- ` + (stderr ? `stderr: ${stderr}` : `exit code: ${err.status}`)
198
+ ` + (stderr ? `stderr: ${stderr}` : `exit code: ${err.status}`),
199
+ { command, cause: err }
51
200
  );
52
201
  }
53
202
  if (!raw) return null;
@@ -89,13 +238,13 @@ Ensure the get-shit-done framework is installed at ~/.claude/get-shit-done/`
89
238
  if (/gsd-route:diagnose|needs-diagnosis/.test(labelStr)) {
90
239
  return "diagnose";
91
240
  }
92
- if (stage === "diagnosing") {
241
+ if (stage === STAGES.DIAGNOSING) {
93
242
  return "diagnose";
94
243
  }
95
- if (stage === "executing") {
244
+ if (stage === STAGES.EXECUTING) {
96
245
  return "execute-only";
97
246
  }
98
- if (stage === "verifying") {
247
+ if (stage === STAGES.VERIFYING) {
99
248
  return "verify-only";
100
249
  }
101
250
  if (projectState && projectState.milestones) ;
@@ -344,15 +493,15 @@ Checked: ` + path.join(__dirname, "..", "templates") + "\nChecked: " + path.join
344
493
  const nextMatch = nextSectionMatch.exec(content);
345
494
  const sectionEnd = nextMatch ? nextMatch.index : content.length;
346
495
  const sectionText = content.slice(sectionStart, sectionEnd);
347
- const goalMatch = sectionText.match(/\*\*Goal[:\*]*\*?\*?[:\s]+([^\n]+)/);
496
+ const goalMatch = sectionText.match(/\*\*Goal[:*]*\*?\*?[:\s]+([^\n]+)/);
348
497
  const goal = goalMatch ? goalMatch[1].trim().replace(/\*+$/, "") : "";
349
- const reqMatch = sectionText.match(/\*\*Requirements?[:\*]*\*?\*?[:\s]+([\s\S]*?)(?=\n\*\*|\n###|$)/);
498
+ const reqMatch = sectionText.match(/\*\*Requirements?[:*]*\*?\*?[:\s]+([\s\S]*?)(?=\n\*\*|\n###|$)/);
350
499
  let requirements = [];
351
500
  if (reqMatch) {
352
501
  const reqText = reqMatch[1].trim();
353
502
  requirements = reqText.split(/[,\n]+/).map((r) => r.trim().replace(/^[-*\s]+/, "").trim()).filter((r) => r.length > 0);
354
503
  }
355
- const scMatch = sectionText.match(/\*\*Success Criteria[:\*]*\*?\*?[:\s]+([\s\S]*?)(?=\n\*\*|\n###|$)/);
504
+ const scMatch = sectionText.match(/\*\*Success Criteria[:*]*\*?\*?[:\s]+([\s\S]*?)(?=\n\*\*|\n###|$)/);
356
505
  let success_criteria = [];
357
506
  if (scMatch) {
358
507
  const scText = scMatch[1];
@@ -477,131 +626,14 @@ function requireTemplates () {
477
626
  return templates;
478
627
  }
479
628
 
480
- var retry;
481
- var hasRequiredRetry;
482
-
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
594
- };
595
- return retry;
596
- }
597
-
598
629
  var progress;
599
630
  var hasRequiredProgress;
600
631
 
601
632
  function requireProgress () {
602
633
  if (hasRequiredProgress) return progress;
603
634
  hasRequiredProgress = 1;
604
- const { IS_TTY, IS_CI, USE_COLOR } = index$1.requireOutput();
635
+ const { USE_COLOR } = index$1.requireOutput();
636
+ const { STAGES } = requirePipeline();
605
637
  const SUPPORTS_COLOR = USE_COLOR;
606
638
  const C = {
607
639
  reset: "\x1B[0m",
@@ -646,16 +678,16 @@ function requireProgress () {
646
678
  }
647
679
  function stageIcon(stage) {
648
680
  switch (stage) {
649
- case "done":
650
- case "pr-created":
681
+ case STAGES.DONE:
682
+ case STAGES.PR_CREATED:
651
683
  return { icon: "\u2713", colored: col(C.green, "\u2713") };
652
- case "executing":
653
- case "planning":
654
- case "verifying":
684
+ case STAGES.EXECUTING:
685
+ case STAGES.PLANNING:
686
+ case STAGES.VERIFYING:
655
687
  return { icon: "\u25C6", colored: col(C.blue, "\u25C6") };
656
- case "failed":
688
+ case STAGES.FAILED:
657
689
  return { icon: "\u2717", colored: col(C.red, "\u2717") };
658
- case "blocked":
690
+ case STAGES.BLOCKED:
659
691
  return { icon: "\u2298", colored: col(C.yellow, "\u2298") };
660
692
  default:
661
693
  return { icon: "\u25CB", colored: col(C.dim, "\u25CB") };
@@ -673,8 +705,8 @@ ${header}
673
705
  `);
674
706
  if (issues && issues.length > 0) {
675
707
  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);
708
+ const s = number === currentIssue && pipeline_stage !== STAGES.DONE ? STAGES.EXECUTING : pipeline_stage || STAGES.NEW;
709
+ const { colored } = stageIcon(s);
678
710
  const numStr = SUPPORTS_COLOR ? `${C.dim}#${number}${C.reset}` : `#${number}`;
679
711
  return `${numStr}${colored}`;
680
712
  });
@@ -698,7 +730,7 @@ var hasRequiredLib;
698
730
  function requireLib () {
699
731
  if (hasRequiredLib) return lib;
700
732
  hasRequiredLib = 1;
701
- lib = {
733
+ const _exports = {
702
734
  ...index$1.requireState(),
703
735
  ...index$1.requireGithub(),
704
736
  ...requireGsd(),
@@ -706,11 +738,23 @@ function requireLib () {
706
738
  ...requireTemplates(),
707
739
  ...index$1.requireOutput(),
708
740
  ...index$1.requireClaude(),
709
- ...requireRetry(),
741
+ ...index$1.requireRetry(),
710
742
  ...index$1.requireSpinner(),
711
743
  ...requireProgress(),
712
- ...index$1.requireTui()
744
+ ...requirePipeline(),
745
+ ...index$1.requireErrors(),
746
+ ...index$1.requireLogger()
713
747
  };
748
+ Object.defineProperty(_exports, "createIssuesBrowser", {
749
+ configurable: true,
750
+ enumerable: true,
751
+ get() {
752
+ const value = index$1.requireTui().createIssuesBrowser;
753
+ Object.defineProperty(_exports, "createIssuesBrowser", { value, enumerable: true });
754
+ return value;
755
+ }
756
+ });
757
+ lib = _exports;
714
758
  return lib;
715
759
  }
716
760
 
@@ -718,4 +762,3 @@ var libExports = requireLib();
718
762
  var index = /*@__PURE__*/index$1.getDefaultExportFromCjs(libExports);
719
763
 
720
764
  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});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snipcodeit/mgw",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "GitHub-native issue-to-PR automation for Claude Code, powered by Get Shit Done",
5
5
  "bin": {
6
6
  "mgw": "./dist/bin/mgw.cjs"
@@ -16,7 +16,8 @@
16
16
  "scripts": {
17
17
  "build": "pkgroll --clean-dist --src .",
18
18
  "dev": "pkgroll --watch --src .",
19
- "test": "node --test 'test/**/*.test.cjs'",
19
+ "test": "node --test test/*.test.cjs",
20
+ "lint": "eslint lib/ bin/ test/",
20
21
  "prepublishOnly": "npm run build",
21
22
  "completions": "node bin/generate-completions.cjs",
22
23
  "postinstall": "node ./bin/mgw-install.cjs"
@@ -28,6 +29,8 @@
28
29
  "neo-blessed": "^0.2.0"
29
30
  },
30
31
  "devDependencies": {
32
+ "@eslint/js": "^10.0.1",
33
+ "eslint": "^10.0.2",
31
34
  "pkgroll": "^2.26.3"
32
35
  },
33
36
  "engines": {