@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.
package/commands/sync.md CHANGED
@@ -33,6 +33,18 @@ Run periodically or when starting a new session to get a clean view.
33
33
 
34
34
  <process>
35
35
 
36
+ <step name="parse_arguments">
37
+ **Parse sync flags:**
38
+ ```bash
39
+ FULL_SYNC=false
40
+ for arg in "$@"; do
41
+ case "$arg" in
42
+ --full) FULL_SYNC=true ;;
43
+ esac
44
+ done
45
+ ```
46
+ </step>
47
+
36
48
  <step name="pull_board_state">
37
49
  **Pull board state to reconstruct missing local .mgw/active/ files.**
38
50
 
@@ -348,6 +360,63 @@ rebuilt from board data — triage results and GSD artifacts will need to be re-
348
360
  issue advances to `planning` or beyond.
349
361
  </step>
350
362
 
363
+ <step name="rebuild_from_committed">
364
+ **Rebuild project context from committed planning files (--full only):**
365
+
366
+ Only runs when `--full` flag is passed. This is the multi-machine context rebuild:
367
+ after `git pull`, a developer runs `mgw:sync --full` to reconstruct full project
368
+ context from committed `.planning/` files and the board.
369
+
370
+ ```bash
371
+ if [ "$FULL_SYNC" = "true" ]; then
372
+ echo "Full sync: rebuilding project context from committed planning files..."
373
+
374
+ # 1. Check for committed ROADMAP.md
375
+ if [ -f ".planning/ROADMAP.md" ]; then
376
+ echo " Found committed ROADMAP.md"
377
+ ROADMAP_ANALYSIS=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs roadmap analyze 2>/dev/null || echo '{}')
378
+ PHASE_COUNT=$(echo "$ROADMAP_ANALYSIS" | python3 -c "import json,sys; print(len(json.load(sys.stdin).get('phases',[])))" 2>/dev/null || echo "0")
379
+ echo " ROADMAP.md contains ${PHASE_COUNT} phases"
380
+ else
381
+ echo " No committed ROADMAP.md found — planning context unavailable"
382
+ echo " (This is expected if the project hasn't run a milestone yet)"
383
+ fi
384
+
385
+ # 2. Scan for committed phase artifacts
386
+ COMMITTED_PLANS=$(find .planning -name '*-PLAN.md' -not -path '.planning/quick/*' 2>/dev/null | wc -l)
387
+ COMMITTED_SUMMARIES=$(find .planning -name '*-SUMMARY.md' -not -path '.planning/quick/*' 2>/dev/null | wc -l)
388
+ echo " Committed phase plans: ${COMMITTED_PLANS}"
389
+ echo " Committed phase summaries: ${COMMITTED_SUMMARIES}"
390
+
391
+ # 3. If project.json is missing but board + ROADMAP exist, offer to reconstruct
392
+ if [ ! -f "${MGW_DIR}/project.json" ] && [ -f ".planning/ROADMAP.md" ] && [ -n "$BOARD_NODE_ID" ]; then
393
+ echo ""
394
+ echo " project.json is missing but ROADMAP.md and board are available."
395
+ echo " Run /mgw:project to reconstruct full project state."
396
+ echo " (Board state has been pulled — active issues are available)"
397
+ fi
398
+
399
+ # 4. Rebuild context cache from GitHub issue comments
400
+ echo " Rebuilding context cache from GitHub issue comments..."
401
+ node -e "
402
+ const ic = require('${REPO_ROOT}/lib/issue-context.cjs');
403
+ ic.rebuildContextCache()
404
+ .then(stats => console.log(' Cached summaries for', stats.issueCount, 'issues across', stats.milestoneCount, 'milestones'))
405
+ .catch(e => console.error(' Cache rebuild failed:', e.message));
406
+ " 2>/dev/null || echo " Context cache rebuild skipped (issue-context module not available)"
407
+
408
+ # 5. Refresh Project README with current milestone progress
409
+ echo " Refreshing Project README..."
410
+ node -e "
411
+ const ic = require('${REPO_ROOT}/lib/issue-context.cjs');
412
+ ic.updateProjectReadme()
413
+ .then(ok => console.log(ok ? ' Project README updated' : ' Project README skipped (no board configured)'))
414
+ .catch(() => console.log(' Project README refresh skipped'));
415
+ " 2>/dev/null || true
416
+ fi
417
+ ```
418
+ </step>
419
+
351
420
  <step name="scan_active">
