@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +116 -0
- package/README.de.md +37 -0
- package/README.fr.md +37 -0
- package/README.it.md +37 -0
- package/README.ja.md +37 -0
- package/README.ko.md +37 -0
- package/README.md +44 -0
- package/README.zh-CN.md +37 -0
- package/SKILL.md +12 -10
- package/agents/design-reflector.md +50 -0
- package/package.json +3 -1
- package/reference/capability-gap-stage-gate.md +261 -0
- package/reference/known-failure-modes.md +185 -0
- package/reference/pseudonymization-rules.md +189 -0
- package/reference/registry.json +22 -1
- package/reference/schemas/events.schema.json +97 -3
- package/reference/schemas/generated.d.ts +319 -4
- package/scripts/build-distribution-bundles.cjs +549 -0
- package/scripts/cli/gdd-events.mjs +35 -2
- package/scripts/gsd-cleanup-incubator.cjs +367 -0
- package/scripts/install.cjs +61 -0
- package/scripts/lib/apply-reflections/incubator-proposals.cjs +448 -0
- package/scripts/lib/bandit-router.cjs +92 -9
- package/scripts/lib/gsd-health-mirror/index.cjs +37 -1
- package/scripts/lib/incubator-author.cjs +845 -0
- package/scripts/lib/install/config-dir.cjs +26 -0
- package/scripts/lib/install/converters/codex-plugin.cjs +407 -0
- package/scripts/lib/install/converters/cursor-marketplace.cjs +309 -0
- package/scripts/lib/install/doctor-codex-plugin.cjs +388 -0
- package/scripts/lib/install/doctor-cursor-marketplace.cjs +366 -0
- package/scripts/lib/install/doctor-tier2.cjs +586 -0
- package/scripts/lib/install/runtimes.cjs +48 -0
- package/scripts/lib/issue-reporter/cli-flag-report.cjs +153 -0
- package/scripts/lib/issue-reporter/consent-prompt.cjs +231 -0
- package/scripts/lib/issue-reporter/dedup.cjs +458 -0
- package/scripts/lib/issue-reporter/destination.cjs +37 -0
- package/scripts/lib/issue-reporter/draft-writer.cjs +157 -0
- package/scripts/lib/issue-reporter/gh-absent-fallback.cjs +220 -0
- package/scripts/lib/issue-reporter/gh-submit.cjs +114 -0
- package/scripts/lib/issue-reporter/kill-switch.cjs +122 -0
- package/scripts/lib/issue-reporter/payload-assembly.cjs +367 -0
- package/scripts/lib/issue-reporter/privacy-diff.cjs +385 -0
- package/scripts/lib/issue-reporter/report-flow.cjs +269 -0
- package/scripts/lib/issue-reporter/triage-matcher.cjs +270 -0
- package/scripts/lib/pseudonymize.cjs +444 -0
- package/scripts/lib/reflections-cycle-writer.cjs +172 -0
- package/scripts/lib/reflector/capability-gap-scan.cjs +751 -0
- package/scripts/lib/reflector-capability-gap-aggregator.cjs +320 -0
- package/scripts/lint-agentskills-spec.cjs +457 -0
- package/scripts/release-smoke-test.cjs +33 -2
- package/scripts/validate-incubator-scope.cjs +133 -0
- package/skills/apply-reflections/SKILL.md +16 -1
- package/skills/apply-reflections/apply-reflections-procedure.md +71 -3
- package/skills/compare/SKILL.md +2 -2
- package/skills/compare/compare-rubric.md +1 -1
- package/skills/darkmode/SKILL.md +2 -2
- package/skills/darkmode/darkmode-audit-procedure.md +1 -1
- package/skills/fast/SKILL.md +46 -0
- package/skills/figma-write/SKILL.md +2 -2
- package/skills/graphify/SKILL.md +2 -2
- package/skills/reflect/SKILL.md +9 -0
- package/skills/reflect/procedures/capability-gap-scan.md +120 -0
- package/skills/report-issue/SKILL.md +53 -0
- package/skills/report-issue/report-issue-procedure.md +120 -0
- package/skills/router/SKILL.md +5 -0
- package/skills/router/capability-gap-emitter.md +65 -0
- package/skills/style/SKILL.md +2 -2
- package/skills/style/style-doc-procedure.md +1 -1
- 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
|
+
};
|
package/scripts/install.cjs
CHANGED
|
@@ -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');
|