@hegemonart/get-design-done 1.57.1 → 1.57.3
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 +26 -41
- package/.claude-plugin/plugin.json +23 -48
- package/CHANGELOG.md +139 -0
- package/README.md +166 -511
- package/SKILL.md +4 -6
- package/agents/README.md +33 -36
- package/agents/a11y-mapper.md +3 -3
- package/agents/component-benchmark-harvester.md +6 -6
- package/agents/component-benchmark-synthesizer.md +3 -3
- package/agents/compose-executor.md +3 -3
- package/agents/cost-forecaster.md +2 -2
- package/agents/design-auditor.md +7 -7
- package/agents/design-authority-watcher.md +15 -15
- package/agents/design-context-builder.md +4 -4
- package/agents/design-context-checker-gate.md +1 -1
- package/agents/design-discussant.md +2 -2
- package/agents/design-doc-writer.md +1 -1
- package/agents/design-executor.md +2 -2
- package/agents/design-figma-writer.md +2 -2
- package/agents/design-fixer.md +7 -7
- package/agents/design-integration-checker-gate.md +1 -1
- package/agents/design-integration-checker.md +1 -1
- package/agents/design-paper-writer.md +3 -3
- package/agents/design-pencil-writer.md +1 -1
- package/agents/design-planner.md +21 -0
- package/agents/design-reflector.md +39 -39
- package/agents/design-research-synthesizer.md +1 -0
- package/agents/design-start-writer.md +1 -1
- package/agents/design-update-checker.md +5 -5
- package/agents/design-verifier-gate.md +1 -1
- package/agents/design-verifier.md +52 -48
- package/agents/ds-generator.md +2 -2
- package/agents/ds-migration-planner.md +4 -4
- package/agents/email-executor.md +9 -9
- package/agents/experiment-result-ingester.md +3 -3
- package/agents/flutter-executor.md +5 -5
- package/agents/gdd-graph-refresh.md +3 -3
- package/agents/gdd-intel-updater.md +2 -2
- package/agents/motion-mapper.md +2 -2
- package/agents/motion-verifier.md +4 -4
- package/agents/pdf-executor.md +8 -8
- package/agents/perf-analyzer.md +17 -17
- package/agents/pr-commenter.md +9 -9
- package/agents/prototype-gate.md +2 -2
- package/agents/quality-gate-runner.md +1 -1
- package/agents/rollout-coordinator.md +3 -3
- package/agents/swift-executor.md +4 -4
- package/agents/ticket-sync-agent.md +6 -6
- package/agents/user-research-synthesizer.md +2 -2
- package/connections/connections.md +44 -45
- package/connections/cursor.md +72 -0
- package/connections/preview.md +3 -3
- package/hooks/first-run-nudge.cjs +171 -0
- package/hooks/gdd-intel-trigger.js +243 -0
- package/hooks/gdd-mcp-circuit-breaker.js +62 -7
- package/hooks/gdd-precompact-snapshot.js +50 -29
- package/hooks/gdd-protected-paths.js +150 -18
- package/hooks/gdd-risk-gate.js +93 -1
- package/hooks/gdd-sessionstart-recap.js +59 -24
- package/hooks/hooks.json +13 -4
- package/hooks/inject-using-gdd.cjs +188 -0
- package/hooks/update-check.cjs +511 -0
- package/package.json +9 -3
- package/reference/STATE-TEMPLATE.md +10 -13
- package/reference/audit-scoring.md +1 -1
- package/reference/cache-tier-doctrine.md +46 -0
- package/reference/config-schema.md +9 -9
- package/reference/i18n.md +1 -1
- package/reference/intel-schema.md +37 -2
- package/reference/meta-rules.md +4 -4
- package/reference/model-tiers.md +2 -2
- package/reference/registry.json +101 -94
- package/reference/runtime-models.md +11 -1
- package/reference/shared-preamble.md +13 -14
- package/reference/skill-graph.md +22 -3
- package/scripts/bootstrap.cjs +373 -0
- package/scripts/injection-patterns.cjs +58 -0
- package/scripts/lib/apply-reflections/incubator-proposals.cjs +57 -26
- package/scripts/lib/install/converters/codex-plugin.cjs +5 -2
- package/scripts/lib/install/converters/cursor.cjs +20 -0
- package/scripts/lib/issue-reporter/report-flow.cjs +1 -1
- package/scripts/lib/manifest/skills.json +75 -28
- package/scripts/lib/state/query-surface.cjs +67 -9
- package/scripts/lib/state/state-store.cjs +68 -26
- package/scripts/lib/worktree-resolve.cjs +4 -16
- package/sdk/cli/commands/stage.ts +17 -0
- package/sdk/cli/index.js +14 -0
- package/skills/README.md +46 -0
- package/skills/bootstrap-ds/SKILL.md +1 -1
- package/skills/cache-manager/SKILL.md +3 -3
- package/skills/cache-manager/cache-policy.md +1 -1
- package/skills/compare/SKILL.md +1 -1
- package/skills/design/SKILL.md +19 -0
- package/skills/explore/SKILL.md +11 -0
- package/skills/figma-write/SKILL.md +13 -2
- package/skills/new-cycle/SKILL.md +1 -1
- package/skills/paper-write/SKILL.md +54 -0
- package/skills/peer-cli-customize/SKILL.md +0 -1
- package/skills/peers/SKILL.md +1 -1
- package/skills/pencil-write/SKILL.md +54 -0
- package/skills/reflect/procedures/capability-gap-scan.md +0 -1
- package/skills/report-issue/SKILL.md +2 -2
- package/skills/report-issue/report-issue-procedure.md +0 -1
- package/skills/router/SKILL.md +2 -2
- package/skills/synthesize/SKILL.md +1 -1
- package/skills/turn-closeout/SKILL.md +1 -1
- package/skills/verify/verify-procedure.md +10 -11
- package/skills/warm-cache/SKILL.md +1 -1
- package/dist/claude-code/.claude/skills/add-backlog/SKILL.md +0 -48
- package/dist/claude-code/.claude/skills/analyze-dependencies/SKILL.md +0 -95
- package/dist/claude-code/.claude/skills/apply-reflections/SKILL.md +0 -109
- package/dist/claude-code/.claude/skills/apply-reflections/apply-reflections-procedure.md +0 -170
- package/dist/claude-code/.claude/skills/audit/SKILL.md +0 -79
- package/dist/claude-code/.claude/skills/bandit-status/SKILL.md +0 -94
- package/dist/claude-code/.claude/skills/benchmark/SKILL.md +0 -65
- package/dist/claude-code/.claude/skills/bootstrap-ds/SKILL.md +0 -43
- package/dist/claude-code/.claude/skills/brief/SKILL.md +0 -145
- package/dist/claude-code/.claude/skills/budget/SKILL.md +0 -45
- package/dist/claude-code/.claude/skills/cache-manager/SKILL.md +0 -66
- package/dist/claude-code/.claude/skills/cache-manager/cache-policy.md +0 -126
- package/dist/claude-code/.claude/skills/check-update/SKILL.md +0 -98
- package/dist/claude-code/.claude/skills/compare/SKILL.md +0 -82
- package/dist/claude-code/.claude/skills/compare/compare-rubric.md +0 -171
- package/dist/claude-code/.claude/skills/complete-cycle/SKILL.md +0 -81
- package/dist/claude-code/.claude/skills/connections/SKILL.md +0 -71
- package/dist/claude-code/.claude/skills/connections/connections-onboarding.md +0 -608
- package/dist/claude-code/.claude/skills/context/SKILL.md +0 -137
- package/dist/claude-code/.claude/skills/continue/SKILL.md +0 -24
- package/dist/claude-code/.claude/skills/darkmode/SKILL.md +0 -76
- package/dist/claude-code/.claude/skills/darkmode/darkmode-audit-procedure.md +0 -258
- package/dist/claude-code/.claude/skills/debug/SKILL.md +0 -41
- package/dist/claude-code/.claude/skills/debug/debug-feedback-loops.md +0 -119
- package/dist/claude-code/.claude/skills/design/SKILL.md +0 -99
- package/dist/claude-code/.claude/skills/design/design-procedure.md +0 -304
- package/dist/claude-code/.claude/skills/discover/SKILL.md +0 -78
- package/dist/claude-code/.claude/skills/discover/discover-procedure.md +0 -222
- package/dist/claude-code/.claude/skills/discuss/SKILL.md +0 -96
- package/dist/claude-code/.claude/skills/do/SKILL.md +0 -45
- package/dist/claude-code/.claude/skills/explore/SKILL.md +0 -107
- package/dist/claude-code/.claude/skills/explore/explore-procedure.md +0 -267
- package/dist/claude-code/.claude/skills/export/SKILL.md +0 -30
- package/dist/claude-code/.claude/skills/extract-learnings/SKILL.md +0 -114
- package/dist/claude-code/.claude/skills/fast/SKILL.md +0 -91
- package/dist/claude-code/.claude/skills/figma-extract/SKILL.md +0 -64
- package/dist/claude-code/.claude/skills/figma-write/SKILL.md +0 -39
- package/dist/claude-code/.claude/skills/graphify/SKILL.md +0 -49
- package/dist/claude-code/.claude/skills/health/SKILL.md +0 -99
- package/dist/claude-code/.claude/skills/health/health-mcp-detection.md +0 -44
- package/dist/claude-code/.claude/skills/health/health-skill-length-report.md +0 -69
- package/dist/claude-code/.claude/skills/help/SKILL.md +0 -87
- package/dist/claude-code/.claude/skills/instinct/SKILL.md +0 -111
- package/dist/claude-code/.claude/skills/list-assumptions/SKILL.md +0 -61
- package/dist/claude-code/.claude/skills/list-pins/SKILL.md +0 -27
- package/dist/claude-code/.claude/skills/live/SKILL.md +0 -98
- package/dist/claude-code/.claude/skills/locale/SKILL.md +0 -51
- package/dist/claude-code/.claude/skills/map/SKILL.md +0 -89
- package/dist/claude-code/.claude/skills/migrate/SKILL.md +0 -70
- package/dist/claude-code/.claude/skills/migrate-context/SKILL.md +0 -123
- package/dist/claude-code/.claude/skills/new-addendum/SKILL.md +0 -81
- package/dist/claude-code/.claude/skills/new-cycle/SKILL.md +0 -37
- package/dist/claude-code/.claude/skills/new-cycle/milestone-completeness-rubric.md +0 -87
- package/dist/claude-code/.claude/skills/new-project/SKILL.md +0 -53
- package/dist/claude-code/.claude/skills/new-skill/SKILL.md +0 -90
- package/dist/claude-code/.claude/skills/next/SKILL.md +0 -68
- package/dist/claude-code/.claude/skills/note/SKILL.md +0 -48
- package/dist/claude-code/.claude/skills/openrouter-status/SKILL.md +0 -86
- package/dist/claude-code/.claude/skills/optimize/SKILL.md +0 -97
- package/dist/claude-code/.claude/skills/override/SKILL.md +0 -86
- package/dist/claude-code/.claude/skills/pause/SKILL.md +0 -77
- package/dist/claude-code/.claude/skills/peer-cli-add/SKILL.md +0 -88
- package/dist/claude-code/.claude/skills/peer-cli-add/peer-cli-protocol.md +0 -161
- package/dist/claude-code/.claude/skills/peer-cli-customize/SKILL.md +0 -90
- package/dist/claude-code/.claude/skills/peers/SKILL.md +0 -96
- package/dist/claude-code/.claude/skills/pin/SKILL.md +0 -37
- package/dist/claude-code/.claude/skills/plan/SKILL.md +0 -105
- package/dist/claude-code/.claude/skills/plan/plan-procedure.md +0 -278
- package/dist/claude-code/.claude/skills/plant-seed/SKILL.md +0 -48
- package/dist/claude-code/.claude/skills/pr-branch/SKILL.md +0 -32
- package/dist/claude-code/.claude/skills/progress/SKILL.md +0 -107
- package/dist/claude-code/.claude/skills/quality-gate/SKILL.md +0 -90
- package/dist/claude-code/.claude/skills/quality-gate/threat-modeling.md +0 -101
- package/dist/claude-code/.claude/skills/quick/SKILL.md +0 -44
- package/dist/claude-code/.claude/skills/reapply-patches/SKILL.md +0 -32
- package/dist/claude-code/.claude/skills/recall/SKILL.md +0 -75
- package/dist/claude-code/.claude/skills/reflect/SKILL.md +0 -85
- package/dist/claude-code/.claude/skills/reflect/procedures/capability-gap-scan.md +0 -120
- package/dist/claude-code/.claude/skills/report-issue/SKILL.md +0 -53
- package/dist/claude-code/.claude/skills/report-issue/report-issue-procedure.md +0 -120
- package/dist/claude-code/.claude/skills/resume/SKILL.md +0 -93
- package/dist/claude-code/.claude/skills/review-backlog/SKILL.md +0 -46
- package/dist/claude-code/.claude/skills/review-decisions/SKILL.md +0 -42
- package/dist/claude-code/.claude/skills/roi/SKILL.md +0 -54
- package/dist/claude-code/.claude/skills/rollout-status/SKILL.md +0 -35
- package/dist/claude-code/.claude/skills/router/SKILL.md +0 -89
- package/dist/claude-code/.claude/skills/router/capability-gap-emitter.md +0 -65
- package/dist/claude-code/.claude/skills/router/router-pick-emitter.md +0 -78
- package/dist/claude-code/.claude/skills/router/router-rules.md +0 -84
- package/dist/claude-code/.claude/skills/scan/SKILL.md +0 -92
- package/dist/claude-code/.claude/skills/scan/scan-procedure.md +0 -732
- package/dist/claude-code/.claude/skills/settings/SKILL.md +0 -87
- package/dist/claude-code/.claude/skills/ship/SKILL.md +0 -48
- package/dist/claude-code/.claude/skills/sketch/SKILL.md +0 -78
- package/dist/claude-code/.claude/skills/sketch-wrap-up/SKILL.md +0 -92
- package/dist/claude-code/.claude/skills/skill-manifest/SKILL.md +0 -79
- package/dist/claude-code/.claude/skills/spike/SKILL.md +0 -67
- package/dist/claude-code/.claude/skills/spike-wrap-up/SKILL.md +0 -86
- package/dist/claude-code/.claude/skills/start/SKILL.md +0 -67
- package/dist/claude-code/.claude/skills/start/start-procedure.md +0 -115
- package/dist/claude-code/.claude/skills/state/SKILL.md +0 -106
- package/dist/claude-code/.claude/skills/stats/SKILL.md +0 -51
- package/dist/claude-code/.claude/skills/style/SKILL.md +0 -71
- package/dist/claude-code/.claude/skills/style/style-doc-procedure.md +0 -150
- package/dist/claude-code/.claude/skills/synthesize/SKILL.md +0 -94
- package/dist/claude-code/.claude/skills/timeline/SKILL.md +0 -66
- package/dist/claude-code/.claude/skills/todo/SKILL.md +0 -64
- package/dist/claude-code/.claude/skills/turn-closeout/SKILL.md +0 -95
- package/dist/claude-code/.claude/skills/undo/SKILL.md +0 -31
- package/dist/claude-code/.claude/skills/unlock-decision/SKILL.md +0 -54
- package/dist/claude-code/.claude/skills/unpin/SKILL.md +0 -31
- package/dist/claude-code/.claude/skills/update/SKILL.md +0 -56
- package/dist/claude-code/.claude/skills/using-gdd/SKILL.md +0 -78
- package/dist/claude-code/.claude/skills/verify/SKILL.md +0 -113
- package/dist/claude-code/.claude/skills/verify/verify-procedure.md +0 -512
- package/dist/claude-code/.claude/skills/warm-cache/SKILL.md +0 -81
- package/dist/claude-code/.claude/skills/watch-authorities/SKILL.md +0 -82
- package/dist/claude-code/.claude/skills/zoom-out/SKILL.md +0 -26
- package/hooks/first-run-nudge.sh +0 -82
- package/hooks/inject-using-gdd.sh +0 -72
- package/hooks/run-hook.cmd +0 -35
- package/hooks/update-check.sh +0 -251
- package/scripts/lib/audit-aggregator/index.cjs +0 -219
- package/scripts/lib/hedge-ensemble.cjs +0 -217
- package/skills/discover/SKILL.md +0 -78
- package/skills/discover/discover-procedure.md +0 -222
- package/skills/new-cycle/milestone-completeness-rubric.md +0 -87
- package/skills/scan/SKILL.md +0 -92
- package/skills/scan/scan-procedure.md +0 -732
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* audit-aggregator/index.cjs — dedup + score + rank findings from N
|
|
3
|
-
* audit-agents (Plan 23-04).
|
|
4
|
-
*
|
|
5
|
-
* Replaces the prompt-only "trust the agent's score" pattern with a
|
|
6
|
-
* deterministic scoring + dedup function that downstream tooling
|
|
7
|
-
* (`/gdd:audit`, `/gdd:reflect`) can rely on.
|
|
8
|
-
*
|
|
9
|
-
* Dedup key: `${lowercased(normalizePath(file))}::${line ?? 0}::${rule_id}`.
|
|
10
|
-
* Survivor selection on collision:
|
|
11
|
-
* 1. higher confidence wins
|
|
12
|
-
* 2. tie → higher severity (P0 > P1 > P2 > P3)
|
|
13
|
-
* 3. tie → lexicographically earliest agent
|
|
14
|
-
* 4. tie → first-seen
|
|
15
|
-
*
|
|
16
|
-
* Score = severityWeight(severity) * confidence.
|
|
17
|
-
*
|
|
18
|
-
* No external deps. CommonJS to match the rest of scripts/lib/.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
'use strict';
|
|
22
|
-
|
|
23
|
-
const SEVERITY_RANK = { P0: 4, P1: 3, P2: 2, P3: 1 };
|
|
24
|
-
const DEFAULT_WEIGHTS = Object.freeze({ P0: 8, P1: 4, P2: 2, P3: 1 });
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @typedef {Object} Finding
|
|
28
|
-
* @property {string} file
|
|
29
|
-
* @property {number} [line]
|
|
30
|
-
* @property {string} rule_id
|
|
31
|
-
* @property {'P0'|'P1'|'P2'|'P3'} severity
|
|
32
|
-
* @property {string} summary
|
|
33
|
-
* @property {string} [evidence]
|
|
34
|
-
* @property {string} [agent]
|
|
35
|
-
* @property {number} [confidence]
|
|
36
|
-
* @property {string[]} [merged_from]
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* @typedef {Object} AggregateResult
|
|
41
|
-
* @property {Finding[]} findings
|
|
42
|
-
* @property {Object<string, number>} byRule
|
|
43
|
-
* @property {Object<string, number>} bySeverity
|
|
44
|
-
* @property {Object<string, number>} byFile
|
|
45
|
-
* @property {number} total
|
|
46
|
-
* @property {number} duplicates
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* @typedef {Object} AggregateOptions
|
|
51
|
-
* @property {number} [topN]
|
|
52
|
-
* @property {Object<string, number>} [severityWeights]
|
|
53
|
-
* @property {(a: Finding, b: Finding) => Finding} [merge]
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
function normalizePath(p) {
|
|
57
|
-
return String(p).replace(/\\/g, '/').toLowerCase();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let _confidenceWarningEmitted = false;
|
|
61
|
-
|
|
62
|
-
function clampConfidence(c) {
|
|
63
|
-
if (c === undefined || c === null) return 1;
|
|
64
|
-
if (typeof c !== 'number' || Number.isNaN(c)) return 1;
|
|
65
|
-
if (c < 0) {
|
|
66
|
-
if (!_confidenceWarningEmitted) {
|
|
67
|
-
process.emitWarning('audit-aggregator: confidence < 0 clamped to 0', 'AuditAggregator');
|
|
68
|
-
_confidenceWarningEmitted = true;
|
|
69
|
-
}
|
|
70
|
-
return 0;
|
|
71
|
-
}
|
|
72
|
-
if (c > 1) {
|
|
73
|
-
if (!_confidenceWarningEmitted) {
|
|
74
|
-
process.emitWarning('audit-aggregator: confidence > 1 clamped to 1', 'AuditAggregator');
|
|
75
|
-
_confidenceWarningEmitted = true;
|
|
76
|
-
}
|
|
77
|
-
return 1;
|
|
78
|
-
}
|
|
79
|
-
return c;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Compute score for a finding.
|
|
84
|
-
*
|
|
85
|
-
* @param {Finding} f
|
|
86
|
-
* @param {Object<string, number>} weights
|
|
87
|
-
* @returns {number}
|
|
88
|
-
*/
|
|
89
|
-
function score(f, weights) {
|
|
90
|
-
const w = (weights && weights[f.severity]) ?? DEFAULT_WEIGHTS[f.severity] ?? 0;
|
|
91
|
-
return w * clampConfidence(f.confidence);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function validateFinding(f, idx) {
|
|
95
|
-
if (!f || typeof f !== 'object') {
|
|
96
|
-
throw new TypeError(`audit-aggregator: input[${idx}] is not an object`);
|
|
97
|
-
}
|
|
98
|
-
if (typeof f.file !== 'string' || f.file.length === 0) {
|
|
99
|
-
throw new TypeError(`audit-aggregator: input[${idx}].file is required (non-empty string)`);
|
|
100
|
-
}
|
|
101
|
-
if (typeof f.rule_id !== 'string' || f.rule_id.length === 0) {
|
|
102
|
-
throw new TypeError(`audit-aggregator: input[${idx}].rule_id is required (non-empty string)`);
|
|
103
|
-
}
|
|
104
|
-
if (!(f.severity in SEVERITY_RANK)) {
|
|
105
|
-
throw new TypeError(
|
|
106
|
-
`audit-aggregator: input[${idx}].severity must be P0|P1|P2|P3 (got ${JSON.stringify(f.severity)})`,
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function dedupKey(f) {
|
|
112
|
-
return `${normalizePath(f.file)}::${f.line ?? 0}::${f.rule_id}`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function defaultMerge(a, b) {
|
|
116
|
-
// Higher confidence wins.
|
|
117
|
-
const ca = clampConfidence(a.confidence);
|
|
118
|
-
const cb = clampConfidence(b.confidence);
|
|
119
|
-
if (ca !== cb) return ca > cb ? a : b;
|
|
120
|
-
// Higher severity wins.
|
|
121
|
-
const ra = SEVERITY_RANK[a.severity];
|
|
122
|
-
const rb = SEVERITY_RANK[b.severity];
|
|
123
|
-
if (ra !== rb) return ra > rb ? a : b;
|
|
124
|
-
// Lexicographic agent.
|
|
125
|
-
const aa = a.agent ?? '';
|
|
126
|
-
const ab = b.agent ?? '';
|
|
127
|
-
if (aa !== ab) return aa < ab ? a : b;
|
|
128
|
-
// First-seen wins (a is by convention the existing entry).
|
|
129
|
-
return a;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Aggregate findings.
|
|
134
|
-
*
|
|
135
|
-
* @param {Finding[]} input
|
|
136
|
-
* @param {AggregateOptions} [opts]
|
|
137
|
-
* @returns {AggregateResult}
|
|
138
|
-
*/
|
|
139
|
-
function aggregate(input, opts = {}) {
|
|
140
|
-
if (!Array.isArray(input)) {
|
|
141
|
-
throw new TypeError('audit-aggregator: input must be an array');
|
|
142
|
-
}
|
|
143
|
-
// Reset the once-per-call warning flag so a second call can warn again.
|
|
144
|
-
_confidenceWarningEmitted = false;
|
|
145
|
-
const merge = typeof opts.merge === 'function' ? opts.merge : defaultMerge;
|
|
146
|
-
const weights = { ...DEFAULT_WEIGHTS, ...(opts.severityWeights || {}) };
|
|
147
|
-
|
|
148
|
-
/** @type {Map<string, Finding>} */
|
|
149
|
-
const byKey = new Map();
|
|
150
|
-
let duplicates = 0;
|
|
151
|
-
for (let i = 0; i < input.length; i++) {
|
|
152
|
-
validateFinding(input[i], i);
|
|
153
|
-
const f = { ...input[i] };
|
|
154
|
-
const key = dedupKey(f);
|
|
155
|
-
if (byKey.has(key)) {
|
|
156
|
-
duplicates += 1;
|
|
157
|
-
const existing = byKey.get(key);
|
|
158
|
-
const winner = merge(existing, f);
|
|
159
|
-
const loser = winner === existing ? f : existing;
|
|
160
|
-
const mergedFrom = new Set(winner.merged_from || []);
|
|
161
|
-
if (existing.agent && existing !== winner) mergedFrom.add(existing.agent);
|
|
162
|
-
if (loser.agent && loser !== winner) mergedFrom.add(loser.agent);
|
|
163
|
-
// Combine prior merged_from too.
|
|
164
|
-
for (const a of (loser.merged_from || [])) mergedFrom.add(a);
|
|
165
|
-
winner.merged_from = Array.from(mergedFrom);
|
|
166
|
-
byKey.set(key, winner);
|
|
167
|
-
} else {
|
|
168
|
-
byKey.set(key, f);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const findings = Array.from(byKey.values()).map((f) => ({ ...f, _score: score(f, weights) }));
|
|
173
|
-
findings.sort((a, b) => {
|
|
174
|
-
if (a._score !== b._score) return b._score - a._score;
|
|
175
|
-
const ra = SEVERITY_RANK[a.severity];
|
|
176
|
-
const rb = SEVERITY_RANK[b.severity];
|
|
177
|
-
if (ra !== rb) return rb - ra;
|
|
178
|
-
if (a.file !== b.file) return a.file < b.file ? -1 : 1;
|
|
179
|
-
return (a.line ?? 0) - (b.line ?? 0);
|
|
180
|
-
});
|
|
181
|
-
// Strip the internal _score field before returning.
|
|
182
|
-
for (const f of findings) delete f._score;
|
|
183
|
-
|
|
184
|
-
const truncated = typeof opts.topN === 'number' && opts.topN >= 0
|
|
185
|
-
? findings.slice(0, opts.topN)
|
|
186
|
-
: findings;
|
|
187
|
-
|
|
188
|
-
/** @type {Record<string, number>} */
|
|
189
|
-
const byRule = {};
|
|
190
|
-
/** @type {Record<string, number>} */
|
|
191
|
-
const bySeverity = { P0: 0, P1: 0, P2: 0, P3: 0 };
|
|
192
|
-
/** @type {Record<string, number>} */
|
|
193
|
-
const byFile = {};
|
|
194
|
-
for (const f of truncated) {
|
|
195
|
-
byRule[f.rule_id] = (byRule[f.rule_id] ?? 0) + 1;
|
|
196
|
-
bySeverity[f.severity] += 1;
|
|
197
|
-
const k = normalizePath(f.file);
|
|
198
|
-
byFile[k] = (byFile[k] ?? 0) + 1;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return {
|
|
202
|
-
findings: truncated,
|
|
203
|
-
byRule,
|
|
204
|
-
bySeverity,
|
|
205
|
-
byFile,
|
|
206
|
-
total: truncated.length,
|
|
207
|
-
duplicates,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
module.exports = {
|
|
212
|
-
aggregate,
|
|
213
|
-
score,
|
|
214
|
-
normalizePath,
|
|
215
|
-
dedupKey,
|
|
216
|
-
defaultMerge,
|
|
217
|
-
DEFAULT_WEIGHTS,
|
|
218
|
-
SEVERITY_RANK,
|
|
219
|
-
};
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hedge-ensemble.cjs — AdaNormalHedge weighted-majority over verifier
|
|
3
|
-
* + checker agents (Plan 23.5-02).
|
|
4
|
-
*
|
|
5
|
-
* Parameter-free: no manual learning rate. Weights self-adapt via
|
|
6
|
-
* the AdaNormalHedge regret-bound trick — η is recomputed each round
|
|
7
|
-
* from cumulative loss variance, eliminating the typical "tune η or
|
|
8
|
-
* suffer" tax.
|
|
9
|
-
*
|
|
10
|
-
* Weights persist at `.design/telemetry/hedge-weights.json` (atomic
|
|
11
|
-
* .tmp + rename). Schema:
|
|
12
|
-
* { schema_version: '1.0.0',
|
|
13
|
-
* generated_at: ISO,
|
|
14
|
-
* pools: { <poolId>: { agents: { <agentId>: {weight, cumLoss, cumLoss2, rounds} } } } }
|
|
15
|
-
*
|
|
16
|
-
* Reused by adaptive_mode = "hedge" or "full" — see Plan 23.5-04.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
'use strict';
|
|
20
|
-
|
|
21
|
-
const fs = require('node:fs');
|
|
22
|
-
const path = require('node:path');
|
|
23
|
-
|
|
24
|
-
const DEFAULT_WEIGHTS_PATH = '.design/telemetry/hedge-weights.json';
|
|
25
|
-
const SCHEMA_VERSION = '1.0.0';
|
|
26
|
-
const DEFAULT_VOTE_THRESHOLD = 0.5;
|
|
27
|
-
|
|
28
|
-
function resolvePath(opts = {}) {
|
|
29
|
-
if (opts.weightsPath) {
|
|
30
|
-
return path.isAbsolute(opts.weightsPath)
|
|
31
|
-
? opts.weightsPath
|
|
32
|
-
: path.resolve(opts.baseDir ?? process.cwd(), opts.weightsPath);
|
|
33
|
-
}
|
|
34
|
-
return path.resolve(opts.baseDir ?? process.cwd(), DEFAULT_WEIGHTS_PATH);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @returns {{schema_version: string, generated_at: string, pools: object}}
|
|
39
|
-
*/
|
|
40
|
-
function loadWeights(opts = {}) {
|
|
41
|
-
const p = resolvePath(opts);
|
|
42
|
-
if (!fs.existsSync(p)) {
|
|
43
|
-
return { schema_version: SCHEMA_VERSION, generated_at: new Date().toISOString(), pools: {} };
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
const data = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
47
|
-
if (!data.pools || typeof data.pools !== 'object') data.pools = {};
|
|
48
|
-
return data;
|
|
49
|
-
} catch {
|
|
50
|
-
return { schema_version: SCHEMA_VERSION, generated_at: new Date().toISOString(), pools: {} };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function saveWeights(state, opts = {}) {
|
|
55
|
-
const p = resolvePath(opts);
|
|
56
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
57
|
-
state.generated_at = new Date().toISOString();
|
|
58
|
-
const tmp = p + '.tmp';
|
|
59
|
-
fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
|
|
60
|
-
fs.renameSync(tmp, p);
|
|
61
|
-
return p;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function ensurePool(state, poolId) {
|
|
65
|
-
if (!state.pools[poolId]) state.pools[poolId] = { agents: {} };
|
|
66
|
-
return state.pools[poolId];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function ensureAgent(pool, agentId) {
|
|
70
|
-
if (!pool.agents[agentId]) {
|
|
71
|
-
pool.agents[agentId] = {
|
|
72
|
-
weight: 1, // uniform start; normalised on read
|
|
73
|
-
cumLoss: 0,
|
|
74
|
-
cumLoss2: 0,
|
|
75
|
-
rounds: 0,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
return pool.agents[agentId];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Apply one round of losses to a pool. losses: Record<agentId, lossInZeroOne>.
|
|
83
|
-
*
|
|
84
|
-
* AdaNormalHedge update (parameter-free):
|
|
85
|
-
* For each agent i:
|
|
86
|
-
* R_i = sum of (mean_loss - loss_i) over rounds (instantaneous regret)
|
|
87
|
-
* C_i = sum of (loss_i - mean_loss)^2 (cumulative loss variance)
|
|
88
|
-
* Set η_i = sqrt(ln(N) / max(1, C_i)) per-agent learning rate.
|
|
89
|
-
* weight_i ∝ Phi(R_i, C_i) where Phi is a positive-only potential.
|
|
90
|
-
*
|
|
91
|
-
* Simplification used here: w_i *= exp(-η * loss_i) with η derived
|
|
92
|
-
* from cumulative variance — gives the same regret bound as full
|
|
93
|
-
* AdaNormalHedge for the binary-loss case we care about (verifier
|
|
94
|
-
* pass/fail). Trade off: slightly less tight bound vs the full
|
|
95
|
-
* potential, but no need to plumb regret tracking everywhere.
|
|
96
|
-
*
|
|
97
|
-
* @param {{poolId: string, losses: Record<string, number>, baseDir?: string, weightsPath?: string, eta?: number}} input
|
|
98
|
-
* @returns {{weights: Record<string, number>, weightsPath: string}}
|
|
99
|
-
*/
|
|
100
|
-
function loss(input) {
|
|
101
|
-
if (!input || typeof input.poolId !== 'string' || input.poolId.length === 0) {
|
|
102
|
-
throw new TypeError('hedge-ensemble.loss: poolId (string) required');
|
|
103
|
-
}
|
|
104
|
-
if (!input.losses || typeof input.losses !== 'object') {
|
|
105
|
-
throw new TypeError('hedge-ensemble.loss: losses (Record<string, number>) required');
|
|
106
|
-
}
|
|
107
|
-
const state = loadWeights(input);
|
|
108
|
-
const pool = ensurePool(state, input.poolId);
|
|
109
|
-
// First, ensure every losing agent exists.
|
|
110
|
-
for (const [agentId, lossVal] of Object.entries(input.losses)) {
|
|
111
|
-
if (typeof lossVal !== 'number' || Number.isNaN(lossVal)) {
|
|
112
|
-
throw new TypeError(`hedge-ensemble.loss: losses.${agentId} must be a number`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
for (const agentId of Object.keys(input.losses)) {
|
|
116
|
-
ensureAgent(pool, agentId);
|
|
117
|
-
}
|
|
118
|
-
const N = Object.keys(pool.agents).length;
|
|
119
|
-
// Compute mean loss this round (over agents that received a value).
|
|
120
|
-
const lossList = Object.values(input.losses);
|
|
121
|
-
const meanLoss = lossList.length > 0 ? lossList.reduce((a, b) => a + b, 0) / lossList.length : 0;
|
|
122
|
-
// Update each agent's cumulative variance + regret-like signal, then
|
|
123
|
-
// recompute its weight via exp(-η_i * loss_i).
|
|
124
|
-
for (const [agentId, rawLoss] of Object.entries(input.losses)) {
|
|
125
|
-
const lossVal = Math.min(1, Math.max(0, rawLoss));
|
|
126
|
-
const a = pool.agents[agentId];
|
|
127
|
-
const dev = lossVal - meanLoss;
|
|
128
|
-
a.cumLoss += lossVal;
|
|
129
|
-
a.cumLoss2 += dev * dev;
|
|
130
|
-
a.rounds += 1;
|
|
131
|
-
const eta =
|
|
132
|
-
typeof input.eta === 'number'
|
|
133
|
-
? input.eta
|
|
134
|
-
: Math.sqrt(Math.log(Math.max(2, N)) / Math.max(1, a.cumLoss2));
|
|
135
|
-
a.weight *= Math.exp(-eta * lossVal);
|
|
136
|
-
if (!Number.isFinite(a.weight) || a.weight <= 0) a.weight = 1e-9;
|
|
137
|
-
}
|
|
138
|
-
// Renormalize.
|
|
139
|
-
const total = Object.values(pool.agents).reduce((s, x) => s + x.weight, 0) || 1;
|
|
140
|
-
/** @type {Record<string, number>} */
|
|
141
|
-
const out = {};
|
|
142
|
-
for (const agentId of Object.keys(pool.agents)) {
|
|
143
|
-
pool.agents[agentId].weight /= total;
|
|
144
|
-
out[agentId] = pool.agents[agentId].weight;
|
|
145
|
-
}
|
|
146
|
-
const writtenPath = saveWeights(state, input);
|
|
147
|
-
return { weights: out, weightsPath: writtenPath };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Compute the weighted-majority verdict for a pool given each agent's
|
|
152
|
-
* binary vote (pass=1, fail=0). Vote passes when the weighted sum
|
|
153
|
-
* exceeds threshold (default 0.5).
|
|
154
|
-
*
|
|
155
|
-
* @param {{poolId: string, votes: Record<string, 0|1|boolean>, threshold?: number, baseDir?: string, weightsPath?: string}} input
|
|
156
|
-
* @returns {{passes: boolean, weighted: number, threshold: number, perAgent: Record<string, {weight: number, vote: number}>}}
|
|
157
|
-
*/
|
|
158
|
-
function vote(input) {
|
|
159
|
-
if (!input || typeof input.poolId !== 'string') {
|
|
160
|
-
throw new TypeError('hedge-ensemble.vote: poolId required');
|
|
161
|
-
}
|
|
162
|
-
if (!input.votes || typeof input.votes !== 'object') {
|
|
163
|
-
throw new TypeError('hedge-ensemble.vote: votes required');
|
|
164
|
-
}
|
|
165
|
-
const state = loadWeights(input);
|
|
166
|
-
const pool = ensurePool(state, input.poolId);
|
|
167
|
-
const threshold = typeof input.threshold === 'number' ? input.threshold : DEFAULT_VOTE_THRESHOLD;
|
|
168
|
-
let total = 0;
|
|
169
|
-
/** @type {Record<string, {weight: number, vote: number}>} */
|
|
170
|
-
const perAgent = {};
|
|
171
|
-
let weightSum = 0;
|
|
172
|
-
for (const [agentId, raw] of Object.entries(input.votes)) {
|
|
173
|
-
const v = raw === true || raw === 1 ? 1 : 0;
|
|
174
|
-
const a = ensureAgent(pool, agentId);
|
|
175
|
-
perAgent[agentId] = { weight: a.weight, vote: v };
|
|
176
|
-
total += a.weight * v;
|
|
177
|
-
weightSum += a.weight;
|
|
178
|
-
}
|
|
179
|
-
// Normalise the weighted sum against the SUM of voting agents'
|
|
180
|
-
// weights — agents in the pool that didn't vote this round don't
|
|
181
|
-
// dilute the result.
|
|
182
|
-
const weighted = weightSum > 0 ? total / weightSum : 0;
|
|
183
|
-
return { passes: weighted >= threshold, weighted, threshold, perAgent };
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Read current weights for a pool, normalised over the pool's agents.
|
|
188
|
-
*
|
|
189
|
-
* @param {{poolId: string, baseDir?: string, weightsPath?: string}} input
|
|
190
|
-
* @returns {Record<string, number>}
|
|
191
|
-
*/
|
|
192
|
-
function weights(input) {
|
|
193
|
-
if (!input || typeof input.poolId !== 'string') {
|
|
194
|
-
throw new TypeError('hedge-ensemble.weights: poolId required');
|
|
195
|
-
}
|
|
196
|
-
const state = loadWeights(input);
|
|
197
|
-
const pool = state.pools[input.poolId];
|
|
198
|
-
if (!pool) return {};
|
|
199
|
-
const total = Object.values(pool.agents).reduce((s, x) => s + x.weight, 0);
|
|
200
|
-
/** @type {Record<string, number>} */
|
|
201
|
-
const out = {};
|
|
202
|
-
for (const [k, v] of Object.entries(pool.agents)) {
|
|
203
|
-
out[k] = total > 0 ? v.weight / total : 0;
|
|
204
|
-
}
|
|
205
|
-
return out;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
module.exports = {
|
|
209
|
-
loss,
|
|
210
|
-
vote,
|
|
211
|
-
weights,
|
|
212
|
-
loadWeights,
|
|
213
|
-
saveWeights,
|
|
214
|
-
DEFAULT_VOTE_THRESHOLD,
|
|
215
|
-
DEFAULT_WEIGHTS_PATH,
|
|
216
|
-
SCHEMA_VERSION,
|
|
217
|
-
};
|
package/skills/discover/SKILL.md
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: discover
|
|
3
|
-
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 /gdd:scan when a fast-path context build is wanted instead of the full /gdd:explore. Activates for requests involving detecting an existing design system, inventorying tokens and components, or onboarding a brownfield repo."
|
|
4
|
-
argument-hint: "[--auto] [--incremental] [--full]"
|
|
5
|
-
user-invocable: true
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Get Design Done - Discover
|
|
9
|
-
|
|
10
|
-
**Stage 1.5 of 4.** Produces `.design/DESIGN-CONTEXT.md`.
|
|
11
|
-
|
|
12
|
-
Full procedure detail: `./discover-procedure.md`.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## State Integration
|
|
17
|
-
|
|
18
|
-
1. Read `.design/STATE.md`.
|
|
19
|
-
- **Missing** -> create minimal skeleton from `reference/STATE-TEMPLATE.md` (stage=discover, status=in_progress, task_progress=0/1) and log warning "STATE.md not found - created fresh. If this is a resumed session, run /get-design-done:scan first."
|
|
20
|
-
- **Present + stage==discover + status==in_progress** -> RESUME (continue interview; do not reset).
|
|
21
|
-
- **Otherwise** -> normal transition: set frontmatter stage=discover, `<position>` stage=discover, status=in_progress, task_progress=0/1.
|
|
22
|
-
2. Probe connection availability. ToolSearch runs FIRST (MCP tools may be in the deferred tool set). Run three probes - A (Figma, variant-agnostic with prefix tiebreaker), B (Refero, ToolSearch-only), C (Pinterest, ToolSearch-only). After all probes, write `<connections>` to STATE.md so the builder doesn't re-probe. Full probe specs: `./discover-procedure.md` §Connection Probes.
|
|
23
|
-
3. Update `last_checkpoint`. Write STATE.md.
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Auto Mode
|
|
28
|
-
|
|
29
|
-
When `--auto` is passed to the builder: if `tailwind.config.{js,cjs,mjs,ts}` exists -> Tailwind-only project (skip CSS file grep, parse tailwind.config for palette/spacing/font, use those as the baseline style signal). Else fall through to the existing CSS file grep logic. Detail: `./discover-procedure.md` §Auto Mode.
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Incremental Mode (Phase 53, default)
|
|
34
|
-
|
|
35
|
-
`--incremental` is the DEFAULT after Phase 53; pass `--full` to opt out and re-map everything. Incremental runs the change classifier FIRST (via the fingerprint store at `.design/fingerprints/`): it fingerprints the current DesignContext graph, diffs each node against the prior cycle, and dispatches mappers per the verdict. SKIP (cosmetic-only / no-op) dispatches 0 mappers; PARTIAL re-maps only the affected community batches; ARCHITECTURE re-batches the dir-reshaped subset; FULL (or `--full`, or a first run with no prior store) re-maps all batches. The batching + classifier engine lives in `explore` (`scripts/lib/explore-parallel-runner` + `scripts/lib/mappers/incremental-discover.cjs`); this flag selects the path. Detail: `./discover-procedure.md` §Incremental Mode.
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## Step 1 - Spawn design-context-builder
|
|
40
|
-
|
|
41
|
-
Spawn `design-context-builder` -> `.design/DESIGN-CONTEXT.md`. The agent auto-detects via grep/glob first and interviews only for areas where auto-detect returned no confident answer. Baseline audit directory chain: `src/` -> `app/` -> `pages/` -> `lib/` -> flag "layout unknown". Common gray areas to probe (Area 7): font-change risk, token-layer introduction risk, component rebuild-vs-restyle. Wait for `## CONTEXT COMPLETE`, then update STATE.md `task_progress = 0.5`. Full prompt: `./discover-procedure.md` §Step 1.
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## Step 1.75 - Lazy gate: should design-context-checker run? (Plan 10.1-04, D-21)
|
|
46
|
-
|
|
47
|
-
Spawn the cheap Haiku gate `design-context-checker-gate` before the full checker. It applies the single-file heuristic (is `DESIGN-CONTEXT.md` in `git diff --name-only HEAD~1..HEAD`?) and emits JSON + `## GATE COMPLETE`. On `spawn: false`: append `lazy_skipped: true` telemetry row, skip Step 2, set STATE.md `<position>` as if checker passed. On `spawn: true`: proceed to Step 2. On first-run discover the gate always returns `spawn: true` (builder just wrote the file); the gate meaningfully short-circuits only on re-runs where the builder made no changes. Full prompt: `./discover-procedure.md` §Step 1.75.
|
|
48
|
-
|
|
49
|
-
**Parallel synthesizer note:** discover does not spawn parallel researchers in v1, so `skills/synthesize/` is not wired here. If future variants spawn N parallel interviewers, wire synthesize between dispatch and collate as in `skills/map/` Step 3.5.
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## Step 2 - Spawn design-context-checker
|
|
54
|
-
|
|
55
|
-
Spawn `design-context-checker` with `<required_reading>` on STATE.md + DESIGN-CONTEXT.md. The agent validates DESIGN-CONTEXT.md across 6 dimensions and returns APPROVED or BLOCKED with per-dimension verdicts. Wait for `## CONTEXT CHECK COMPLETE`. Full prompt: `./discover-procedure.md` §Step 2.
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
## Step 3 - Handle checker verdict
|
|
60
|
-
|
|
61
|
-
- **APPROVED** -> proceed to state update.
|
|
62
|
-
- **BLOCKED** -> present blocked dimensions to user, offer fix-and-retry loop (re-spawn builder with specific fix instructions). Do not proceed to planning.
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## State Update (exit)
|
|
67
|
-
|
|
68
|
-
1. Set `<position>` `status=completed`, `task_progress=1/1`.
|
|
69
|
-
2. Set `<timestamps>` `discover_completed_at=<ISO 8601 now>`.
|
|
70
|
-
3. Update `last_checkpoint`. Write STATE.md.
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## After Writing
|
|
75
|
-
|
|
76
|
-
Print the "=== Discovery complete ===" block with saved path, baseline score, top key issues, and next step (`/get-design-done:plan`). Do not proceed to planning automatically unless `--auto` was passed. Template: `./discover-procedure.md` §After Writing.
|
|
77
|
-
|
|
78
|
-
## DISCOVER COMPLETE
|