352
421
  **Scan all active issue states:**
353
422
 
@@ -364,24 +364,12 @@ and codebase to classify, then MGW presents the result and offers follow-up acti
364
364
  - **NEVER** let MGW read application source code directly — spawn an analysis agent
365
365
  - **NEVER** omit `subagent_type` from a Task() call — always specify the agent type
366
366
 
367
- ## Consumers
368
-
369
- | Pattern | Referenced By |
370
- |---------|-------------|
371
- | Standard spawn template | run.md, issue.md, pr.md, ask.md, review.md |
372
- | Comment classification | run.md (pre-flight), review.md (standalone) |
373
- | Quick pipeline | run.md |
374
- | Milestone pipeline | run.md, milestone.md |
375
- | Question classification | ask.md |
376
- | Model resolution | run.md |
377
- TB|## PR Review Pattern
367
+ ## PR Review Pattern
378
368
 
379
369
  MGW:review has two modes:
380
370
  1. **Issue Comment Review** (default) — Classifies new comments on an issue since triage
381
371
  2. **PR Deep Review** (with --pr flag or PR URL) — Comprehensive senior engineer review
382
372
 
383
- The PR Deep Review pattern below is for Mode 2. This is problem-solving orchestration
384
-
385
373
  Used by `/mgw:review` for deep PR analysis. This is problem-solving orchestration
386
374
  (not execution orchestration) — the reviewer has high autonomy to analyze, question
387
375
  assumptions, and provide architectural guidance.
package/dist/bin/mgw.cjs CHANGED
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- var index = require('../index-BiwU0uWA.cjs');
4
+ var index = require('../index-B-_JvYpz.cjs');
5
5
  var require$$0 = require('commander');
6
6
  var require$$1 = require('path');
7
- var require$$0$2 = require('fs');
7
+ var require$$2 = require('fs');
8
8
  var require$$0$1 = require('child_process');
9
+ require('os');
9
10
  require('events');
10
11
 
11
12
  var mgw$1 = {};
12
13
 
