@hegemonart/get-design-done 1.28.7 → 1.30.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.
Files changed (71) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +116 -0
  4. package/README.de.md +37 -0
  5. package/README.fr.md +37 -0
  6. package/README.it.md +37 -0
  7. package/README.ja.md +37 -0
  8. package/README.ko.md +37 -0
  9. package/README.md +44 -0
  10. package/README.zh-CN.md +37 -0
  11. package/SKILL.md +12 -10
  12. package/agents/design-reflector.md +50 -0
  13. package/package.json +3 -1
  14. package/reference/capability-gap-stage-gate.md +261 -0
  15. package/reference/known-failure-modes.md +185 -0
  16. package/reference/pseudonymization-rules.md +189 -0
  17. package/reference/registry.json +22 -1
  18. package/reference/schemas/events.schema.json +97 -3
  19. package/reference/schemas/generated.d.ts +319 -4
  20. package/scripts/build-distribution-bundles.cjs +549 -0
  21. package/scripts/cli/gdd-events.mjs +35 -2
  22. package/scripts/gsd-cleanup-incubator.cjs +367 -0
  23. package/scripts/install.cjs +61 -0
  24. package/scripts/lib/apply-reflections/incubator-proposals.cjs +448 -0
  25. package/scripts/lib/bandit-router.cjs +92 -9
  26. package/scripts/lib/gsd-health-mirror/index.cjs +37 -1
  27. package/scripts/lib/incubator-author.cjs +845 -0
  28. package/scripts/lib/install/config-dir.cjs +26 -0
  29. package/scripts/lib/install/converters/codex-plugin.cjs +407 -0
  30. package/scripts/lib/install/converters/cursor-marketplace.cjs +309 -0
  31. package/scripts/lib/install/doctor-codex-plugin.cjs +388 -0
  32. package/scripts/lib/install/doctor-cursor-marketplace.cjs +366 -0
  33. package/scripts/lib/install/doctor-tier2.cjs +586 -0
  34. package/scripts/lib/install/runtimes.cjs +48 -0
  35. package/scripts/lib/issue-reporter/cli-flag-report.cjs +153 -0
  36. package/scripts/lib/issue-reporter/consent-prompt.cjs +231 -0
  37. package/scripts/lib/issue-reporter/dedup.cjs +458 -0
  38. package/scripts/lib/issue-reporter/destination.cjs +37 -0
  39. package/scripts/lib/issue-reporter/draft-writer.cjs +157 -0
  40. package/scripts/lib/issue-reporter/gh-absent-fallback.cjs +220 -0
  41. package/scripts/lib/issue-reporter/gh-submit.cjs +114 -0
  42. package/scripts/lib/issue-reporter/kill-switch.cjs +122 -0
  43. package/scripts/lib/issue-reporter/payload-assembly.cjs +367 -0
  44. package/scripts/lib/issue-reporter/privacy-diff.cjs +385 -0
  45. package/scripts/lib/issue-reporter/report-flow.cjs +269 -0
  46. package/scripts/lib/issue-reporter/triage-matcher.cjs +270 -0
  47. package/scripts/lib/pseudonymize.cjs +444 -0
  48. package/scripts/lib/reflections-cycle-writer.cjs +172 -0
  49. package/scripts/lib/reflector/capability-gap-scan.cjs +751 -0
  50. package/scripts/lib/reflector-capability-gap-aggregator.cjs +320 -0
  51. package/scripts/lint-agentskills-spec.cjs +457 -0
  52. package/scripts/release-smoke-test.cjs +33 -2
  53. package/scripts/validate-incubator-scope.cjs +133 -0
  54. package/skills/apply-reflections/SKILL.md +16 -1
  55. package/skills/apply-reflections/apply-reflections-procedure.md +71 -3
  56. package/skills/compare/SKILL.md +2 -2
  57. package/skills/compare/compare-rubric.md +1 -1
  58. package/skills/darkmode/SKILL.md +2 -2
  59. package/skills/darkmode/darkmode-audit-procedure.md +1 -1
  60. package/skills/fast/SKILL.md +46 -0
  61. package/skills/figma-write/SKILL.md +2 -2
  62. package/skills/graphify/SKILL.md +2 -2
  63. package/skills/reflect/SKILL.md +9 -0
  64. package/skills/reflect/procedures/capability-gap-scan.md +120 -0
  65. package/skills/report-issue/SKILL.md +53 -0
  66. package/skills/report-issue/report-issue-procedure.md +120 -0
  67. package/skills/router/SKILL.md +5 -0
  68. package/skills/router/capability-gap-emitter.md +65 -0
  69. package/skills/style/SKILL.md +2 -2
  70. package/skills/style/style-doc-procedure.md +1 -1
  71. package/skills/update/SKILL.md +3 -2
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scripts/gsd-cleanup-incubator.cjs — Phase 29 Plan 06 / CONTEXT D-06.
4
+ *
5
+ * Walk `.design/reflections/incubator/<slug>/`, archive slugs whose
6
+ * newest matching `capability_gap` event is older than the TTL
7
+ * (default P=30 days). Archive (not delete) preserves audit trail.
8
+ * Refresh = new `capability_gap` event matching the slug's
9
+ * `context_hash` resets the timer (because newest event timestamp
10
+ * advances).
11
+ *
12
+ * Standalone — no pre-existing `gsd-cleanup` extension point exists
13
+ * in this repo (survey: `find scripts -iname "*cleanup*"` returned
14
+ * only `.git` log noise + a workflow doc, no actual cleanup script).
15
+ *
16
+ * Usage:
17
+ * node scripts/gsd-cleanup-incubator.cjs [--ttl-days N] [--dry-run] [--base-dir PATH]
18
+ *
19
+ * Library mode (for tests):
20
+ * const { scanIncubator, archiveSlug, DEFAULT_TTL_DAYS } = require('./gsd-cleanup-incubator.cjs');
21
+ */
22
+
23
+ 'use strict';
24
+
25
+ const fs = require('node:fs');
26
+ const path = require('node:path');
27
+
28
+ const eventChain = require('./lib/event-chain.cjs');
29
+
30
+ const DEFAULT_TTL_DAYS = 30; // CONTEXT D-06
31
+ const INCUBATOR_DIR = '.design/reflections/incubator';
32
+ const ARCHIVE_SUBDIR = 'archive';
33
+
34
+ /**
35
+ * Tiny YAML-ish frontmatter parser. Match `---\n(.+?)\n---` (multiline,
36
+ * non-greedy), split lines on `\n`, parse `key: value` pairs (string
37
+ * values only, single quotes optional).
38
+ *
39
+ * @param {string} content
40
+ * @returns {Record<string, string>}
41
+ */
42
+ function parseFrontmatter(content) {
43
+ if (typeof content !== 'string') return {};
44
+ // Match the leading frontmatter block — must start at the very top
45
+ // (allow a possible BOM/whitespace).
46
+ const m = content.match(/^\s*---\r?\n([\s\S]*?)\r?\n---/);
47
+ if (!m) return {};
48
+ const body = m[1];
49
+ /** @type {Record<string, string>} */
50
+ const out = {};
51
+ for (const line of body.split(/\r?\n/)) {
52
+ const trimmed = line.trim();
53
+ if (!trimmed || trimmed.startsWith('#')) continue;
54
+ const idx = trimmed.indexOf(':');
55
+ if (idx < 0) continue;
56
+ const key = trimmed.slice(0, idx).trim();
57
+ let val = trimmed.slice(idx + 1).trim();
58
+ // Strip surrounding quotes (single or double).
59
+ if (
60
+ (val.startsWith("'") && val.endsWith("'")) ||
61
+ (val.startsWith('"') && val.endsWith('"'))
62
+ ) {
63
+ val = val.slice(1, -1);
64
+ }
65
+ out[key] = val;
66
+ }
67
+ return out;
68
+ }
69
+
70
+ /**
71
+ * Find the first `.md` draft file under
72
+ * `<baseDir>/.design/reflections/incubator/<slug>/`. Returns the
73
+ * absolute path, or `null` if the directory or any `.md` is missing.
74
+ *
75
+ * @param {string} baseDir
76
+ * @param {string} slug
77
+ * @returns {string | null}
78
+ */
79
+ function findDraft(baseDir, slug) {
80
+ const dir = path.join(baseDir, INCUBATOR_DIR, slug);
81
+ if (!fs.existsSync(dir)) return null;
82
+ let entries;
83
+ try {
84
+ entries = fs.readdirSync(dir, { withFileTypes: true });
85
+ } catch {
86
+ return null;
87
+ }
88
+ const mdFiles = entries
89
+ .filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.md'))
90
+ .map((e) => e.name)
91
+ .sort();
92
+ if (mdFiles.length === 0) return null;
93
+ return path.join(dir, mdFiles[0]);
94
+ }
95
+
96
+ /**
97
+ * Read the event chain via `event-chain.cjs.readChain` and return the
98
+ * newest `capability_gap` event timestamp matching `contextHash`, or
99
+ * `null` if none found. Events with invalid `ts` are silently skipped.
100
+ *
101
+ * Recognises both `ev.type === 'capability_gap'` (future field) and
102
+ * `ev.outcome === 'capability_gap'` (existing Phase 22 convention).
103
+ *
104
+ * @param {{baseDir: string, contextHash: string}} input
105
+ * @returns {Date | null}
106
+ */
107
+ function computeNewestGapTimestamp(input) {
108
+ const baseDir = input.baseDir;
109
+ const contextHash = input.contextHash;
110
+ let newest = null;
111
+ for (const ev of eventChain.readChain({ baseDir })) {
112
+ if (!isCapabilityGap(ev)) continue;
113
+ if (ev.context_hash !== contextHash) continue;
114
+ if (typeof ev.ts !== 'string') continue;
115
+ const d = new Date(ev.ts);
116
+ if (Number.isNaN(d.getTime())) continue;
117
+ if (newest === null || d.getTime() > newest.getTime()) {
118
+ newest = d;
119
+ }
120
+ }
121
+ return newest;
122
+ }
123
+
124
+ /**
125
+ * Recognise both the future `type` field and the existing
126
+ * `outcome`-as-type convention used by Phase 22's event chain.
127
+ *
128
+ * @param {Record<string, unknown>} ev
129
+ * @returns {boolean}
130
+ */
131
+ function isCapabilityGap(ev) {
132
+ return ev && (ev.type === 'capability_gap' || ev.outcome === 'capability_gap');
133
+ }
134
+
135
+ /**
136
+ * Scan `.design/reflections/incubator/<slug>/` and return a status per
137
+ * slug. Pure read — never mutates the filesystem.
138
+ *
139
+ * Status values:
140
+ * 'kept' — newest matching event is within TTL
141
+ * 'would-archive' — newest matching event is older than TTL
142
+ * 'no-events' — no matching `capability_gap` events for the slug
143
+ * 'no-draft' — slug dir has no `.md` files
144
+ * 'no-context-hash' — draft frontmatter missing `context_hash`
145
+ *
146
+ * @param {{baseDir: string, ttlDays?: number, now?: Date}} input
147
+ * @returns {Array<{slug: string, status: string, newestEvent: Date | null, ageDays: number | null, contextHash: string | null}>}
148
+ */
149
+ function scanIncubator(input) {
150
+ const baseDir = input.baseDir;
151
+ const ttlDays = typeof input.ttlDays === 'number' ? input.ttlDays : DEFAULT_TTL_DAYS;
152
+ const now = input.now instanceof Date ? input.now : new Date();
153
+ const incubatorRoot = path.join(baseDir, INCUBATOR_DIR);
154
+
155
+ if (!fs.existsSync(incubatorRoot)) return [];
156
+
157
+ let entries;
158
+ try {
159
+ entries = fs.readdirSync(incubatorRoot, { withFileTypes: true });
160
+ } catch {
161
+ return [];
162
+ }
163
+
164
+ /** @type {Array<{slug: string, status: string, newestEvent: Date | null, ageDays: number | null, contextHash: string | null}>} */
165
+ const results = [];
166
+ for (const e of entries) {
167
+ if (!e.isDirectory()) continue;
168
+ if (e.name === ARCHIVE_SUBDIR) continue;
169
+ const slug = e.name;
170
+
171
+ const draftPath = findDraft(baseDir, slug);
172
+ if (!draftPath) {
173
+ results.push({ slug, status: 'no-draft', newestEvent: null, ageDays: null, contextHash: null });
174
+ continue;
175
+ }
176
+
177
+ let fm;
178
+ try {
179
+ const content = fs.readFileSync(draftPath, 'utf8');
180
+ fm = parseFrontmatter(content);
181
+ } catch {
182
+ results.push({ slug, status: 'no-draft', newestEvent: null, ageDays: null, contextHash: null });
183
+ continue;
184
+ }
185
+
186
+ const contextHash = fm.context_hash;
187
+ if (!contextHash) {
188
+ results.push({ slug, status: 'no-context-hash', newestEvent: null, ageDays: null, contextHash: null });
189
+ continue;
190
+ }
191
+
192
+ const newest = computeNewestGapTimestamp({ baseDir, contextHash });
193
+ if (newest === null) {
194
+ results.push({ slug, status: 'no-events', newestEvent: null, ageDays: null, contextHash });
195
+ continue;
196
+ }
197
+
198
+ const ageMs = now.getTime() - newest.getTime();
199
+ const ageDays = ageMs / 86_400_000;
200
+ const status = ageDays > ttlDays ? 'would-archive' : 'kept';
201
+ results.push({ slug, status, newestEvent: newest, ageDays, contextHash });
202
+ }
203
+ // Sort for determinism.
204
+ results.sort((a, b) => a.slug.localeCompare(b.slug));
205
+ return results;
206
+ }
207
+
208
+ /**
209
+ * Move `<baseDir>/.design/reflections/incubator/<slug>/` to
210
+ * `<baseDir>/.design/reflections/incubator/archive/<slug>/`. On
211
+ * collision, append a `-YYYYMMDD-HHMMSS` suffix derived from `now`.
212
+ *
213
+ * @param {{baseDir: string, slug: string, now?: Date}} input
214
+ * @returns {{srcPath: string, archivePath: string}}
215
+ */
216
+ function archiveSlug(input) {
217
+ const baseDir = input.baseDir;
218
+ const slug = input.slug;
219
+ const now = input.now instanceof Date ? input.now : new Date();
220
+ const src = path.join(baseDir, INCUBATOR_DIR, slug);
221
+ const archiveRoot = path.join(baseDir, INCUBATOR_DIR, ARCHIVE_SUBDIR);
222
+ fs.mkdirSync(archiveRoot, { recursive: true });
223
+ let dest = path.join(archiveRoot, slug);
224
+ if (fs.existsSync(dest)) {
225
+ const stamp = formatTimestamp(now);
226
+ dest = path.join(archiveRoot, `${slug}-${stamp}`);
227
+ }
228
+ fs.renameSync(src, dest);
229
+ return { srcPath: src, archivePath: dest };
230
+ }
231
+
232
+ /**
233
+ * Convert a Date to a `YYYYMMDD-HHMMSS` string. Deterministic for tests.
234
+ *
235
+ * @param {Date} date
236
+ * @returns {string}
237
+ */
238
+ function formatTimestamp(date) {
239
+ const iso = date.toISOString();
240
+ // '2026-05-19T22:53:11.123Z' → '20260519-225311'
241
+ return iso
242
+ .replace(/\.\d{3}Z$/, '')
243
+ .replace(/[-:]/g, '')
244
+ .replace('T', '-');
245
+ }
246
+
247
+ /**
248
+ * CLI arg parser. Supports:
249
+ * --ttl-days N
250
+ * --dry-run
251
+ * --base-dir PATH
252
+ * --help
253
+ *
254
+ * @param {string[]} argv
255
+ * @returns {{help?: boolean, error?: string, ttlDays?: number, dryRun?: boolean, baseDir?: string}}
256
+ */
257
+ function parseArgs(argv) {
258
+ /** @type {{help?: boolean, error?: string, ttlDays?: number, dryRun?: boolean, baseDir?: string}} */
259
+ const out = {
260
+ ttlDays: DEFAULT_TTL_DAYS,
261
+ dryRun: false,
262
+ baseDir: process.cwd(),
263
+ };
264
+ for (let i = 0; i < argv.length; i++) {
265
+ const a = argv[i];
266
+ if (a === '--help' || a === '-h') {
267
+ out.help = true;
268
+ return out;
269
+ } else if (a === '--dry-run') {
270
+ out.dryRun = true;
271
+ } else if (a === '--ttl-days') {
272
+ const v = argv[++i];
273
+ if (v === undefined) return { error: '--ttl-days requires an integer argument' };
274
+ const n = Number.parseInt(v, 10);
275
+ if (!Number.isFinite(n) || n < 0) {
276
+ return { error: `--ttl-days requires a non-negative integer (got ${v})` };
277
+ }
278
+ out.ttlDays = n;
279
+ } else if (a === '--base-dir') {
280
+ const v = argv[++i];
281
+ if (v === undefined) return { error: '--base-dir requires a path argument' };
282
+ out.baseDir = v;
283
+ } else if (a.startsWith('--')) {
284
+ return { error: `unknown flag: ${a}` };
285
+ } else {
286
+ return { error: `unexpected positional argument: ${a}` };
287
+ }
288
+ }
289
+ return out;
290
+ }
291
+
292
+ function printUsage() {
293
+ const lines = [
294
+ 'Usage: node scripts/gsd-cleanup-incubator.cjs [options]',
295
+ '',
296
+ 'Options:',
297
+ ' --ttl-days N Override TTL (default: 30, per CONTEXT D-06).',
298
+ ' --dry-run Log actions without mutating filesystem.',
299
+ ' --base-dir PATH Override project root (for tests). Default: process.cwd().',
300
+ ' --help Print usage and exit.',
301
+ '',
302
+ 'Exit codes:',
303
+ ' 0 success (may include "no archives needed")',
304
+ ' 1 filesystem error (incubator dir unreadable, archive dir create failed)',
305
+ ' 2 invalid CLI args',
306
+ ];
307
+ console.log(lines.join('\n'));
308
+ }
309
+
310
+ /**
311
+ * @param {string[]} argv — argv slice excluding `node` + script path
312
+ * @returns {number} exit code
313
+ */
314
+ function main(argv) {
315
+ const args = parseArgs(argv);
316
+ if (args.help) {
317
+ printUsage();
318
+ return 0;
319
+ }
320
+ if (args.error) {
321
+ console.error(args.error);
322
+ printUsage();
323
+ return 2;
324
+ }
325
+ try {
326
+ const results = scanIncubator({
327
+ baseDir: args.baseDir,
328
+ ttlDays: args.ttlDays,
329
+ now: new Date(),
330
+ });
331
+ for (const r of results) {
332
+ if (r.status === 'would-archive') {
333
+ if (args.dryRun) {
334
+ console.log(`[dry-run] would archive: ${r.slug} (age ${r.ageDays.toFixed(1)}d)`);
335
+ } else {
336
+ const { archivePath } = archiveSlug({ baseDir: args.baseDir, slug: r.slug });
337
+ console.log(`archived: ${r.slug} → ${path.relative(args.baseDir, archivePath)}`);
338
+ }
339
+ } else {
340
+ console.log(`skipped: ${r.slug} (${r.status})`);
341
+ }
342
+ }
343
+ return 0;
344
+ } catch (err) {
345
+ console.error(`error: ${err && err.message ? err.message : String(err)}`);
346
+ return 1;
347
+ }
348
+ }
349
+
350
+ if (require.main === module) {
351
+ process.exit(main(process.argv.slice(2)));
352
+ }
353
+
354
+ module.exports = {
355
+ scanIncubator,
356
+ archiveSlug,
357
+ computeNewestGapTimestamp,
358
+ findDraft,
359
+ parseFrontmatter,
360
+ formatTimestamp,
361
+ isCapabilityGap,
362
+ parseArgs,
363
+ main,
364
+ DEFAULT_TTL_DAYS,
365
+ INCUBATOR_DIR,
366
+ ARCHIVE_SUBDIR,
367
+ };
@@ -15,6 +15,18 @@
15
15
  //
