@snipcodeit/mgw 0.1.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.
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var claude = require('../claude-Vp9qvImH.cjs');
5
+ var require$$0 = require('commander');
6
+ var require$$1 = require('path');
7
+ var require$$0$2 = require('fs');
8
+ var require$$0$1 = require('child_process');
9
+
10
+ var mgw$1 = {};
11
+
12
+ var version = "0.1.0";
13
+ var require$$8 = {
14
+ version: version};
15
+
16
+ var hasRequiredMgw;
17
+
18
+ function requireMgw () {
19
+ if (hasRequiredMgw) return mgw$1;
20
+ hasRequiredMgw = 1;
21
+ const { Command } = require$$0;
22
+ const path = require$$1;
23
+ const fs = require$$0$2;
24
+ const { execSync } = require$$0$1;
25
+ const { assertClaudeAvailable, invokeClaude, getCommandsDir } = claude.requireClaude();
26
+ const { log, error, formatJson, verbose } = claude.requireOutput();
27
+ const { getActiveDir, getCompletedDir, getMgwDir } = claude.requireState();
28
+ const { getIssue, listIssues } = claude.requireGithub();
29
+ const pkg = require$$8;
30
+ const program = new Command();
31
+ program.name("mgw").description("GitHub issue pipeline automation \u2014 Day 1 idea to Go Live").version(pkg.version).option("--dry-run", "show what would happen without executing").option("--json", "output structured JSON").option("-v, --verbose", "show API calls and file writes").option("--debug", "full payloads and timings").option("--model <model>", "Claude model override");
32
+ async function runAiCommand(commandName, userPrompt, opts) {
33
+ assertClaudeAvailable();
34
+ const cmdFile = path.join(getCommandsDir(), `${commandName}.md`);
35
+ const result = await invokeClaude(cmdFile, userPrompt, {
36
+ model: opts.model,
37
+ quiet: opts.quiet,
38
+ dryRun: opts.dryRun,
39
+ json: opts.json
40
+ });
41
+ process.exitCode = result.exitCode;
42
+ }
43
+ program.command("run <issue-number>").description("Run the full pipeline for an issue").option("--quiet", "buffer output, show summary at end").option("--auto", "phase chaining: discuss -> plan -> execute").action(async function(issueNumber) {
44
+ const opts = this.optsWithGlobals();
45
+ await runAiCommand("run", issueNumber, opts);
46
+ });
47
+ program.command("init").description("Bootstrap repo for MGW (state, templates, labels)").action(async function() {
48
+ const opts = this.optsWithGlobals();
49
+ await runAiCommand("init", "", opts);
50
+ });
51
+ program.command("project").description("Initialize project from template (milestones, issues, ROADMAP)").action(async function() {
52
+ const opts = this.optsWithGlobals();
53
+ await runAiCommand("project", "", opts);
54
+ });
55
+ program.command("milestone [number]").description("Execute milestone issues in dependency order").option("--interactive", "pause between issues for review").action(async function(number) {
56
+ const opts = this.optsWithGlobals();
57
+ await runAiCommand("milestone", number || "", opts);
58
+ });
59
+ program.command("next").description("Show next unblocked issue").action(async function() {
60
+ const opts = this.optsWithGlobals();
61
+ await runAiCommand("next", "", opts);
62
+ });
63
+ program.command("issue <number>").description("Triage issue against codebase").action(async function(number) {
64
+ const opts = this.optsWithGlobals();
65
+ await runAiCommand("issue", number, opts);
66
+ });
67
+ program.command("update <number> [message]").description("Post status comment on issue").action(async function(number, message) {
68
+ const opts = this.optsWithGlobals();
69
+ const userPrompt = [number, message].filter(Boolean).join(" ");
70
+ await runAiCommand("update", userPrompt, opts);
71
+ });
72
+ program.command("pr [number]").description("Create PR from GSD artifacts").option("--base <branch>", "custom base branch").action(async function(number) {
73
+ const opts = this.optsWithGlobals();
74
+ const parts = [number, opts.base ? "--base " + opts.base : ""].filter(Boolean);
75
+ await runAiCommand("pr", parts.join(" "), opts);
76
+ });
77
+ program.command("sync").description("Reconcile .mgw/ state with GitHub").action(async function() {
78
+ const opts = this.optsWithGlobals();
79
+ const activeDir = getActiveDir();
80
+ if (!fs.existsSync(activeDir)) {
81
+ if (opts.json) {
82
+ log(formatJson({ status: "no-active-issues", drifted: [], archived: [] }));
83
+ } else {
84
+ log("No active issues found in .mgw/active/");
85
+ }
86
+ return;
87
+ }
88
+ let files;
89
+ try {
90
+ files = fs.readdirSync(activeDir).filter((f) => f.endsWith(".json"));
91
+ } catch (err) {
92
+ error("Failed to read active directory: " + err.message);
93
+ process.exitCode = 1;
94
+ return;
95
+ }
96
+ if (files.length === 0) {
97
+ if (opts.json) {
98
+ log(formatJson({ status: "no-active-issues", drifted: [], archived: [] }));
99
+ } else {
100
+ log("No active issues in .mgw/active/");
101
+ }
102
+ return;
103
+ }
104
+ const results = [];
105
+ for (const file of files) {
106
+ const filePath = path.join(activeDir, file);
107
+ let issueData;
108
+ try {
109
+ issueData = JSON.parse(fs.readFileSync(filePath, "utf-8"));
110
+ } catch {
111
+ verbose(`Skipping unreadable file: ${file}`, opts);
112
+ continue;
113
+ }
114
+ const number = issueData.number;
115
+ if (!number) {
116
+ verbose(`Skipping file with no issue number: ${file}`, opts);
117
+ continue;
118
+ }
119
+ let ghIssue;
120
+ try {
121
+ ghIssue = getIssue(number);
122
+ } catch (err) {
123
+ error(`Failed to fetch issue #${number} from GitHub: ${err.message}`);
124
+ results.push({ number, file, status: "error", error: err.message });
125
+ continue;
126
+ }
127
+ const localState = issueData.state || "unknown";
128
+ const ghState = ghIssue.state;
129
+ const drifted = localState.toLowerCase() !== ghState.toLowerCase();
130
+ if (drifted) {
131
+ if (opts.dryRun) {
132
+ results.push({ number, file, status: "drift", localState, ghState, action: "would-archive" });
133
+ if (!opts.json) {
134
+ log(`[drift] #${number}: local=${localState}, github=${ghState} -> would archive`);
135
+ }
136
+ } else {
137
+ const completedDir = getCompletedDir();
138
+ if (!fs.existsSync(completedDir)) {
139
+ fs.mkdirSync(completedDir, { recursive: true });
140
+ }
141
+ const dest = path.join(completedDir, file);
142
+ fs.renameSync(filePath, dest);
143
+ results.push({ number, file, status: "archived", localState, ghState });
144
+ if (!opts.json) {
145
+ log(`[archived] #${number}: ${ghState} on GitHub -> moved to .mgw/completed/`);
146
+ }
147
+ }
148
+ } else {
149
+ results.push({ number, file, status: "ok", state: ghState });
150
+ if (!opts.json) {
151
+ verbose(`[ok] #${number}: ${ghState}`, opts);
152
+ }
153
+ }
154
+ }
155
+ if (opts.json) {
156
+ const drifted = results.filter((r) => r.status === "drift" || r.status === "archived");
157
+ log(formatJson({ status: "complete", drifted, all: results }));
158
+ } else if (!opts.dryRun) {
159
+ const archived = results.filter((r) => r.status === "archived").length;
160
+ const ok = results.filter((r) => r.status === "ok").length;
161
+ log(`sync complete: ${ok} up-to-date, ${archived} archived`);
162
+ }
163
+ });
164
+ program.command("issues [filters...]").description("List open issues").option("--label <label>", "filter by label").option("--milestone <name>", "filter by milestone").option("--assignee <user>", "filter by assignee (default: @me)", "@me").option("--state <state>", "issue state: open, closed, all (default: open)", "open").action(async function() {
165
+ const opts = this.optsWithGlobals();
166
+ const ghFilters = {
167
+ assignee: opts.assignee || "@me",
168
+ state: opts.state || "open"
169
+ };
170
+ if (opts.label) ghFilters.label = opts.label;
171
+ if (opts.milestone) ghFilters.milestone = opts.milestone;
172
+ let issues;
173
+ try {
174
+ issues = listIssues(ghFilters);
175
+ } catch (err) {
176
+ error("Failed to list issues: " + err.message);
177
+ process.exitCode = 1;
178
+ return;
179
+ }
180
+ if (opts.json) {
181
+ log(formatJson(issues));
182
+ return;
183
+ }
184
+ if (issues.length === 0) {
185
+ log("No issues found.");
186
+ return;
187
+ }
188
+ const pad = (s, n) => String(s).padEnd(n);
189
+ log(pad("#", 6) + pad("Title", 60) + pad("State", 10) + "Labels");
190
+ log("-".repeat(90));
191
+ for (const issue of issues) {
192
+ const labels = (issue.labels || []).map((l) => l.name || l).join(", ");
193
+ log(
194
+ pad(issue.number, 6) + pad((issue.title || "").substring(0, 58), 60) + pad(issue.state || "", 10) + labels
195
+ );
196
+ }
197
+ log("\n" + issues.length + " issue(s)");
198
+ });
199
+ program.command("link <ref-a> <ref-b>").description("Cross-reference issues/PRs/branches").option("--quiet", "no GitHub comments").action(async function(refA, refB) {
200
+ const opts = this.optsWithGlobals();
201
+ const mgwDir = getMgwDir();
202
+ const crossRefsPath = path.join(mgwDir, "cross-refs.json");
203
+ let crossRefs = { links: [] };
204
+ if (fs.existsSync(crossRefsPath)) {
205
+ try {
206
+ crossRefs = JSON.parse(fs.readFileSync(crossRefsPath, "utf-8"));
207
+ if (!Array.isArray(crossRefs.links)) crossRefs.links = [];
208
+ } catch {
209
+ crossRefs = { links: [] };
210
+ }
211
+ }
212
+ const exists = crossRefs.links.some(
213
+ function(l) {
214
+ return l.a === refA && l.b === refB || l.a === refB && l.b === refA;
215
+ }
216
+ );
217
+ if (exists) {
218
+ log("Link already exists: " + refA + " <-> " + refB);
219
+ return;
220
+ }
221
+ const entry = { a: refA, b: refB, created: (/* @__PURE__ */ new Date()).toISOString() };
222
+ if (opts.dryRun) {
223
+ if (opts.json) {
224
+ log(formatJson(Object.assign({ action: "would-link" }, entry)));
225
+ } else {
226
+ log("[dry-run] Would link: " + refA + " <-> " + refB);
227
+ }
228
+ return;
229
+ }
230
+ crossRefs.links.push(entry);
231
+ if (!fs.existsSync(mgwDir)) {
232
+ fs.mkdirSync(mgwDir, { recursive: true });
233
+ }
234
+ fs.writeFileSync(crossRefsPath, JSON.stringify(crossRefs, null, 2), "utf-8");
235
+ if (!opts.quiet) {
236
+ const issuePattern = /^#?(\d+)$/;
237
+ const aMatch = refA.match(issuePattern);
238
+ const bMatch = refB.match(issuePattern);
239
+ if (aMatch && bMatch) {
240
+ const numA = aMatch[1];
241
+ const numB = bMatch[1];
242
+ try {
243
+ execSync(
244
+ "gh issue comment " + numA + ' --body "Cross-referenced with #' + numB + '"',
245
+ { stdio: ["pipe", "pipe", "pipe"], encoding: "utf-8" }
246
+ );
247
+ execSync(
248
+ "gh issue comment " + numB + ' --body "Cross-referenced with #' + numA + '"',
249
+ { stdio: ["pipe", "pipe", "pipe"], encoding: "utf-8" }
250
+ );
251
+ } catch (err) {
252
+ verbose("GitHub comment failed (non-fatal): " + err.message, opts);
253
+ }
254
+ }
255
+ }
256
+ if (opts.json) {
257
+ log(formatJson(Object.assign({ action: "linked" }, entry)));
258
+ } else {
259
+ log("Linked: " + refA + " <-> " + refB);
260
+ }
261
+ });
262
+ program.command("help").description("Show command reference").action(function() {
263
+ const helpMdPath = path.join(getCommandsDir(), "help.md");
264
+ let helpText;
265
+ try {
266
+ const raw = fs.readFileSync(helpMdPath, "utf-8");
267
+ const fenceStart = raw.indexOf("```\n");
268
+ const fenceEnd = raw.lastIndexOf("\n```");
269
+ if (fenceStart !== -1 && fenceEnd !== -1 && fenceEnd > fenceStart) {
270
+ helpText = raw.substring(fenceStart + 4, fenceEnd);
271
+ } else {
272
+ helpText = raw;
273
+ }
274
+ } catch (err) {
275
+ error("Failed to load help text: " + err.message);
276
+ process.exitCode = 1;
277
+ return;
278
+ }
279
+ process.stdout.write(helpText + "\n");
280
+ });
281
+ program.parseAsync(process.argv).catch(function(err) {
282
+ error(err.message);
283
+ process.exit(1);
284
+ });
285
+ return mgw$1;
286
+ }
287
+
288
+ var mgwExports = requireMgw();
289
+ var mgw = /*@__PURE__*/claude.getDefaultExportFromCjs(mgwExports);
290
+
291
+ module.exports = mgw;