@hegemonart/get-design-done 1.28.8 → 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 (53) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +81 -0
  4. package/README.de.md +23 -0
  5. package/README.fr.md +23 -0
  6. package/README.it.md +23 -0
  7. package/README.ja.md +23 -0
  8. package/README.ko.md +23 -0
  9. package/README.md +28 -0
  10. package/README.zh-CN.md +23 -0
  11. package/SKILL.md +2 -0
  12. package/agents/design-reflector.md +50 -0
  13. package/package.json +1 -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/cli/gdd-events.mjs +35 -2
  21. package/scripts/gsd-cleanup-incubator.cjs +367 -0
  22. package/scripts/lib/apply-reflections/incubator-proposals.cjs +448 -0
  23. package/scripts/lib/bandit-router.cjs +92 -9
  24. package/scripts/lib/gsd-health-mirror/index.cjs +37 -1
  25. package/scripts/lib/incubator-author.cjs +845 -0
  26. package/scripts/lib/issue-reporter/cli-flag-report.cjs +153 -0
  27. package/scripts/lib/issue-reporter/consent-prompt.cjs +231 -0
  28. package/scripts/lib/issue-reporter/dedup.cjs +458 -0
  29. package/scripts/lib/issue-reporter/destination.cjs +37 -0
  30. package/scripts/lib/issue-reporter/draft-writer.cjs +157 -0
  31. package/scripts/lib/issue-reporter/gh-absent-fallback.cjs +220 -0
  32. package/scripts/lib/issue-reporter/gh-submit.cjs +114 -0
  33. package/scripts/lib/issue-reporter/kill-switch.cjs +122 -0
  34. package/scripts/lib/issue-reporter/payload-assembly.cjs +367 -0
  35. package/scripts/lib/issue-reporter/privacy-diff.cjs +385 -0
  36. package/scripts/lib/issue-reporter/report-flow.cjs +269 -0
  37. package/scripts/lib/issue-reporter/triage-matcher.cjs +270 -0
  38. package/scripts/lib/pseudonymize.cjs +444 -0
  39. package/scripts/lib/reflections-cycle-writer.cjs +172 -0
  40. package/scripts/lib/reflector/capability-gap-scan.cjs +751 -0
  41. package/scripts/lib/reflector-capability-gap-aggregator.cjs +320 -0
  42. package/scripts/release-smoke-test.cjs +33 -2
  43. package/scripts/validate-incubator-scope.cjs +133 -0
  44. package/skills/apply-reflections/SKILL.md +16 -1
  45. package/skills/apply-reflections/apply-reflections-procedure.md +71 -3
  46. package/skills/fast/SKILL.md +46 -0
  47. package/skills/reflect/SKILL.md +9 -0
  48. package/skills/reflect/procedures/capability-gap-scan.md +120 -0
  49. package/skills/report-issue/SKILL.md +53 -0
  50. package/skills/report-issue/report-issue-procedure.md +120 -0
  51. package/skills/router/SKILL.md +5 -0
  52. package/skills/router/capability-gap-emitter.md +65 -0
  53. package/skills/update/SKILL.md +3 -2
