@snipcodeit/mgw 0.3.0 → 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,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var index$1 = require('../index-s7v-ifd0.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
10
  var pipeline;
@@ -153,24 +153,52 @@ function requireGsdAdapter () {
153
153
  hasRequiredGsdAdapter = 1;
154
154
  const { execSync } = require$$0;
155
155
  const path = require$$1;
156
- const os = require$$2;
157
- const fs = require$$0$1;
156
+ const os = require$$3;
157
+ const fs = require$$2;
158
158
  const { TimeoutError, GsdToolError } = index$1.requireErrors();
159
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
+ }
160
176
  function getGsdToolsPath() {
161
- const standard = path.join(
162
- os.homedir(),
163
- ".claude",
164
- "get-shit-done",
165
- "bin",
166
- "gsd-tools.cjs"
167
- );
168
- if (fs.existsSync(standard)) {
169
- return standard;
177
+ const root = resolveGsdRoot();
178
+ const toolPath = path.join(root, "bin", "gsd-tools.cjs");
179
+ if (fs.existsSync(toolPath)) {
180
+ return toolPath;
170
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
+ }
195
+ }
196
+ const defaultPath = path.join(os.homedir(), ".claude", "get-shit-done", "bin", "gsd-tools.cjs");
197
+ checked.push(` default: ${defaultPath}`);
171
198
  throw new Error(
172
- `GSD tools not found at ${standard}.
173
- 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`
174
202
  );
175
203
  }
176
204
  const GSD_TIMEOUT_MS = 15e3;
@@ -289,6 +317,7 @@ Ensure the get-shit-done framework is installed at ~/.claude/get-shit-done/`
289
317
  return { activeMilestone, currentPhase, planCount };
290
318
  }
291
319
  gsdAdapter = {
320
+ resolveGsdRoot,
292
321
  getGsdToolsPath,
293
322
  invokeGsdTool,
294
323
  getTimestamp,
@@ -320,7 +349,7 @@ function requireTemplateLoader () {
320
349
  if (hasRequiredTemplateLoader) return templateLoader.exports;
321
350
  hasRequiredTemplateLoader = 1;
322
351
  (function (module) {
323
- const fs = require$$0$1;
352
+ const fs = require$$2;
324
353
  const path = require$$1;
325
354
  const VALID_GSD_ROUTES = [
326
355
  "quick",
@@ -626,6 +655,21 @@ function requireTemplates () {
626
655
  return templates;
627
656
  }
628
657
 
658
+ var claude;
659
+ var hasRequiredClaude;
660
+
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
669
+ };
670
+ return claude;
671
+ }
672
+
629
673
  var progress;
630
674
  var hasRequiredProgress;
631
675
 
@@ -724,6 +768,415 @@ ${header}
724
768
  return progress;
725
769
  }
726
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
+
727
1180
  var lib;
728
1181
  var hasRequiredLib;
729
1182
 
@@ -737,13 +1190,15 @@ function requireLib () {
737
1190
  ...requireGsdAdapter(),
738
1191
  ...requireTemplates(),
739
1192
  ...index$1.requireOutput(),
740
- ...index$1.requireClaude(),
1193
+ ...requireClaude(),
1194
+ ...index$1.requireProviderManager(),
741
1195
  ...index$1.requireRetry(),
742
1196
  ...index$1.requireSpinner(),
743
1197
  ...requireProgress(),
744
1198
  ...requirePipeline(),
745
1199
  ...index$1.requireErrors(),
746
- ...index$1.requireLogger()
1200
+ ...index$1.requireLogger(),
1201
+ ...requireIssueContext()
747
1202
  };
748
1203
  Object.defineProperty(_exports, "createIssuesBrowser", {
749
1204
  configurable: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snipcodeit/mgw",
3
- "version": "0.3.0",
3
+ "version": "0.4.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"