13
- var version = "0.2.2";
14
- var require$$10 = {
14
+ var version = "0.4.0";
15
+ var require$$11 = {
15
16
  version: version};
16
17
 
17
18
  var hasRequiredMgw;
@@ -21,17 +22,18 @@ function requireMgw () {
21
22
  hasRequiredMgw = 1;
22
23
  const { Command } = require$$0;
23
24
  const path = require$$1;
24
- const fs = require$$0$2;
25
+ const fs = require$$2;
25
26
  const { execSync } = require$$0$1;
26
- const { assertClaudeAvailable, invokeClaude, getCommandsDir } = index.requireClaude();
27
+ const { ProviderManager } = index.requireProviderManager();
27
28
  const { log, error, formatJson, verbose } = index.requireOutput();
28
29
  const { getActiveDir, getCompletedDir, getMgwDir } = index.requireState();
29
30
  const { getIssue, listIssues } = index.requireGithub();
30
31
  const { createIssuesBrowser } = index.requireTui();
31
32
  const { createSpinner } = index.requireSpinner();
32
- const pkg = require$$10;
33
+ const { startTimer } = index.requireLogger();
34
+ const pkg = require$$11;
33
35
  const program = new Command();
34
- 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");
36
+ 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").option("--provider <provider>", "AI provider override (default: claude)");
35
37
  const STAGE_LABELS = {
36
38
  run: "pipeline",
37
39
  init: "init",
@@ -43,15 +45,21 @@ function requireMgw () {
43
45
  next: "next"
44
46
  };
45
47
  async function runAiCommand(commandName, userPrompt, opts) {
46
- assertClaudeAvailable();
47
- const cmdFile = path.join(getCommandsDir(), `${commandName}.md`);
48
+ const provider = ProviderManager.getProvider(opts.provider);
49
+ provider.assertAvailable();
50
+ const cmdFile = path.join(provider.getCommandsDir(), `${commandName}.md`);
48
51
  const stageLabel = STAGE_LABELS[commandName] || commandName;
52
+ const issueMatch = String(userPrompt).match(/^\d+/);
53
+ const timer = startTimer({
54
+ command: commandName,
55
+ issue: issueMatch ? parseInt(issueMatch[0], 10) : void 0
56
+ });
49
57
  if (opts.quiet) {
50
58
  const spinner = createSpinner(`mgw:${stageLabel}`);
51
59
  spinner.start();
52
60
  let result;
53
61
  try {
54
- result = await invokeClaude(cmdFile, userPrompt, {
62
+ result = await provider.invoke(cmdFile, userPrompt, {
55
63
  model: opts.model,
56
64
  quiet: true,
57
65
  dryRun: opts.dryRun,
@@ -59,12 +67,15 @@ function requireMgw () {
59
67
  });
60
68
  } catch (err) {
61
69
  spinner.fail(`${stageLabel} failed`);
70
+ timer.finish("error", err.message);
62
71
  throw err;
63
72
  }
64
73
  if (result.exitCode === 0) {
65
74
  spinner.succeed(`${stageLabel} complete`);
75
+ timer.finish("ok");
66
76
  } else {
67
77
  spinner.fail(`${stageLabel} failed (exit ${result.exitCode})`);
78
+ timer.finish("error", `exit ${result.exitCode}`);
68
79
  }
69
80
  if (result.output) {
70
81
  process.stdout.write(result.output);
@@ -75,12 +86,13 @@ function requireMgw () {
75
86
  spinner.start();
76
87
  await new Promise((r) => setTimeout(r, 80));
77
88
  spinner.stop();
78
- const result = await invokeClaude(cmdFile, userPrompt, {
89
+ const result = await provider.invoke(cmdFile, userPrompt, {
79
90
  model: opts.model,
80
91
  quiet: false,
81
92
  dryRun: opts.dryRun,
82
93
  json: opts.json
83
94
  });
95
+ timer.finish(result.exitCode === 0 ? "ok" : "error", result.exitCode !== 0 ? `exit ${result.exitCode}` : void 0);
84
96
  process.exitCode = result.exitCode;
85
97
  }
86
98
  }
@@ -150,6 +162,7 @@ function requireMgw () {
150
162
  });
151
163
  program.command("sync").description("Reconcile .mgw/ state with GitHub").action(async function() {
152
164
  const opts = this.optsWithGlobals();
165
+ const syncTimer = startTimer({ command: "sync" });
153
166
  const activeDir = getActiveDir();
154
167
  if (!fs.existsSync(activeDir)) {
155
168
  if (opts.json) {
@@ -192,7 +205,7 @@ function requireMgw () {
192
205
  }
193
206
  let ghIssue;
194
207
  try {
195
- ghIssue = getIssue(number);
208
+ ghIssue = await getIssue(number);
196
209
  } catch (err) {
197
210
  error(`Failed to fetch issue #${number} from GitHub: ${err.message}`);
198
211
  results.push({ number, file, status: "error", error: err.message });
@@ -234,6 +247,7 @@ function requireMgw () {
234
247
  const ok = results.filter((r) => r.status === "ok").length;
235
248
  log(`sync complete: ${ok} up-to-date, ${archived} archived`);
236
249
  }
250
+ syncTimer.finish("ok");
237
251
  });
238
252
  program.command("issues [filters...]").description("Browse open issues \u2014 interactive TUI in TTY, static table otherwise").option("--label <label>", "filter by label").option("--milestone <name>", "filter by milestone").option("--assignee <user>", 'filter by assignee ("all" = no filter, default: all)', "all").option("--state <state>", "issue state: open, closed, all (default: open)", "open").option("-s, --search <query>", "pre-populate fuzzy search input").option("--limit <n>", "max issues to load (default: 50)", "50").action(async function() {
239
253
  const opts = this.optsWithGlobals();
@@ -248,7 +262,7 @@ function requireMgw () {
248
262
  if (opts.milestone) ghFilters.milestone = opts.milestone;
249
263
  let issues;
250
264
  try {
251
- issues = listIssues(ghFilters);
265
+ issues = await listIssues(ghFilters);
252
266
  } catch (err) {
253
267
  error("Failed to list issues: " + err.message);
254
268
  process.exitCode = 1;
@@ -343,8 +357,86 @@ function requireMgw () {
343
357
  log("Linked: " + refA + " <-> " + refB);
344
358
  }
345
359
  });
360
+ program.command("log").description("Query execution logs").option("--since <period>", "time period: 1d, 7d, 30d, or ISO date").option("--issue <number>", "filter by issue number").option("--command <name>", "filter by command name").option("--limit <n>", "max entries (default: 50)", "50").option("--metrics", "show aggregated metrics instead of log entries").action(function() {
361
+ const opts = this.optsWithGlobals();
362
+ const { readLogs, aggregateMetrics } = index.requireLogger();
363
+ const logOpts = {
364
+ since: opts.since,
365
+ issue: opts.issue ? parseInt(opts.issue, 10) : void 0,
366
+ command: opts.command,
367
+ limit: opts.metrics ? void 0 : parseInt(opts.limit, 10) || 50
368
+ };
369
+ const entries = readLogs(logOpts);
370
+ if (opts.metrics) {
371
+ const metrics = aggregateMetrics(entries);
372
+ if (opts.json) {
373
+ log(formatJson(metrics));
374
+ } else {
375
+ log(`Total invocations: ${metrics.total}`);
376
+ log(`Avg duration: ${metrics.avgDuration}ms`);
377
+ log(`Failure rate: ${metrics.failureRate}%`);
378
+ log("");
379
+ log("By command:");
380
+ for (const [cmd, stats] of Object.entries(metrics.byCommand)) {
381
+ log(` ${cmd}: ${stats.count} calls, ${stats.errors} errors, avg ${stats.avgDuration}ms`);
382
+ }
383
+ }
384
+ return;
385
+ }
386
+ if (entries.length === 0) {
387
+ log("No log entries found.");
388
+ return;
389
+ }
390
+ if (opts.json) {
391
+ log(formatJson(entries));
392
+ return;
393
+ }
394
+ for (const e of entries) {
395
+ const ts = e.timestamp ? e.timestamp.slice(0, 19).replace("T", " ") : "?";
396
+ const issue = e.issue ? `#${e.issue}` : "";
397
+ const dur = typeof e.duration_ms === "number" ? `${e.duration_ms}ms` : "";
398
+ const status = e.status === "error" ? `ERR: ${e.error || "unknown"}` : e.status;
399
+ log(`${ts} ${(e.command || "").padEnd(12)} ${issue.padEnd(6)} ${dur.padEnd(8)} ${status}`);
400
+ }
401
+ });
402
+ program.command("metrics").description("Show execution metrics \u2014 success rates, durations, command usage").option("--since <period>", "time period: 1d, 7d, 30d, or ISO date (default: 7d)", "7d").option("--issue <number>", "filter by issue number").option("--command <name>", "filter by command name").action(function() {
403
+ const opts = this.optsWithGlobals();
404
+ const { readLogs, aggregateMetrics } = index.requireLogger();
405
+ const { USE_COLOR, COLORS } = index.requireOutput();
406
+ const logOpts = {
407
+ since: opts.since,
408
+ issue: opts.issue ? parseInt(opts.issue, 10) : void 0,
409
+ command: opts.command
410
+ };
411
+ const entries = readLogs(logOpts);
412
+ const metrics = aggregateMetrics(entries);
413
+ if (opts.json) {
414
+ log(formatJson(metrics));
415
+ return;
416
+ }
417
+ if (metrics.total === 0) {
418
+ log("No log entries found for the given period.");
419
+ return;
420
+ }
421
+ const dim = USE_COLOR ? COLORS.dim : "";
422
+ const bold = USE_COLOR ? COLORS.bold : "";
423
+ const reset = USE_COLOR ? COLORS.reset : "";
424
+ log(`${bold}Execution Metrics${reset} ${dim}(${opts.since})${reset}`);
425
+ log("");
426
+ log(` Total invocations: ${metrics.total}`);
427
+ log(` Avg duration: ${metrics.avgDuration}ms`);
428
+ log(` Failure rate: ${metrics.failureRate}%`);
429
+ log("");
430
+ log(`${bold}By Command${reset}`);
431
+ log(`${" Command".padEnd(16)} ${"Calls".padEnd(8)} ${"Errors".padEnd(8)} ${"Avg ms".padEnd(10)}`);
432
+ log(` ${dim}${"\u2500".repeat(38)}${reset}`);
433
+ for (const [cmd, stats] of Object.entries(metrics.byCommand)) {
434
+ log(` ${cmd.padEnd(14)} ${String(stats.count).padEnd(8)} ${String(stats.errors).padEnd(8)} ${String(stats.avgDuration).padEnd(10)}`);
435
+ }
436
+ });
346
437
  program.command("help").description("Show command reference").action(function() {
347
- const helpMdPath = path.join(getCommandsDir(), "help.md");
438
+ const opts = this.optsWithGlobals();
439
+ const helpMdPath = path.join(ProviderManager.getProvider(opts.provider).getCommandsDir(), "help.md");
348
440
  let helpText;
349
441
  try {
350
442
  const raw = fs.readFileSync(helpMdPath, "utf-8");