@@ -0,0 +1,270 @@
1
+ /**
2
+ * scripts/lib/issue-reporter/triage-matcher.cjs — Plan 30-03
3
+ *
4
+ * Phase 30 triage gate. Pure module consulted by the report-issue skill
5
+ * (Plan 30-04) BEFORE the consent prompt. If a catalogued failure mode
6
+ * matches the user's error, the gate surfaces "this looks like X — try
7
+ * Y" and exits the report flow without prompting (D-07). --force-report
8
+ * bypasses the gate but still requires consent (D-11).
9
+ *
10
+ * matchKnownFailure(errorContext) → { matched: false }
11
+ * | { matched: true, modeId, diagnosis, remedy, severity, propose_report }
12
+ *
13
+ * Inputs (errorContext shape; subset; only these fields are consulted):
14
+ * - message: string (error.message)
15
+ * - stack: string (error.stack; may be undefined)
16
+ * - command: string (optional; reserved for future enrichment, not matched today)
17
+ *
18
+ * Pattern application:
19
+ * - Compile entry.pattern as `new RegExp(pattern)` (no flags assumed).
20
+ * - Test against `[errorContext.message, errorContext.stack].filter(Boolean).join("\n")`.
21
+ * - First entry whose regex tests true wins. File order is authoritative.
22
+ *
23
+ * Resilience guarantees (proven by tests/triage-matcher.test.cjs):
24
+ * - Invalid regex inside the catalogue → skip + warn once, NEVER throw.
25
+ * - Missing / unparseable catalogue file → return { matched: false }, warn once, NEVER throw.
26
+ * - Malformed errorContext (null, missing fields, wrong types) → return { matched: false }.
27
+ * - No process.exit, no network I/O, no writes to .design/ or reference/.
28
+ *
29
+ * Test-injection helpers (NOT for production use; underscore-prefixed):
30
+ * - __setCataloguePath(absPath): override the catalogue path (also via
31
+ * GDD_KNOWN_FAILURE_MODES_PATH env var; explicit setter wins).
32
+ * - __resetCache(): clear the parsed-catalogue cache (forces a re-read on next call).
33
+ *
34
+ * Conforms to D-07 (gate runs before issue prompt), D-11 (propose_report
35
+ * flag round-trips so 30-04 can gate --report on the whitelist), D-13
36
+ * (tests use synthetic fixtures only), D-14 (no payload assembly).
37
+ */
38
+
39
+ 'use strict';
40
+
41
+ const fs = require('node:fs');
42
+ const path = require('node:path');
43
+
44
+ const SEVERITIES = new Set(['low', 'medium', 'high']);
45
+
46
+ /** Resolve the repo root by walking up from this file until a package.json is found. */
47
+ function findRepoRoot() {
48
+ let dir = __dirname;
49
+ for (let i = 0; i < 12; i++) {
50
+ if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
51
+ const parent = path.dirname(dir);
52
+ if (parent === dir) break;
53
+ dir = parent;
54
+ }
55
+ // Fall back to three-up (scripts/lib/issue-reporter -> repo root).
56
+ return path.resolve(__dirname, '..', '..', '..');
57
+ }
58
+
59
+ const DEFAULT_CATALOGUE_PATH = path.join(
60
+ findRepoRoot(),
61
+ 'reference',
62
+ 'known-failure-modes.md'
63
+ );
64
+
65
+ let _cataloguePathOverride = null;
66
+ let _entriesCache = null;
67
+ let _missingCatalogueWarned = false;
68
+
69
+ /** Resolve the catalogue path: explicit setter > env var > default. */
70
+ function resolveCataloguePath() {
71
+ if (typeof _cataloguePathOverride === 'string' && _cataloguePathOverride.length > 0) {
72
+ return _cataloguePathOverride;
73
+ }
74
+ const envOverride = process.env.GDD_KNOWN_FAILURE_MODES_PATH;
75
+ if (typeof envOverride === 'string' && envOverride.length > 0) {
76
+ return envOverride;
77
+ }
78
+ return DEFAULT_CATALOGUE_PATH;
79
+ }
80
+
81
+ /**
82
+ * Extract every fenced ```yaml block from a markdown string and parse
83
+ * each as a flat key:value mapping (single-level, no nesting). Invalid
84
+ * entries (missing required fields, bad severity, bad regex) are dropped
85
+ * with a one-line console.warn referencing the entry id.
86
+ *
87
+ * Matches the codebase YAML-from-markdown convention used by
88
+ * scripts/lib/domain-primitives/nng.cjs.
89
+ *
90
+ * @param {string} markdown
91
+ * @returns {Array<{id:string, pattern:string, diagnosis:string, remedy:string, severity:string, propose_report?:boolean, regex:RegExp}>}
92
+ */
93
+ function parseEntries(markdown) {
94
+ const out = [];
95
+ const re = /```yaml\s*\n([\s\S]*?)\n```/g;
96
+ let m;
97
+ while ((m = re.exec(markdown)) !== null) {
98
+ const body = m[1];
99
+ /** @type {Record<string,string>} */
100
+ const fields = {};
101
+ for (const line of body.split(/\r?\n/)) {
102
+ const kv = line.match(/^\s*([A-Za-z_][\w-]*)\s*:\s*(.*?)\s*$/);
103
+ if (!kv) continue;
104
+ let v = kv[2];
105
+ // Strip surrounding single or double quotes — matches nng.cjs handling.
106
+ if ((v.startsWith("'") && v.endsWith("'")) || (v.startsWith('"') && v.endsWith('"'))) {
107
+ v = v.slice(1, -1);
108
+ // Unescape doubled-single-quotes (YAML single-quoted-scalar convention).
109
+ v = v.replace(/''/g, "'");
110
+ }
111
+ fields[kv[1]] = v;
112
+ }
113
+ // Required-field guard — skip silently if not a real entry (e.g. unrelated yaml block).
114
+ if (!fields.id || !fields.pattern || !fields.diagnosis || !fields.remedy || !fields.severity) {
115
+ continue;
116
+ }
117
+ if (!SEVERITIES.has(fields.severity)) {
118
+ console.warn(
119
+ `[triage-matcher] skip ${fields.id}: invalid severity '${fields.severity}' (expected low|medium|high)`
120
+ );
121
+ continue;
122
+ }
123
+ let regex;
124
+ try {
125
+ regex = new RegExp(fields.pattern);
126
+ } catch (e) {
127
+ console.warn(
128
+ `[triage-matcher] skip ${fields.id}: invalid regex (${e && e.message ? e.message : 'compile error'})`
129
+ );
130
+ continue;
131
+ }
132
+ const proposeReport = fields.propose_report === 'true';
133
+ out.push({
134
+ id: fields.id,
135
+ pattern: fields.pattern,
136
+ diagnosis: fields.diagnosis,
137
+ remedy: fields.remedy,
138
+ severity: fields.severity,
139
+ propose_report: proposeReport,
140
+ regex,
141
+ });
142
+ }
143
+ return out;
144
+ }
145
+
146
+ /** Load + cache the entry list. On any load failure, returns []. */
147
+ function loadEntries() {
148
+ if (_entriesCache !== null) return _entriesCache;
149
+ const file = resolveCataloguePath();
150
+ let md;
151
+ try {
152
+ md = fs.readFileSync(file, 'utf8');
153
+ } catch (e) {
154
+ if (!_missingCatalogueWarned) {
155
+ _missingCatalogueWarned = true;
156
+ console.warn(
157
+ `[triage-matcher] catalogue unreadable at ${file}: ${e && e.message ? e.message : 'read error'}`
158
+ );
159
+ }
160
+ _entriesCache = [];
161
+ return _entriesCache;
162
+ }
163
+ try {
164
+ _entriesCache = parseEntries(md);
165
+ } catch (e) {
166
+ // parseEntries() itself does not throw under any tested path, but guard anyway
167
+ // — never let an upstream surprise propagate out of matchKnownFailure.
168
+ console.warn(
169
+ `[triage-matcher] catalogue parse failed at ${file}: ${e && e.message ? e.message : 'parse error'}`
170
+ );
171
+ _entriesCache = [];
172
+ }
173
+ return _entriesCache;
174
+ }
175
+
176
+ /**
177
+ * @param {{message?: string, stack?: string, command?: string} | null | undefined} errorContext
178
+ * @returns {{matched: false} | {matched: true, modeId: string, diagnosis: string, remedy: string, severity: string, propose_report: boolean}}
179
+ */
180
+ function matchKnownFailure(errorContext) {
181
+ // Tolerate any input shape — return {matched:false} rather than throw.
182
+ if (!errorContext || typeof errorContext !== 'object') return { matched: false };
183
+
184
+ const msg = typeof errorContext.message === 'string' ? errorContext.message : '';
185
+ const stk = typeof errorContext.stack === 'string' ? errorContext.stack : '';
186
+ const haystack = [msg, stk].filter(Boolean).join('\n');
187
+ if (haystack.length === 0) return { matched: false };
188
+
189
+ let entries;
190
+ try {
191
+ entries = loadEntries();
192
+ } catch {
193
+ // Defensive — loadEntries already guards every IO path.
194
+ return { matched: false };
195
+ }
196
+ if (!Array.isArray(entries) || entries.length === 0) {
197
+ return { matched: false };
198
+ }
199
+
200
+ for (const e of entries) {
201
+ let hit;
202
+ try {
203
+ hit = e.regex.test(haystack);
204
+ } catch {
205
+ // RegExp.test should not throw on string input, but guard anyway.
206
+ continue;
207
+ }
208
+ if (hit) {
209
+ return {
210
+ matched: true,
211
+ modeId: e.id,
212
+ diagnosis: e.diagnosis,
213
+ remedy: e.remedy,
214
+ severity: e.severity,
215
+ propose_report: e.propose_report === true,
216
+ };
217
+ }
218
+ }
219
+ return { matched: false };
220
+ }
221
+
222
+ /**
223
+ * List the modes flagged `propose_report: true` in the catalogue.
224
+ *
225
+ * Used by Plan 30-04's cli-flag-report.cjs to derive the D-11
226
+ * `--report` whitelist: a command only grows the `--report` flag if
227
+ * at least one propose_report=true mode in the catalogue could plausibly
228
+ * apply to it. The matcher itself does NOT act on this flag — it's
229
+ * advisory metadata consumed by the report-flow orchestrator.
230
+ *
231
+ * @returns {Array<{modeId: string, propose_report: true, severity: string}>}
232
+ */
233
+ function listProposeReportModes() {
234
+ let entries;
235
+ try {
236
+ entries = loadEntries();
237
+ } catch {
238
+ return [];
239
+ }
240
+ if (!Array.isArray(entries)) return [];
241
+ return entries
242
+ .filter((e) => e && e.propose_report === true)
243
+ .map((e) => ({
244
+ modeId: e.id,
245
+ propose_report: true,
246
+ severity: e.severity,
247
+ }));
248
+ }
249
+
250
+ /** Test helper — override the catalogue path. */
251
+ function __setCataloguePath(absPath) {
252
+ _cataloguePathOverride = absPath;
253
+ }
254
+
255
+ /** Test helper — clear the parsed-catalogue cache. */
256
+ function __resetCache() {
257
+ _entriesCache = null;
258
+ _missingCatalogueWarned = false;
259
+ }
260
+
261
+ module.exports = {
262
+ matchKnownFailure,
263
+ listProposeReportModes,
264
+ // Underscore-prefixed test injection helpers; not part of the public API.
265
+ __setCataloguePath,
266
+ __resetCache,
267
+ // Exported for higher-level consumers that may want to introspect the
268
+ // catalogue without invoking match logic. Internal use only.
269
+ _parseEntries: parseEntries,
270
+ };