16
16
  // Modifiers: --global (default) | --local; --uninstall; --dry-run;
17
17
  // --config-dir <path>; --help / -h.
18
+ //
19
+ // Read-only modes: --doctor (Phase 28.8 — Tier-2 distribution-channel
20
+ // status; no install side effects). Future Tier-2 channels (Codex
21
+ // plugin via Plan 28-8-C2; aggregated Tier-2 status via Plan 28-8-X2)
22
+ // plug additional sections into the same flag dispatch — see
23
+ // `runDoctor()` below for the section-module pattern.
24
+ //
25
+ // Read-only modes: --doctor (Phase 28.8 — Tier-2 distribution-channel
26
+ // status; no install side effects). Future Tier-2 channels (Codex
27
+ // plugin via Plan 28-8-C2; aggregated Tier-2 status via Plan 28-8-X2)
28
+ // plug additional sections into the same flag dispatch — see
29
+ // `runDoctor()` below for the section-module pattern.
18
30
 
19
31
  const path = require('node:path');
20
32
 
@@ -64,6 +76,7 @@ function helpText() {
64
76
  ' --no-peer-prompt Suppress the post-install peer-CLI detection nudge',
65
77
  ' --register-mcp Register gdd-mcp with detected harnesses (Claude Code, Codex). Opt-in.',
66
78
  ' --no-register-mcp Skip MCP registration (default behavior; included for symmetry).',
79
+ ' --doctor Print Tier-2 distribution-channel status (read-only; no install)',
67
80
  ' --help, -h Show this message',
68
81
  '',
69
82
  'Environment overrides (per-runtime):',
@@ -125,6 +138,47 @@ function summariseResults(results) {
125
138
  return lines.join('\n');
126
139
  }
127
140
 
141
+ // Phase 28.8 — Tier-2 distribution-channel doctor.
142
+ //
143
+ // Read-only status reporter for distribution channels (Cursor Marketplace,
144
+ // Codex Plugins, agentskills.io lint pass). Phase 28.8-X2 D-13/D-16: the
145
+ // three channels are aggregated via `scripts/lib/install/doctor-tier2.cjs`
146
+ // which composes B2's `reportCursorMarketplace`, C2's `checkCodexPlugin`,
147
+ // and A1's `lintSummary` into a single "## Tier-2 Distribution Channels"
148
+ // section with a one-line summary + per-channel subsections.
149
+ //
150
+ // Phase 28.8-X2: B2's and C2's individual doctor sections used to be
151
+ // rendered as standalone blocks here. With X2 the aggregator now owns
152
+ // the entire Tier-2 section — the B2/C2 modules remain callable internals
153
+ // (the aggregator consumes their pure `report*()` functions), but the
154
+ // individual formatters are no longer invoked directly from install.cjs.
155
+ // Rationale: a single section with a unified summary line is what the
156
+ // maintainer wants (Plan 28-8-X2 §<objective>). The aggregator handles
157
+ // throw safety internally (Cursor's malformed-state-file throw becomes
158
+ // a `not-configured` subsection rather than killing the doctor).
159
+ function runDoctor() {
160
+ const projectRoot = process.cwd();
161
+ try {
162
+ const {
163
+ readTier2Status,
164
+ formatTier2Section,
165
+ } = require('./lib/install/doctor-tier2.cjs');
166
+ const status = readTier2Status({ sourceRoot: projectRoot });
167
+ process.stdout.write(formatTier2Section(status) + '\n');
168
+ } catch (err) {
169
+ // The aggregator is throw-resistant (channel errors surface as
170
+ // `not-configured` with detail). A top-level throw here implies the
171
+ // aggregator module itself failed to load — surface inline so the
172
+ // maintainer sees the breakage without losing the whole CLI exit.
173
+ process.stdout.write(
174
+ '## Tier-2 Distribution Channels\n\n'
175
+ + ' ERROR: '
176
+ + (err && err.message ? err.message : String(err))
177
+ + '\n'
178
+ );
179
+ }
180
+ }
181
+
128
182
  async function main() {
129
183
  const { flags, configDir } = parseArgs(process.argv);
130
184
 
@@ -133,6 +187,13 @@ async function main() {
133
187
  process.exit(0);
134
188
  }
135
189
 
190
+ // Phase 28.8 D-16 + B2 — read-only Tier-2 doctor. Early dispatch BEFORE
191
+ // any runtime selection so doctor never performs install side effects.
192
+ if (flags.has('--doctor')) {
193
+ runDoctor();
194
+ process.exit(0);
195
+ }
196
+
136
197
  const dryRun = flags.has('--dry-run');
137
198
  const uninstall = flags.has('--uninstall');
138
199
  const local = flags.has('--local');