@hegemonart/get-design-done 1.31.0 → 1.32.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 (180) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +75 -0
  4. package/NOTICE +262 -0
  5. package/README.md +13 -1
  6. package/SKILL.md +4 -0
  7. package/agents/design-authority-watcher.md +1 -1
  8. package/agents/perf-analyzer.md +2 -2
  9. package/bin/gdd-mcp +78 -0
  10. package/bin/gdd-sdk +34 -24
  11. package/bin/gdd-state-mcp +78 -0
  12. package/{README.de.md → docs/i18n/README.de.md} +1 -1
  13. package/{README.fr.md → docs/i18n/README.fr.md} +1 -1
  14. package/{README.it.md → docs/i18n/README.it.md} +1 -1
  15. package/{README.ja.md → docs/i18n/README.ja.md} +1 -1
  16. package/{README.ko.md → docs/i18n/README.ko.md} +1 -1
  17. package/{README.zh-CN.md → docs/i18n/README.zh-CN.md} +1 -1
  18. package/hooks/_hook-emit.js +1 -1
  19. package/hooks/budget-enforcer.ts +5 -5
  20. package/hooks/context-exhaustion.ts +2 -2
  21. package/hooks/gdd-precompact-snapshot.js +3 -3
  22. package/hooks/gdd-read-injection-scanner.ts +2 -2
  23. package/hooks/gdd-sessionstart-recap.js +1 -1
  24. package/hooks/gdd-turn-closeout.js +1 -1
  25. package/hooks/hooks.json +9 -0
  26. package/hooks/inject-using-gdd.sh +72 -0
  27. package/hooks/run-hook.cmd +35 -0
  28. package/package.json +20 -9
  29. package/recipes/.gitkeep +0 -0
  30. package/reference/schemas/events.schema.json +63 -1
  31. package/reference/schemas/recipe.schema.json +33 -0
  32. package/scripts/cli/gdd-events.mjs +5 -5
  33. package/scripts/lib/cache/gdd-cache-manager.cjs +1 -1
  34. package/scripts/lib/cli/index.ts +22 -160
  35. package/scripts/lib/connection-probe/index.cjs +1 -1
  36. package/scripts/lib/discuss-parallel-runner/aggregator.ts +1 -1
  37. package/scripts/lib/discuss-parallel-runner/index.ts +1 -1
  38. package/scripts/lib/error-classifier.cjs +24 -227
  39. package/scripts/lib/event-stream/index.ts +25 -193
  40. package/scripts/lib/gdd-errors/index.ts +24 -213
  41. package/scripts/lib/gdd-state/index.ts +23 -161
  42. package/scripts/lib/health-mirror/index.cjs +79 -1
  43. package/scripts/lib/iteration-budget.cjs +23 -199
  44. package/scripts/lib/jittered-backoff.cjs +24 -107
  45. package/scripts/lib/lockfile.cjs +23 -195
  46. package/scripts/lib/logger/index.ts +1 -1
  47. package/scripts/lib/parallelism-engine/concurrency-tuner.cjs +1 -1
  48. package/scripts/lib/perf-analyzer/index.cjs +1 -1
  49. package/scripts/lib/pipeline-runner/index.ts +4 -4
  50. package/scripts/lib/pipeline-runner/state-machine.ts +1 -1
  51. package/scripts/lib/prompt-dedup/index.cjs +1 -1
  52. package/scripts/lib/rate-guard.cjs +2 -2
  53. package/scripts/lib/recipe-loader.cjs +142 -0
  54. package/scripts/lib/session-runner/errors.ts +3 -3
  55. package/scripts/lib/session-runner/index.ts +3 -3
  56. package/scripts/lib/session-runner/transcript.ts +1 -1
  57. package/scripts/lib/tool-scoping/index.ts +1 -1
  58. package/scripts/mcp-servers/gdd-mcp/server.ts +29 -311
  59. package/scripts/mcp-servers/gdd-state/server.ts +28 -282
  60. package/sdk/README.md +45 -0
  61. package/{scripts/lib → sdk}/cli/commands/audit.ts +3 -3
  62. package/{scripts/lib → sdk}/cli/commands/init.ts +3 -3
  63. package/{scripts/lib → sdk}/cli/commands/query.ts +4 -4
  64. package/{scripts/lib → sdk}/cli/commands/run.ts +5 -5
  65. package/{scripts/lib → sdk}/cli/commands/stage.ts +5 -5
  66. package/sdk/cli/index.js +8091 -0
  67. package/sdk/cli/index.ts +172 -0
  68. package/{scripts/lib → sdk}/cli/parse-args.ts +2 -2
  69. package/{scripts/lib/gdd-errors → sdk/errors}/classification.ts +1 -1
  70. package/sdk/errors/index.ts +218 -0
  71. package/{scripts/lib → sdk}/event-stream/emitter.ts +1 -1
  72. package/sdk/event-stream/index.ts +197 -0
  73. package/{scripts/lib → sdk}/event-stream/reader.ts +1 -1
  74. package/{scripts/lib → sdk}/event-stream/types.ts +2 -2
  75. package/{scripts/lib → sdk}/event-stream/writer.ts +1 -1
  76. package/sdk/index.ts +19 -0
  77. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/README.md +3 -3
  78. package/sdk/mcp/gdd-mcp/server.js +1966 -0
  79. package/sdk/mcp/gdd-mcp/server.ts +325 -0
  80. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_cycle_recap.ts +3 -3
  81. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_decisions_list.ts +2 -2
  82. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_events_tail.ts +3 -3
  83. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_health.ts +2 -2
  84. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_intel_get.ts +2 -2
  85. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_learnings_digest.ts +2 -2
  86. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phase_current.ts +2 -2
  87. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phases_list.ts +2 -2
  88. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_plans_list.ts +2 -2
  89. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_reflections_latest.ts +2 -2
  90. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_status.ts +3 -3
  91. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_telemetry_query.ts +3 -3
  92. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/index.ts +2 -2
  93. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/shared.ts +3 -3
  94. package/sdk/mcp/gdd-state/server.js +2790 -0
  95. package/sdk/mcp/gdd-state/server.ts +294 -0
  96. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_blocker.ts +3 -3
  97. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_decision.ts +3 -3
  98. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_must_have.ts +3 -3
  99. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/checkpoint.ts +2 -2
  100. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/frontmatter_update.ts +2 -2
  101. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/get.ts +3 -3
  102. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/index.ts +1 -1
  103. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/probe_connections.ts +3 -3
  104. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/resolve_blocker.ts +3 -3
  105. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/set_status.ts +2 -2
  106. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/shared.ts +8 -8
  107. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/transition_stage.ts +4 -4
  108. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/update_progress.ts +2 -2
  109. package/sdk/primitives/error-classifier.cjs +232 -0
  110. package/sdk/primitives/iteration-budget.cjs +205 -0
  111. package/sdk/primitives/jittered-backoff.cjs +112 -0
  112. package/sdk/primitives/lockfile.cjs +201 -0
  113. package/{scripts/lib/gdd-state → sdk/state}/gates.ts +1 -1
  114. package/sdk/state/index.ts +167 -0
  115. package/{scripts/lib/gdd-state → sdk/state}/lockfile.ts +1 -1
  116. package/{scripts/lib/gdd-state → sdk/state}/mutator.ts +1 -1
  117. package/{scripts/lib/gdd-state → sdk/state}/parser.ts +1 -1
  118. package/{scripts/lib/gdd-state → sdk/state}/types.ts +4 -4
  119. package/skills/audit/SKILL.md +13 -0
  120. package/skills/brief/SKILL.md +25 -0
  121. package/skills/design/SKILL.md +17 -0
  122. package/skills/discuss/SKILL.md +13 -0
  123. package/skills/explore/SKILL.md +17 -0
  124. package/skills/health/SKILL.md +6 -0
  125. package/skills/plan/SKILL.md +25 -0
  126. package/skills/quality-gate/SKILL.md +2 -2
  127. package/skills/router/SKILL.md +4 -0
  128. package/skills/router/router-pick-emitter.md +78 -0
  129. package/skills/using-gdd/SKILL.md +78 -0
  130. package/skills/verify/SKILL.md +17 -0
  131. package/scripts/aggregate-agent-metrics.ts +0 -282
  132. package/scripts/bootstrap-manifest.txt +0 -3
  133. package/scripts/bootstrap.sh +0 -80
  134. package/scripts/build-distribution-bundles.cjs +0 -549
  135. package/scripts/build-intel.cjs +0 -486
  136. package/scripts/codegen-schema-types.ts +0 -149
  137. package/scripts/detect-stale-refs.cjs +0 -107
  138. package/scripts/e2e/run-headless.ts +0 -514
  139. package/scripts/extract-changelog-section.cjs +0 -58
  140. package/scripts/gsd-cleanup-incubator.cjs +0 -367
  141. package/scripts/injection-patterns.cjs +0 -58
  142. package/scripts/lint-agentskills-spec.cjs +0 -457
  143. package/scripts/release-smoke-test.cjs +0 -200
  144. package/scripts/rollback-release.sh +0 -42
  145. package/scripts/run-injection-scanner-ci.cjs +0 -83
  146. package/scripts/tests/test-authority-rejected-kinds.sh +0 -58
  147. package/scripts/tests/test-authority-watcher-diff.sh +0 -113
  148. package/scripts/tests/test-motion-provenance.sh +0 -64
  149. package/scripts/validate-frontmatter.ts +0 -409
  150. package/scripts/validate-incubator-scope.cjs +0 -133
  151. package/scripts/validate-schemas.ts +0 -401
  152. package/scripts/validate-skill-length.cjs +0 -283
  153. package/scripts/verify-version-sync.cjs +0 -30
  154. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_cycle_recap.schema.json +0 -0
  155. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_decisions_list.schema.json +0 -0
  156. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_events_tail.schema.json +0 -0
  157. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_health.schema.json +0 -0
  158. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_intel_get.schema.json +0 -0
  159. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_learnings_digest.schema.json +0 -0
  160. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phase_current.schema.json +0 -0
  161. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phases_list.schema.json +0 -0
  162. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_plans_list.schema.json +0 -0
  163. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_reflections_latest.schema.json +0 -0
  164. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_status.schema.json +0 -0
  165. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_telemetry_query.schema.json +0 -0
  166. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_blocker.schema.json +0 -0
  167. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_decision.schema.json +0 -0
  168. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_must_have.schema.json +0 -0
  169. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/checkpoint.schema.json +0 -0
  170. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/frontmatter_update.schema.json +0 -0
  171. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/get.schema.json +0 -0
  172. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/probe_connections.schema.json +0 -0
  173. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/resolve_blocker.schema.json +0 -0
  174. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/set_status.schema.json +0 -0
  175. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/transition_stage.schema.json +0 -0
  176. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/update_progress.schema.json +0 -0
  177. /package/{scripts/lib → sdk/primitives}/error-classifier.d.cts +0 -0
  178. /package/{scripts/lib → sdk/primitives}/iteration-budget.d.cts +0 -0
  179. /package/{scripts/lib → sdk/primitives}/jittered-backoff.d.cts +0 -0
  180. /package/{scripts/lib → sdk/primitives}/lockfile.d.cts +0 -0
