@hegemonart/get-design-done 1.30.6 → 1.31.5
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 +6 -3
- package/.claude-plugin/plugin.json +5 -2
- package/CHANGELOG.md +105 -0
- package/NOTICE +224 -0
- package/README.md +22 -1
- package/SKILL.md +1 -0
- package/agents/design-authority-watcher.md +1 -1
- package/agents/perf-analyzer.md +2 -2
- package/bin/gdd-mcp +78 -0
- package/bin/gdd-sdk +34 -24
- package/bin/gdd-state-mcp +78 -0
- package/{README.de.md → docs/i18n/README.de.md} +1 -1
- package/{README.fr.md → docs/i18n/README.fr.md} +1 -1
- package/{README.it.md → docs/i18n/README.it.md} +1 -1
- package/{README.ja.md → docs/i18n/README.ja.md} +1 -1
- package/{README.ko.md → docs/i18n/README.ko.md} +1 -1
- package/{README.zh-CN.md → docs/i18n/README.zh-CN.md} +1 -1
- package/hooks/_hook-emit.js +1 -1
- package/hooks/budget-enforcer.ts +5 -5
- package/hooks/context-exhaustion.ts +2 -2
- package/hooks/gdd-precompact-snapshot.js +3 -3
- package/hooks/gdd-read-injection-scanner.ts +2 -2
- package/hooks/gdd-sessionstart-recap.js +1 -1
- package/hooks/gdd-turn-closeout.js +1 -1
- package/package.json +24 -10
- package/recipes/.gitkeep +0 -0
- package/reference/schemas/recipe.schema.json +33 -0
- package/scripts/cli/gdd-events.mjs +5 -5
- package/scripts/lib/cache/gdd-cache-manager.cjs +1 -1
- package/scripts/lib/cli/index.ts +22 -160
- package/scripts/lib/connection-probe/index.cjs +1 -1
- package/scripts/lib/discuss-parallel-runner/aggregator.ts +1 -1
- package/scripts/lib/discuss-parallel-runner/index.ts +1 -1
- package/scripts/lib/error-classifier.cjs +24 -227
- package/scripts/lib/event-stream/index.ts +25 -193
- package/scripts/lib/figma-extract/digest.cjs +430 -0
- package/scripts/lib/figma-extract/parse-url.cjs +87 -0
- package/scripts/lib/figma-extract/payload-schema.json +108 -0
- package/scripts/lib/figma-extract/pull.cjs +394 -0
- package/scripts/lib/figma-extract/receiver.cjs +273 -0
- package/scripts/lib/figma-extract/render-md.cjs +143 -0
- package/scripts/lib/figma-extract/styles-resolver.cjs +147 -0
- package/scripts/lib/figma-extract/walk.cjs +100 -0
- package/scripts/lib/gdd-errors/index.ts +24 -213
- package/scripts/lib/gdd-state/index.ts +23 -161
- package/scripts/lib/health-mirror/index.cjs +88 -1
- package/scripts/lib/iteration-budget.cjs +23 -199
- package/scripts/lib/jittered-backoff.cjs +24 -107
- package/scripts/lib/lockfile.cjs +23 -195
- package/scripts/lib/logger/index.ts +1 -1
- package/scripts/lib/parallelism-engine/concurrency-tuner.cjs +1 -1
- package/scripts/lib/perf-analyzer/index.cjs +1 -1
- package/scripts/lib/pipeline-runner/index.ts +4 -4
- package/scripts/lib/pipeline-runner/state-machine.ts +1 -1
- package/scripts/lib/prompt-dedup/index.cjs +1 -1
- package/scripts/lib/rate-guard.cjs +2 -2
- package/scripts/lib/recipe-loader.cjs +142 -0
- package/scripts/lib/session-runner/errors.ts +3 -3
- package/scripts/lib/session-runner/index.ts +3 -3
- package/scripts/lib/session-runner/transcript.ts +1 -1
- package/scripts/lib/tool-scoping/index.ts +1 -1
- package/scripts/mcp-servers/gdd-mcp/server.ts +29 -311
- package/scripts/mcp-servers/gdd-state/server.ts +28 -282
- package/sdk/README.md +45 -0
- package/{scripts/lib → sdk}/cli/commands/audit.ts +3 -3
- package/{scripts/lib → sdk}/cli/commands/init.ts +3 -3
- package/{scripts/lib → sdk}/cli/commands/query.ts +4 -4
- package/{scripts/lib → sdk}/cli/commands/run.ts +5 -5
- package/{scripts/lib → sdk}/cli/commands/stage.ts +5 -5
- package/sdk/cli/index.js +8091 -0
- package/sdk/cli/index.ts +172 -0
- package/{scripts/lib → sdk}/cli/parse-args.ts +2 -2
- package/{scripts/lib/gdd-errors → sdk/errors}/classification.ts +1 -1
- package/sdk/errors/index.ts +218 -0
- package/{scripts/lib → sdk}/event-stream/emitter.ts +1 -1
- package/sdk/event-stream/index.ts +197 -0
- package/{scripts/lib → sdk}/event-stream/reader.ts +1 -1
- package/{scripts/lib → sdk}/event-stream/types.ts +2 -2
- package/{scripts/lib → sdk}/event-stream/writer.ts +1 -1
- package/sdk/index.ts +19 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/README.md +3 -3
- package/sdk/mcp/gdd-mcp/server.js +1924 -0
- package/sdk/mcp/gdd-mcp/server.ts +325 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_cycle_recap.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_decisions_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_events_tail.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_health.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_intel_get.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_learnings_digest.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phase_current.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phases_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_plans_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_reflections_latest.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_status.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_telemetry_query.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/index.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/shared.ts +3 -3
- package/sdk/mcp/gdd-state/server.js +2790 -0
- package/sdk/mcp/gdd-state/server.ts +294 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_blocker.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_decision.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_must_have.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/checkpoint.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/frontmatter_update.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/get.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/index.ts +1 -1
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/probe_connections.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/resolve_blocker.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/set_status.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/shared.ts +8 -8
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/transition_stage.ts +4 -4
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/update_progress.ts +2 -2
- package/sdk/primitives/error-classifier.cjs +232 -0
- package/sdk/primitives/iteration-budget.cjs +205 -0
- package/sdk/primitives/jittered-backoff.cjs +112 -0
- package/sdk/primitives/lockfile.cjs +201 -0
- package/{scripts/lib/gdd-state → sdk/state}/gates.ts +1 -1
- package/sdk/state/index.ts +167 -0
- package/{scripts/lib/gdd-state → sdk/state}/lockfile.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/mutator.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/parser.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/types.ts +4 -4
- package/skills/figma-extract/SKILL.md +64 -0
- package/skills/health/SKILL.md +10 -0
- package/skills/quality-gate/SKILL.md +2 -2
- package/scripts/aggregate-agent-metrics.ts +0 -282
- package/scripts/bootstrap-manifest.txt +0 -3
- package/scripts/bootstrap.sh +0 -80
- package/scripts/build-distribution-bundles.cjs +0 -549
- package/scripts/build-intel.cjs +0 -486
- package/scripts/codegen-schema-types.ts +0 -149
- package/scripts/detect-stale-refs.cjs +0 -107
- package/scripts/e2e/run-headless.ts +0 -514
- package/scripts/extract-changelog-section.cjs +0 -58
- package/scripts/gsd-cleanup-incubator.cjs +0 -367
- package/scripts/injection-patterns.cjs +0 -58
- package/scripts/lint-agentskills-spec.cjs +0 -457
- package/scripts/release-smoke-test.cjs +0 -200
- package/scripts/rollback-release.sh +0 -42
- package/scripts/run-injection-scanner-ci.cjs +0 -83
- package/scripts/tests/test-authority-rejected-kinds.sh +0 -58
- package/scripts/tests/test-authority-watcher-diff.sh +0 -113
- package/scripts/tests/test-motion-provenance.sh +0 -64
- package/scripts/validate-frontmatter.ts +0 -409
- package/scripts/validate-incubator-scope.cjs +0 -133
- package/scripts/validate-schemas.ts +0 -401
- package/scripts/validate-skill-length.cjs +0 -283
- package/scripts/verify-version-sync.cjs +0 -30
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_cycle_recap.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_decisions_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_events_tail.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_health.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_intel_get.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_learnings_digest.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phase_current.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phases_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_plans_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_reflections_latest.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_status.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_telemetry_query.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_blocker.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_decision.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_must_have.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/checkpoint.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/frontmatter_update.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/get.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/probe_connections.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/resolve_blocker.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/set_status.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/transition_stage.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/update_progress.schema.json +0 -0
- /package/{scripts/lib → sdk/primitives}/error-classifier.d.cts +0 -0
- /package/{scripts/lib → sdk/primitives}/iteration-budget.d.cts +0 -0
- /package/{scripts/lib → sdk/primitives}/jittered-backoff.d.cts +0 -0
- /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 };
|