@hegemonart/get-design-done 1.51.0 → 1.53.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 +96 -0
- package/README.md +4 -0
- package/SKILL.md +2 -0
- package/agents/a11y-mapper.md +30 -1
- package/agents/component-taxonomy-mapper.md +30 -1
- package/agents/design-context-reviewer-gate.md +102 -0
- package/agents/design-context-reviewer.md +186 -0
- package/agents/design-debt-crawler.md +60 -60
- package/agents/design-research-synthesizer.md +27 -1
- package/agents/motion-mapper.md +35 -13
- package/agents/token-mapper.md +30 -1
- package/agents/visual-hierarchy-mapper.md +30 -1
- package/dist/claude-code/.claude/skills/context/SKILL.md +137 -0
- package/dist/claude-code/.claude/skills/discover/SKILL.md +7 -1
- package/dist/claude-code/.claude/skills/explore/SKILL.md +3 -1
- package/dist/claude-code/.claude/skills/migrate-context/SKILL.md +123 -0
- package/dist/claude-code/.claude/skills/progress/SKILL.md +4 -0
- package/package.json +3 -2
- package/reference/design-context-schema.md +159 -0
- package/reference/design-context-tag-vocab.md +82 -0
- package/reference/registry.json +14 -0
- package/reference/schemas/design-context.schema.json +130 -0
- package/reference/schemas/mcp-gdd-tools.schema.json +34 -1
- package/reference/skill-graph.md +3 -1
- package/scripts/lib/design-context/extract-a11y.mjs +188 -0
- package/scripts/lib/design-context/extract-components.mjs +243 -0
- package/scripts/lib/design-context/extract-motion.mjs +248 -0
- package/scripts/lib/design-context/extract-tokens.mjs +234 -0
- package/scripts/lib/design-context/extract-visual-hierarchy.mjs +178 -0
- package/scripts/lib/design-context/integration-map.mjs +251 -0
- package/scripts/lib/design-context/merge-fragments.mjs +227 -0
- package/scripts/lib/design-context-query.cjs +0 -0
- package/scripts/lib/explore-parallel-runner/index.ts +58 -0
- package/scripts/lib/explore-parallel-runner/types.ts +58 -0
- package/scripts/lib/manifest/skills.json +18 -2
- package/scripts/lib/mappers/compute-batches.mjs +625 -0
- package/scripts/lib/mappers/graph-adjacency.mjs +129 -0
- package/scripts/lib/mappers/incremental-discover.cjs +617 -0
- package/scripts/lib/mappers/incremental-discover.d.cts +133 -0
- package/scripts/lib/mappers/neighbor-map.mjs +0 -0
- package/scripts/lib/mcp-tools-lint/index.cjs +3 -1
- package/sdk/cli/index.js +369 -2
- package/sdk/fingerprint/classify.cjs +406 -0
- package/sdk/fingerprint/index.ts +405 -0
- package/sdk/fingerprint/store.cjs +523 -0
- package/sdk/index.ts +1 -0
- package/sdk/mcp/gdd-mcp/schemas/gdd_context_query.schema.json +60 -0
- package/sdk/mcp/gdd-mcp/server.js +474 -158
- package/sdk/mcp/gdd-mcp/server.ts +9 -5
- package/sdk/mcp/gdd-mcp/tools/gdd_context_query.ts +35 -0
- package/sdk/mcp/gdd-mcp/tools/index.ts +18 -13
- package/skills/context/SKILL.md +137 -0
- package/skills/discover/SKILL.md +7 -1
- package/skills/explore/SKILL.md +3 -1
- package/skills/migrate-context/SKILL.md +123 -0
- package/skills/progress/SKILL.md +4 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/lib/design-context/merge-fragments.mjs — Phase 52 (DesignContext graph), executor B.
|
|
3
|
+
//
|
|
4
|
+
// Deterministic, dependency-free fragment merger. Reads N mapper Fragments
|
|
5
|
+
// (schema_version 52.0) and produces the single merged Graph
|
|
6
|
+
// (schema_version 52.0, NO `mapper` field) written atomically to disk.
|
|
7
|
+
//
|
|
8
|
+
// Merge rules
|
|
9
|
+
// -----------
|
|
10
|
+
// NODES — deduped by `id`:
|
|
11
|
+
// - tags : union (order-stable, de-duplicated)
|
|
12
|
+
// - summary : prefer the first NON-STUB summary (non-empty) seen; an
|
|
13
|
+
// empty-string summary is a stub the LLM phase fills later
|
|
14
|
+
// - complexity : prefer the first non-'moderate' value (the stub default)
|
|
15
|
+
// - other fields : first-writer-wins, but later non-empty values fill gaps
|
|
16
|
+
// left empty/absent by the first writer (e.g. value, layer,
|
|
17
|
+
// subtype). type/name keep the first non-empty.
|
|
18
|
+
//
|
|
19
|
+
// EDGES — deduped by (source,target,type); for each edge we verify BOTH
|
|
20
|
+
// endpoints resolve to a node id that exists in SOME fragment (the merged node
|
|
21
|
+
// set):
|
|
22
|
+
// - both endpoints resolve -> keep the edge (this is cross-fragment
|
|
23
|
+
// "recovery": the a11y fragment can reference a
|
|
24
|
+
// component:* node defined only in the component
|
|
25
|
+
// fragment, and the edge survives the merge);
|
|
26
|
+
// - an endpoint is missing -> DROP the edge and report it. A missing
|
|
27
|
+
// endpoint cannot be recovered (no node with that
|
|
28
|
+
// id exists in any fragment), so the edge is
|
|
29
|
+
// truly dangling.
|
|
30
|
+
//
|
|
31
|
+
// "Could not fix" items (dropped dangling edges; any unresolved id) are written
|
|
32
|
+
// to stderr, one per line, prefixed `could-not-fix:`.
|
|
33
|
+
//
|
|
34
|
+
// Public API:
|
|
35
|
+
// merge(fragments) -> { graph, couldNotFix: string[] } (pure)
|
|
36
|
+
// main() -> reads argv globs / .design/fragments/*.json, atomic-writes graph
|
|
37
|
+
//
|
|
38
|
+
// No network, no deps beyond the sibling atomic-write helper, no top-level
|
|
39
|
+
// Date.now() (stamped in main()).
|
|
40
|
+
|
|
41
|
+
import fs from 'node:fs';
|
|
42
|
+
import path from 'node:path';
|
|
43
|
+
import { pathToFileURL } from 'node:url';
|
|
44
|
+
import { atomicWriteJson } from '../graph/atomic-write.mjs';
|
|
45
|
+
|
|
46
|
+
const SCHEMA_VERSION = '52.0';
|
|
47
|
+
const DEFAULT_OUT = path.join('.design', 'context-graph.json');
|
|
48
|
+
const DEFAULT_FRAGMENT_DIR = path.join('.design', 'fragments');
|
|
49
|
+
|
|
50
|
+
const STUB_COMPLEXITY = 'moderate';
|
|
51
|
+
const NODE_RESERVED = new Set(['id', 'type', 'name', 'summary', 'tags', 'complexity']);
|
|
52
|
+
|
|
53
|
+
function isNonStubSummary(s) {
|
|
54
|
+
return typeof s === 'string' && s.trim().length > 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Merge one node into the accumulator map (dedupe by id). */
|
|
58
|
+
function mergeNode(map, node) {
|
|
59
|
+
if (!node || typeof node.id !== 'string') return;
|
|
60
|
+
const existing = map.get(node.id);
|
|
61
|
+
if (!existing) {
|
|
62
|
+
// Clone so we never mutate the caller's input objects.
|
|
63
|
+
map.set(node.id, {
|
|
64
|
+
id: node.id,
|
|
65
|
+
type: node.type,
|
|
66
|
+
name: node.name,
|
|
67
|
+
summary: isNonStubSummary(node.summary) ? node.summary : '',
|
|
68
|
+
tags: Array.isArray(node.tags) ? [...new Set(node.tags)] : [],
|
|
69
|
+
complexity: node.complexity || STUB_COMPLEXITY,
|
|
70
|
+
...copyExtras({}, node),
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// tags: union, order-stable.
|
|
76
|
+
if (Array.isArray(node.tags) && node.tags.length) {
|
|
77
|
+
const seen = new Set(existing.tags);
|
|
78
|
+
for (const t of node.tags) if (!seen.has(t)) { existing.tags.push(t); seen.add(t); }
|
|
79
|
+
}
|
|
80
|
+
// summary: first non-stub wins.
|
|
81
|
+
if (!isNonStubSummary(existing.summary) && isNonStubSummary(node.summary)) {
|
|
82
|
+
existing.summary = node.summary;
|
|
83
|
+
}
|
|
84
|
+
// complexity: first non-default ('moderate' is the stub) wins.
|
|
85
|
+
if (existing.complexity === STUB_COMPLEXITY && node.complexity && node.complexity !== STUB_COMPLEXITY) {
|
|
86
|
+
existing.complexity = node.complexity;
|
|
87
|
+
}
|
|
88
|
+
// type/name: fill if the first writer left them empty.
|
|
89
|
+
if (!existing.type && node.type) existing.type = node.type;
|
|
90
|
+
if (!existing.name && node.name) existing.name = node.name;
|
|
91
|
+
// extras: fill gaps the first writer did not set.
|
|
92
|
+
copyExtras(existing, node, /* fillOnly */ true);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Copy non-reserved fields from `node` onto `target`. */
|
|
96
|
+
function copyExtras(target, node, fillOnly = false) {
|
|
97
|
+
for (const k of Object.keys(node)) {
|
|
98
|
+
if (NODE_RESERVED.has(k)) continue;
|
|
99
|
+
if (fillOnly && target[k] !== undefined && target[k] !== '' && target[k] !== null) continue;
|
|
100
|
+
target[k] = node[k];
|
|
101
|
+
}
|
|
102
|
+
return target;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Pure merge.
|
|
107
|
+
* @param {object[]} fragments array of Fragment objects (each {nodes[], edges[]})
|
|
108
|
+
* @returns {{graph: object, couldNotFix: string[]}}
|
|
109
|
+
*/
|
|
110
|
+
export function merge(fragments) {
|
|
111
|
+
const list = Array.isArray(fragments) ? fragments : [fragments];
|
|
112
|
+
const nodeMap = new Map();
|
|
113
|
+
const couldNotFix = [];
|
|
114
|
+
|
|
115
|
+
// Pass 1: union all nodes (so edge recovery can see ids from any fragment).
|
|
116
|
+
for (const frag of list) {
|
|
117
|
+
if (!frag || !Array.isArray(frag.nodes)) continue;
|
|
118
|
+
for (const n of frag.nodes) mergeNode(nodeMap, n);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Pass 2: dedupe + validate edges against the merged node set.
|
|
122
|
+
const edgeMap = new Map();
|
|
123
|
+
for (const frag of list) {
|
|
124
|
+
if (!frag || !Array.isArray(frag.edges)) continue;
|
|
125
|
+
for (const e of frag.edges) {
|
|
126
|
+
if (!e || typeof e.source !== 'string' || typeof e.target !== 'string' || !e.type) {
|
|
127
|
+
couldNotFix.push(`could-not-fix: malformed edge ${JSON.stringify(e)}`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const srcOk = nodeMap.has(e.source);
|
|
131
|
+
const dstOk = nodeMap.has(e.target);
|
|
132
|
+
if (!srcOk || !dstOk) {
|
|
133
|
+
// Truly dangling — no node with that id exists in ANY fragment.
|
|
134
|
+
const missing = [!srcOk ? `source=${e.source}` : null, !dstOk ? `target=${e.target}` : null]
|
|
135
|
+
.filter(Boolean).join(' ');
|
|
136
|
+
couldNotFix.push(`could-not-fix: dropped dangling edge (${e.type}) ${missing}`);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
// Recovered (or always-resolved) — keep, deduped by (source,target,type).
|
|
140
|
+
const key = `${e.source}--${e.type}-->${e.target}`;
|
|
141
|
+
if (!edgeMap.has(key)) {
|
|
142
|
+
edgeMap.set(key, {
|
|
143
|
+
source: e.source,
|
|
144
|
+
target: e.target,
|
|
145
|
+
type: e.type,
|
|
146
|
+
direction: e.direction || 'forward',
|
|
147
|
+
weight: typeof e.weight === 'number' ? e.weight : 0.5,
|
|
148
|
+
});
|
|
149
|
+
} else if (typeof e.weight === 'number') {
|
|
150
|
+
// Keep the max weight when the same edge appears in two fragments.
|
|
151
|
+
const cur = edgeMap.get(key);
|
|
152
|
+
if (e.weight > cur.weight) cur.weight = e.weight;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const graph = {
|
|
158
|
+
schema_version: SCHEMA_VERSION,
|
|
159
|
+
generated_at: '',
|
|
160
|
+
nodes: [...nodeMap.values()],
|
|
161
|
+
edges: [...edgeMap.values()],
|
|
162
|
+
};
|
|
163
|
+
return { graph, couldNotFix };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// CLI helpers.
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
/** Resolve argv into a concrete list of fragment file paths. */
|
|
171
|
+
function resolveInputs(argv) {
|
|
172
|
+
// argv shape: [...inputs?] [--out <path>]
|
|
173
|
+
const args = [...argv];
|
|
174
|
+
let out = DEFAULT_OUT;
|
|
175
|
+
const inputs = [];
|
|
176
|
+
for (let i = 0; i < args.length; i++) {
|
|
177
|
+
if (args[i] === '--out' || args[i] === '-o') { out = args[++i] || out; continue; }
|
|
178
|
+
inputs.push(args[i]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let files = [];
|
|
182
|
+
if (inputs.length) {
|
|
183
|
+
for (const inp of inputs) {
|
|
184
|
+
let st;
|
|
185
|
+
try { st = fs.statSync(inp); } catch { continue; }
|
|
186
|
+
if (st.isDirectory()) {
|
|
187
|
+
for (const f of fs.readdirSync(inp)) if (f.endsWith('.json')) files.push(path.join(inp, f));
|
|
188
|
+
} else if (st.isFile()) {
|
|
189
|
+
files.push(inp);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} else if (fs.existsSync(DEFAULT_FRAGMENT_DIR)) {
|
|
193
|
+
for (const f of fs.readdirSync(DEFAULT_FRAGMENT_DIR)) {
|
|
194
|
+
if (f.endsWith('.json')) files.push(path.join(DEFAULT_FRAGMENT_DIR, f));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
files = [...new Set(files)].sort(); // deterministic order
|
|
198
|
+
return { files, out };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function readFragment(file) {
|
|
202
|
+
try {
|
|
203
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
204
|
+
} catch (err) {
|
|
205
|
+
process.stderr.write(`could-not-fix: unreadable fragment ${file} (${err.message})\n`);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** CLI entry: read fragments, merge, stamp generated_at, atomic-write graph. */
|
|
211
|
+
export function main(argv = process.argv.slice(2)) {
|
|
212
|
+
const { files, out } = resolveInputs(argv);
|
|
213
|
+
const fragments = files.map(readFragment).filter(Boolean);
|
|
214
|
+
const { graph, couldNotFix } = merge(fragments);
|
|
215
|
+
graph.generated_at = new Date().toISOString();
|
|
216
|
+
|
|
217
|
+
for (const line of couldNotFix) process.stderr.write(line + '\n');
|
|
218
|
+
|
|
219
|
+
atomicWriteJson(out, graph);
|
|
220
|
+
process.stderr.write(
|
|
221
|
+
`merged ${fragments.length} fragment(s) -> ${out} (${graph.nodes.length} nodes, ${graph.edges.length} edges, ${couldNotFix.length} could-not-fix)\n`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const invokedDirectly =
|
|
226
|
+
process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
227
|
+
if (invokedDirectly) main();
|
|
Binary file
|
|
@@ -26,6 +26,10 @@ import { resolve as resolvePath } from 'node:path';
|
|
|
26
26
|
|
|
27
27
|
import { getLogger } from '../logger/index.ts';
|
|
28
28
|
import { resolveConcurrency } from '../parallelism-engine/concurrency-tuner.cjs';
|
|
29
|
+
// Phase 53 (DISC-01): the incremental batching composer. CJS, imported the same
|
|
30
|
+
// way as concurrency-tuner.cjs above. Only invoked when opts.incremental.graph
|
|
31
|
+
// is supplied — the default explore path never loads its ESM/TS dependencies.
|
|
32
|
+
import { planIncremental } from '../mappers/incremental-discover.cjs';
|
|
29
33
|
|
|
30
34
|
import {
|
|
31
35
|
isParallelismSafe,
|
|
@@ -130,6 +134,58 @@ export async function run(
|
|
|
130
134
|
|
|
131
135
|
const outputPath: string = resolvePath(cwd, '.design/DESIGN-PATTERNS.md');
|
|
132
136
|
|
|
137
|
+
// --- Phase 53 (DISC-01): incremental batching ----------------------------
|
|
138
|
+
//
|
|
139
|
+
// ONLY runs when a Phase-52 graph is supplied. Groups the graph into Louvain
|
|
140
|
+
// community batches, runs the change classifier against the prior fingerprint
|
|
141
|
+
// snapshot, and elects which batches to re-map (SKIP=0, PARTIAL=affected,
|
|
142
|
+
// FULL=all). The result rides on `batching`; the spec roster + rolling
|
|
143
|
+
// semaphore below are UNCHANGED. Backward-compatible: absent graph ⇒ undefined
|
|
144
|
+
// batching ⇒ the Phase-21 path is byte-for-byte the same. Never throws —
|
|
145
|
+
// batching is advisory metadata, and a planning failure must not abort mappers.
|
|
146
|
+
let batching: ExploreRunnerResult['batching'] = undefined;
|
|
147
|
+
if (opts.incremental && opts.incremental.graph !== undefined && opts.incremental.graph !== null) {
|
|
148
|
+
try {
|
|
149
|
+
const plan = await planIncremental({
|
|
150
|
+
graph: opts.incremental.graph,
|
|
151
|
+
prevFingerprints: opts.incremental.prevFingerprints,
|
|
152
|
+
opts: {
|
|
153
|
+
...(opts.incremental.forceFull !== undefined ? { forceFull: opts.incremental.forceFull } : {}),
|
|
154
|
+
...(opts.incremental.computeBatchesOpts !== undefined ? { computeBatchesOpts: opts.incremental.computeBatchesOpts } : {}),
|
|
155
|
+
...(opts.incremental.neighborCap !== undefined ? { neighborCap: opts.incremental.neighborCap } : {}),
|
|
156
|
+
...(opts.incremental.thresholds !== undefined ? { thresholds: opts.incremental.thresholds } : {}),
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
batching = Object.freeze({
|
|
160
|
+
action: plan.action,
|
|
161
|
+
method: plan.method,
|
|
162
|
+
modularity: plan.modularity,
|
|
163
|
+
batches: Object.freeze(plan.batches.map((b: { id: string; members: string[]; mergeable: boolean; kind: string; source: string }) => Object.freeze({
|
|
164
|
+
id: b.id,
|
|
165
|
+
members: Object.freeze([...b.members]),
|
|
166
|
+
mergeable: b.mergeable,
|
|
167
|
+
kind: b.kind,
|
|
168
|
+
source: b.source,
|
|
169
|
+
}))),
|
|
170
|
+
batchesToMap: Object.freeze(plan.batchesToMap.map((b: { id: string }) => b.id)),
|
|
171
|
+
neighborMaps: Object.freeze({ ...plan.neighborMaps }),
|
|
172
|
+
classification: Object.freeze({ ...plan.classification }),
|
|
173
|
+
});
|
|
174
|
+
logger.info('explore.runner.batching', {
|
|
175
|
+
action: plan.action,
|
|
176
|
+
method: plan.method,
|
|
177
|
+
batch_count: plan.batches.length,
|
|
178
|
+
batches_to_map: plan.batchesToMap.length,
|
|
179
|
+
structural_count: plan.classification.structuralCount,
|
|
180
|
+
});
|
|
181
|
+
} catch (err) {
|
|
182
|
+
// Planning failure degrades to "no batching" — the mappers still run.
|
|
183
|
+
const message: string = err instanceof Error ? err.message : String(err);
|
|
184
|
+
logger.warn('explore.runner.batching_failed', { message });
|
|
185
|
+
batching = undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
133
189
|
logger.info('explore.runner.started', {
|
|
134
190
|
mapper_count: specs.length,
|
|
135
191
|
concurrency,
|
|
@@ -154,6 +210,7 @@ export async function run(
|
|
|
154
210
|
parallel_count: 0,
|
|
155
211
|
serial_count: 0,
|
|
156
212
|
total_usage: { input_tokens: 0, output_tokens: 0, usd_cost: 0 },
|
|
213
|
+
...(batching !== undefined ? { batching } : {}),
|
|
157
214
|
});
|
|
158
215
|
}
|
|
159
216
|
|
|
@@ -294,5 +351,6 @@ export async function run(
|
|
|
294
351
|
output_tokens: totalOutput,
|
|
295
352
|
usd_cost: totalCost,
|
|
296
353
|
},
|
|
354
|
+
...(batching !== undefined ? { batching } : {}),
|
|
297
355
|
});
|
|
298
356
|
}
|
|
@@ -109,6 +109,35 @@ export interface ExploreRunnerOptions {
|
|
|
109
109
|
readonly pollIntervalMs?: number;
|
|
110
110
|
/** Override the file-watch timeout (ms). Default 600_000 (10 min). */
|
|
111
111
|
readonly timeoutMs?: number;
|
|
112
|
+
/**
|
|
113
|
+
* Phase 53 (DISC-01) — OPTIONAL incremental batching. When supplied with a
|
|
114
|
+
* Phase-52 DesignContext `graph`, the runner runs the change classifier
|
|
115
|
+
* (`scripts/lib/mappers/incremental-discover.cjs#planIncremental`) FIRST,
|
|
116
|
+
* groups the graph into Louvain community batches, attaches a per-batch
|
|
117
|
+
* neighborMap sidecar, and dispatches only the batches the classifier elects
|
|
118
|
+
* to re-map (SKIP=0, PARTIAL=affected, FULL=all). The mapper SPEC roster is
|
|
119
|
+
* unchanged — batching is metadata threaded onto the result so the explore
|
|
120
|
+
* stage can scope each mapper to its community; the rolling semaphore still
|
|
121
|
+
* spawns the spec roster at the tuned concurrency.
|
|
122
|
+
*
|
|
123
|
+
* BACKWARD-COMPATIBLE: when `incremental` is absent (or `graph` is missing),
|
|
124
|
+
* the runner behaves EXACTLY as before — no batching, no classifier, the
|
|
125
|
+
* Phase-21 partition-then-spawn path runs untouched.
|
|
126
|
+
*/
|
|
127
|
+
readonly incremental?: {
|
|
128
|
+
/** Phase-52 DesignContext graph ({ nodes, edges }). Required to batch. */
|
|
129
|
+
readonly graph: unknown;
|
|
130
|
+
/** Prior fingerprint snapshot (store.readCurrent().fingerprints). Absent ⇒ bootstrap ⇒ FULL. */
|
|
131
|
+
readonly prevFingerprints?: unknown;
|
|
132
|
+
/** The `--full` opt-out: force a FULL re-map regardless of the classifier. */
|
|
133
|
+
readonly forceFull?: boolean;
|
|
134
|
+
/** Forwarded to computeBatches (resolution, maxCommunitySize, configCwd, …). */
|
|
135
|
+
readonly computeBatchesOpts?: unknown;
|
|
136
|
+
/** buildNeighborMap cap (default 50). */
|
|
137
|
+
readonly neighborCap?: number;
|
|
138
|
+
/** Forwarded into classify's projectStats.thresholds. */
|
|
139
|
+
readonly thresholds?: unknown;
|
|
140
|
+
};
|
|
112
141
|
}
|
|
113
142
|
|
|
114
143
|
/**
|
|
@@ -136,4 +165,33 @@ export interface ExploreRunnerResult {
|
|
|
136
165
|
readonly output_tokens: number;
|
|
137
166
|
readonly usd_cost: number;
|
|
138
167
|
};
|
|
168
|
+
/**
|
|
169
|
+
* Phase 53 (DISC-01) — present ONLY when `ExploreRunnerOptions.incremental`
|
|
170
|
+
* was supplied. Carries the classifier verdict + the community batch plan so
|
|
171
|
+
* the explore stage can scope each mapper to its community and skip re-mapping
|
|
172
|
+
* unchanged batches. Absent (undefined) on the backward-compatible default
|
|
173
|
+
* path — existing consumers that never set `incremental` never see this key.
|
|
174
|
+
*/
|
|
175
|
+
readonly batching?: {
|
|
176
|
+
/** SKIP | PARTIAL_UPDATE | ARCHITECTURE_UPDATE | FULL_UPDATE (post `--full`). */
|
|
177
|
+
readonly action: string;
|
|
178
|
+
/** Batching method actually used. */
|
|
179
|
+
readonly method: 'louvain' | 'count-fallback';
|
|
180
|
+
/** Modularity of the Louvain partition (null on the count-fallback). */
|
|
181
|
+
readonly modularity: number | null;
|
|
182
|
+
/** Every community batch (opaque ids + members). */
|
|
183
|
+
readonly batches: ReadonlyArray<{
|
|
184
|
+
readonly id: string;
|
|
185
|
+
readonly members: readonly string[];
|
|
186
|
+
readonly mergeable: boolean;
|
|
187
|
+
readonly kind: string;
|
|
188
|
+
readonly source: string;
|
|
189
|
+
}>;
|
|
190
|
+
/** The batch ids elected for re-map this cycle (SKIP ⇒ empty). */
|
|
191
|
+
readonly batchesToMap: readonly string[];
|
|
192
|
+
/** Per-batch neighborMap sidecar, keyed by batch id (selected batches only). */
|
|
193
|
+
readonly neighborMaps: Readonly<Record<string, unknown>>;
|
|
194
|
+
/** The change classifier's full result (structuralCount, pct, hints, reason, …). */
|
|
195
|
+
readonly classification: Readonly<Record<string, unknown>>;
|
|
196
|
+
};
|
|
139
197
|
}
|
|
@@ -93,6 +93,14 @@
|
|
|
93
93
|
"user_invocable": true,
|
|
94
94
|
"tools": "Read, Write, Bash, Glob, Grep, AskUserQuestion, ToolSearch"
|
|
95
95
|
},
|
|
96
|
+
{
|
|
97
|
+
"name": "context",
|
|
98
|
+
"description": "Queries the typed DesignContext graph at .design/context-graph.json - the design-semantic map of tokens, components, variants, states, motion, a11y patterns, screens, layers, and design patterns plus the edges between them. Lists and filters nodes and edges, traces a path between two nodes, finds the consumers of a node, and reports unreachable nodes, dependency cycles, and coverage. Use when the user wants to inspect the design graph, find what depends on a token or component, trace how one node reaches another, or check graph health. Activates for requests involving the design context graph, design dependencies, token consumers, component composition, unreachable design nodes, design cycles, or design coverage.",
|
|
99
|
+
"argument_hint": "[nodes --type X | edges --type Z | path <a> <b> | consumers-of <id> | unreachable | cycles | coverage]",
|
|
100
|
+
"tools": "Read, Bash",
|
|
101
|
+
"user_invocable": true,
|
|
102
|
+
"registered_in_phase": "52"
|
|
103
|
+
},
|
|
96
104
|
{
|
|
97
105
|
"name": "continue",
|
|
98
106
|
"description": "Alias for {{command_prefix}}resume - restore session context from the most recent checkpoint.",
|
|
@@ -127,7 +135,7 @@
|
|
|
127
135
|
"name": "discover",
|
|
128
136
|
"frontmatter_name": "discover",
|
|
129
137
|
"description": "Stage 1.5 of 4 orchestrator that probes Figma / Refero / Pinterest connections, spawns design-context-builder (auto-detect + interview) and (via lazy gate) design-context-checker (6-dimension validator), producing .design/DESIGN-CONTEXT.md. Use after {{command_prefix}}scan when a fast-path context build is wanted instead of the full {{command_prefix}}explore. Activates for requests involving detecting an existing design system, inventorying tokens and components, or onboarding a brownfield repo.",
|
|
130
|
-
"argument_hint": "[--auto]",
|
|
138
|
+
"argument_hint": "[--auto] [--incremental] [--full]",
|
|
131
139
|
"user_invocable": true
|
|
132
140
|
},
|
|
133
141
|
{
|
|
@@ -145,7 +153,7 @@
|
|
|
145
153
|
{
|
|
146
154
|
"name": "explore",
|
|
147
155
|
"description": "Stage 2 of 5 - unified exploration merging inventory grep + design interview. Probes 6 connections, scans the codebase, conducts the AskUserQuestion interview, and writes .design/DESIGN.md + DESIGN-DEBT.md + DESIGN-CONTEXT.md. Use after {{command_prefix}}brief to map the existing system and lock decisions before planning. Activates for requests involving researching design direction, gathering references, or exploring visual options.",
|
|
148
|
-
"argument_hint": "[--skip-interview] [--skip-scan]",
|
|
156
|
+
"argument_hint": "[--skip-interview] [--skip-scan] [--incremental] [--full]",
|
|
149
157
|
"tools": "Read, Write, Bash, Grep, Glob, Task, AskUserQuestion, mcp__gdd_state__get, mcp__gdd_state__transition_stage, mcp__gdd_state__probe_connections, mcp__gdd_state__update_progress, mcp__gdd_state__set_status, mcp__gdd_state__add_blocker, mcp__gdd_state__checkpoint, mcp__gdd_state__add_decision",
|
|
150
158
|
"next_skills": [
|
|
151
159
|
"plan"
|
|
@@ -244,6 +252,14 @@
|
|
|
244
252
|
"user_invocable": true,
|
|
245
253
|
"tools": "Read, Write, Bash, Grep, Glob"
|
|
246
254
|
},
|
|
255
|
+
{
|
|
256
|
+
"name": "migrate-context",
|
|
257
|
+
"description": "Migrates a pre-Phase-52 project from the flat .design/map/*.md mapper notes to the typed DesignContext graph at .design/context-graph.json. Reads the old map notes, runs the deterministic extract-*.mjs passes to build mapper fragments, merges them with merge-fragments.mjs, validates the result with validate-design-context.cjs, and flags every low-confidence transform for human review before anything is trusted. Read-first and reversible; --dry-run previews the plan without writing. Use when upgrading a project to the DesignContext graph and .design/map/*.md still holds the only structured design notes. Activates for requests involving migrating design maps, building the context graph from old notes, or DesignContext graph migration.",
|
|
258
|
+
"argument_hint": "[--dry-run]",
|
|
259
|
+
"tools": "Read, Write, Bash",
|
|
260
|
+
"user_invocable": true,
|
|
261
|
+
"registered_in_phase": "52"
|
|
262
|
+
},
|
|
247
263
|
{
|
|
248
264
|
"name": "new-cycle",
|
|
249
265
|
"description": "Start a new design cycle. Creates cycle scope in STATE.md, initializes .design/CYCLES.md entry. Each cycle has its own goal and tracks its own decisions/tasks/pipeline runs.",
|