@@ -1,367 +0,0 @@
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
- };
@@ -1,58 +0,0 @@
1
- 'use strict';
2
- // Shared prompt-injection patterns — single source of truth for both
3
- // hooks/gdd-read-injection-scanner.js (runtime hook) and
4
- // scripts/run-injection-scanner-ci.cjs (CI scanner).
5
- // Add new patterns here; both consumers pick them up automatically.
6
- //
7
- // Phase 14.5 adds three new families: invisible-Unicode obfuscation,
8
- // HTML-comment instruction hijacks, and secret-exfil trigger patterns.
9
-
10
- // Zero-width + word-joiner + BOM + bidi overrides. Used for detection
11
- // AND as a normalization stripper for hooks that run scan after NFKC.
12
- const _CONTEXT_INVISIBLE_CHARS = /[\u200B-\u200D\u2060\uFEFF\u202A-\u202E]/;
13
-
14
- const INJECTION_PATTERNS = [
15
- // ── classic prompt-injection verbs ──────────────────────────────────
16
- { name: 'ignore previous', re: /ignore\s+(all\s+)?(previous|prior|above)\s+instructions?/i },
17
- { name: 'disregard previous', re: /disregard\s+(all\s+)?(previous|prior|above)\s+instructions?/i },
18
- { name: 'forget previous', re: /forget\s+(the\s+|all\s+)?(previous|prior|above)/i },
19
- { name: 'you are now a different', re: /you\s+are\s+now\s+a\s+different/i },
20
- { name: 'system: you are', re: /system\s*:\s*you\s+are/i },
21
- { name: 'role tag injection', re: /<\s*\/?\s*(system|assistant|human)\s*>/i },
22
- { name: '[INST] fragment', re: /\[INST\]/i },
23
- { name: '### instruction fragment',re: /###\s*instruction/i },
24
-
25
- // ── invisible-Unicode obfuscation (14.5 new family) ─────────────────
26
- { name: 'invisible-unicode chars', re: _CONTEXT_INVISIBLE_CHARS },
27
- { name: 'bidi-override instruction', re: /[\u202A-\u202E][^\n]*(ignore|disregard|forget|system\s*:)/i },
28
-
29
- // ── HTML-comment / hidden-element instruction hijack (14.5 new) ─────
30
- { name: 'html-comment system', re: /<!--\s*system\s*:/i },
31
- { name: 'html-comment assistant', re: /<!--\s*assistant\s*:/i },
32
- { name: 'html-comment ignore', re: /<!--\s*(ignore|disregard|forget)\b/i },
33
- { name: 'hidden div system', re: /<div\s+[^>]*style\s*=\s*["'][^"']*display\s*:\s*none[^"']*["'][^>]*>\s*(system|ignore|disregard)/i },
34
- { name: 'hidden span system', re: /<span\s+[^>]*style\s*=\s*["'][^"']*visibility\s*:\s*hidden[^"']*["'][^>]*>\s*(system|ignore|disregard)/i },
35
- { name: 'zero-font-size trick', re: /style\s*=\s*["'][^"']*font-size\s*:\s*0[^"']*["'][^>]*>\s*(ignore|system|disregard)/i },
36
-
37
- // ── secret-exfil trigger patterns (14.5 new) ─────────────────────────
38
- { name: 'curl-with-api-key-env', re: /curl\s+[^|\n]*\$\{?[A-Z][A-Z0-9_]*_(KEY|TOKEN|SECRET|PASSWORD|AUTH)\}?/ },
39
- { name: 'cat-dotenv', re: /\bcat\s+\.env(\.[a-z]+)?\b/ },
40
- { name: 'printenv-leak', re: /\bprintenv\b[^\n]{0,80}\|\s*(curl|wget|nc|ssh)/ },
41
- { name: 'tar-home-netcat', re: /\btar\s+c[fzvj]+\s+-\s+~[^\n]*\|\s*(nc|ssh|curl)/ },
42
- { name: 'env-dot-leak', re: /process\.env\.[A-Z][A-Z0-9_]*_(KEY|TOKEN|SECRET)\s*[^;,\n]*(fetch|axios|XMLHttpRequest|http\.request)/ },
43
- { name: 'ssh-key-cat', re: /\bcat\s+~?\/?\.ssh\/id_(rsa|ed25519|ecdsa|dsa)\b/ },
44
- ];
45
-
46
- /**
47
- * Apply patterns to content and return matched pattern names (deduped).
48
- */
49
- function scan(content) {
50
- if (typeof content !== 'string' || !content) return [];
51
- const hits = [];
52
- for (const { name, re } of INJECTION_PATTERNS) {
53
- if (re.test(content)) hits.push(name);
54
- }
55
- return hits;
56
- }
57
-
58
- module.exports = { INJECTION_PATTERNS, _CONTEXT_INVISIBLE_CHARS, scan };