@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
|
@@ -25,14 +25,27 @@
|
|
|
25
25
|
const fs = require('node:fs');
|
|
26
26
|
const path = require('node:path');
|
|
27
27
|
|
|
28
|
-
const SNAPSHOT_DIR = path.resolve(process.cwd(), '.design', 'snapshots');
|
|
29
|
-
const STATE_MD_PATH = path.resolve(process.cwd(), '.design', 'STATE.md');
|
|
30
|
-
const EVENTS_PATH = path.resolve(process.cwd(), '.design', 'telemetry', 'events.jsonl');
|
|
31
28
|
const RETENTION_COUNT = 10;
|
|
32
29
|
const EVENTS_TAIL_COUNT = 50;
|
|
33
30
|
const DECISIONS_TAIL_COUNT = 10;
|
|
34
31
|
const SCHEMA_VERSION = '1.0.0';
|
|
35
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the bundle of paths the hook reads/writes, anchored at `cwd`.
|
|
35
|
+
*
|
|
36
|
+
* Originally these resolved at module load via `process.cwd()`, which is
|
|
37
|
+
* the wrong anchor when Claude Code invokes the hook from a worktree
|
|
38
|
+
* (the harness's cwd at module load can differ from the project root).
|
|
39
|
+
* Resolving against `payload.cwd` matches how 8 sibling hooks already work.
|
|
40
|
+
*/
|
|
41
|
+
function computePaths(cwd) {
|
|
42
|
+
return {
|
|
43
|
+
snapshotDir: path.resolve(cwd, '.design', 'snapshots'),
|
|
44
|
+
stateMdPath: path.resolve(cwd, '.design', 'STATE.md'),
|
|
45
|
+
eventsPath: path.resolve(cwd, '.design', 'telemetry', 'events.jsonl'),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
// ---------------------------------------------------------------------------
|
|
37
50
|
// Harness detection (D-10)
|
|
38
51
|
// ---------------------------------------------------------------------------
|
|
@@ -66,13 +79,13 @@ function getAppendEvent() {
|
|
|
66
79
|
// STATE.md tolerant parser — extracts frontmatter + decisions + blockers
|
|
67
80
|
// ---------------------------------------------------------------------------
|
|
68
81
|
|
|
69
|
-
function readStateSections() {
|
|
70
|
-
if (!fs.existsSync(
|
|
82
|
+
function readStateSections(paths) {
|
|
83
|
+
if (!fs.existsSync(paths.stateMdPath)) {
|
|
71
84
|
return { frontmatter: {}, decisions: [], blockers: [], session: '' };
|
|
72
85
|
}
|
|
73
86
|
let body;
|
|
74
87
|
try {
|
|
75
|
-
body = fs.readFileSync(
|
|
88
|
+
body = fs.readFileSync(paths.stateMdPath, 'utf8');
|
|
76
89
|
} catch {
|
|
77
90
|
return { frontmatter: {}, decisions: [], blockers: [], session: '' };
|
|
78
91
|
}
|
|
@@ -124,11 +137,11 @@ function readStateSections() {
|
|
|
124
137
|
// Events tail reader — JSONL-tolerant (malformed lines are skipped)
|
|
125
138
|
// ---------------------------------------------------------------------------
|
|
126
139
|
|
|
127
|
-
function readEventsTail(count) {
|
|
128
|
-
if (!fs.existsSync(
|
|
140
|
+
function readEventsTail(paths, count) {
|
|
141
|
+
if (!fs.existsSync(paths.eventsPath)) return [];
|
|
129
142
|
let body;
|
|
130
143
|
try {
|
|
131
|
-
body = fs.readFileSync(
|
|
144
|
+
body = fs.readFileSync(paths.eventsPath, 'utf8');
|
|
132
145
|
} catch {
|
|
133
146
|
return [];
|
|
134
147
|
}
|
|
@@ -149,16 +162,16 @@ function readEventsTail(count) {
|
|
|
149
162
|
// Retention prune — LRU by mtime, keep last RETENTION_COUNT (D-08)
|
|
150
163
|
// ---------------------------------------------------------------------------
|
|
151
164
|
|
|
152
|
-
function pruneSnapshots() {
|
|
165
|
+
function pruneSnapshots(paths) {
|
|
153
166
|
let files;
|
|
154
167
|
try {
|
|
155
|
-
files = fs.readdirSync(
|
|
168
|
+
files = fs.readdirSync(paths.snapshotDir);
|
|
156
169
|
} catch {
|
|
157
170
|
return;
|
|
158
171
|
}
|
|
159
172
|
const jsonFiles = files
|
|
160
173
|
.filter((f) => f.endsWith('.json') && f !== 'last-recap.json')
|
|
161
|
-
.map((f) => ({ name: f, full: path.join(
|
|
174
|
+
.map((f) => ({ name: f, full: path.join(paths.snapshotDir, f), mtime: 0 }));
|
|
162
175
|
|
|
163
176
|
for (const entry of jsonFiles) {
|
|
164
177
|
try {
|
|
@@ -186,35 +199,43 @@ function pruneSnapshots() {
|
|
|
186
199
|
async function main() {
|
|
187
200
|
const harness = detectHarness();
|
|
188
201
|
if (harness === 'codex') {
|
|
189
|
-
// D-10: Codex has no PreCompact event; emit notice + exit.
|
|
190
|
-
//
|
|
202
|
+
// D-10: Codex has no PreCompact event; emit notice + exit. Tracked in
|
|
203
|
+
// the runtime-parity matrix; full pre-large-context-action interception
|
|
204
|
+
// is on the roadmap.
|
|
191
205
|
process.stderr.write(
|
|
192
206
|
'[gdd-precompact-snapshot] this harness does not emit PreCompact; snapshots disabled\n',
|
|
193
207
|
);
|
|
194
208
|
process.exit(0);
|
|
195
209
|
}
|
|
196
210
|
|
|
197
|
-
// Drain stdin
|
|
198
|
-
//
|
|
211
|
+
// Drain stdin and extract payload.cwd. Claude Code pipes a hook-event JSON
|
|
212
|
+
// envelope including the project cwd; we need it to anchor SNAPSHOT_DIR and
|
|
213
|
+
// friends correctly when the harness's process.cwd() at module load may not
|
|
214
|
+
// match (e.g. when invoked from a worktree). Draining also avoids EPIPE on
|
|
215
|
+
// the writer side. Falls back to process.cwd() when stdin is empty or
|
|
216
|
+
// malformed (unit tests, direct invocation).
|
|
217
|
+
let buf = '';
|
|
199
218
|
try {
|
|
200
|
-
|
|
201
|
-
// Best-effort, non-blocking — we have nothing time-sensitive in stdin.
|
|
202
|
-
process.stdin.on('error', () => {
|
|
203
|
-
/* swallow */
|
|
204
|
-
});
|
|
205
|
-
process.stdin.resume();
|
|
206
|
-
}
|
|
219
|
+
for await (const chunk of process.stdin) buf += chunk;
|
|
207
220
|
} catch {
|
|
208
|
-
/* swallow */
|
|
221
|
+
/* swallow — empty stdin is fine */
|
|
222
|
+
}
|
|
223
|
+
let payload = {};
|
|
224
|
+
try {
|
|
225
|
+
payload = JSON.parse(buf || '{}');
|
|
226
|
+
} catch {
|
|
227
|
+
/* malformed stdin → fall through with empty payload */
|
|
209
228
|
}
|
|
229
|
+
const cwd = (payload && typeof payload.cwd === 'string') ? payload.cwd : process.cwd();
|
|
230
|
+
const paths = computePaths(cwd);
|
|
210
231
|
|
|
211
232
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
212
|
-
const snapshotPath = path.join(
|
|
233
|
+
const snapshotPath = path.join(paths.snapshotDir, ts + '.json');
|
|
213
234
|
const tmpPath = snapshotPath + '.tmp';
|
|
214
235
|
|
|
215
236
|
// Ensure snapshot dir exists (mkdir -p semantics).
|
|
216
237
|
try {
|
|
217
|
-
fs.mkdirSync(
|
|
238
|
+
fs.mkdirSync(paths.snapshotDir, { recursive: true });
|
|
218
239
|
} catch {
|
|
219
240
|
/* swallow — write will fail loudly below if truly missing */
|
|
220
241
|
}
|
|
@@ -240,8 +261,8 @@ async function main() {
|
|
|
240
261
|
}
|
|
241
262
|
|
|
242
263
|
try {
|
|
243
|
-
const sections = readStateSections();
|
|
244
|
-
const events = readEventsTail(EVENTS_TAIL_COUNT);
|
|
264
|
+
const sections = readStateSections(paths);
|
|
265
|
+
const events = readEventsTail(paths, EVENTS_TAIL_COUNT);
|
|
245
266
|
const decisions = sections.decisions.slice(-DECISIONS_TAIL_COUNT);
|
|
246
267
|
const cycleId =
|
|
247
268
|
sections.frontmatter && sections.frontmatter.milestone
|
|
@@ -280,7 +301,7 @@ async function main() {
|
|
|
280
301
|
}
|
|
281
302
|
|
|
282
303
|
// Retention prune (T-27.6.05-04 DoS mitigation).
|
|
283
|
-
pruneSnapshots();
|
|
304
|
+
pruneSnapshots(paths);
|
|
284
305
|
|
|
285
306
|
// Best-effort event emit.
|
|
286
307
|
const appendEvent = getAppendEvent();
|
|
@@ -59,28 +59,160 @@ function loadProtectedPaths(cwd) {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
62
|
+
* Tokenise a string of shell-style args into individual arguments, honoring
|
|
63
|
+
* single/double quotes and basic backslash escapes. Used by the bash target
|
|
64
|
+
* extractor to get reliable arg arrays for destructive coreutils.
|
|
65
|
+
*/
|
|
66
|
+
function parseShellArgs(s) {
|
|
67
|
+
const args = [];
|
|
68
|
+
let current = '';
|
|
69
|
+
let inQuote = null;
|
|
70
|
+
for (let i = 0; i < s.length; i++) {
|
|
71
|
+
const ch = s[i];
|
|
72
|
+
if (inQuote) {
|
|
73
|
+
if (ch === inQuote) {
|
|
74
|
+
inQuote = null;
|
|
75
|
+
args.push(current);
|
|
76
|
+
current = '';
|
|
77
|
+
} else if (ch === '\\' && i + 1 < s.length && (s[i + 1] === inQuote || s[i + 1] === '\\')) {
|
|
78
|
+
current += s[++i];
|
|
79
|
+
} else {
|
|
80
|
+
current += ch;
|
|
81
|
+
}
|
|
82
|
+
} else if (ch === '"' || ch === "'") {
|
|
83
|
+
if (current) { args.push(current); current = ''; }
|
|
84
|
+
inQuote = ch;
|
|
85
|
+
} else if (/\s/.test(ch)) {
|
|
86
|
+
if (current) { args.push(current); current = ''; }
|
|
87
|
+
} else if (ch === '\\' && i + 1 < s.length) {
|
|
88
|
+
current += s[++i];
|
|
89
|
+
} else {
|
|
90
|
+
current += ch;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (current) args.push(current);
|
|
94
|
+
return args;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Extract destructive-op file targets from one shell pipeline segment
|
|
99
|
+
* (already split on `&&`/`||`/`;`/`|`). Catches:
|
|
100
|
+
* - rm / cp / mv / mkdir / touch / rmdir / chmod / chown / ln / tee
|
|
101
|
+
* with ALL their non-flag args (not just the first).
|
|
102
|
+
* - git rm / mv / restore / checkout — same treatment.
|
|
103
|
+
* - sed -i <args> file1 [file2 ...]
|
|
104
|
+
* - > file and >> file redirects appearing anywhere in the segment.
|
|
105
|
+
*
|
|
106
|
+
* `sudo ` prefix is stripped before dispatch.
|
|
107
|
+
*/
|
|
108
|
+
function extractTargetsFromSegment(seg) {
|
|
109
|
+
const targets = [];
|
|
110
|
+
const cleaned = seg.replace(/^sudo\s+/, '');
|
|
111
|
+
|
|
112
|
+
// git destructive subcommands
|
|
113
|
+
const gitMatch = cleaned.match(/^git\s+(rm|mv|restore|checkout)\b(.*)$/);
|
|
114
|
+
if (gitMatch) {
|
|
115
|
+
const args = parseShellArgs(gitMatch[2]);
|
|
116
|
+
for (const arg of args) {
|
|
117
|
+
if (!arg.startsWith('-')) targets.push(arg);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// sed -i: only treat as destructive when -i is present
|
|
122
|
+
if (/^sed\b/.test(cleaned) && /(?:^|\s)-i(?:\b|=)/.test(cleaned)) {
|
|
123
|
+
const tokens = parseShellArgs(cleaned).slice(1); // drop 'sed'
|
|
124
|
+
let i = 0;
|
|
125
|
+
while (i < tokens.length) {
|
|
126
|
+
const tok = tokens[i];
|
|
127
|
+
// BSD `sed -i ''` consumes an extra empty-string arg
|
|
128
|
+
if (tok === '-i' && i + 1 < tokens.length && tokens[i + 1] === '') {
|
|
129
|
+
i += 2;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (tok.startsWith('-')) { i++; continue; }
|
|
133
|
+
// First non-flag arg may be either an in-line sed script or a file;
|
|
134
|
+
// path matcher will simply not match a non-path. Be permissive: queue all.
|
|
135
|
+
targets.push(tok);
|
|
136
|
+
i++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Coreutils destructive verbs
|
|
141
|
+
const coreutilsMatch = cleaned.match(/^(rm|cp|mv|mkdir|touch|rmdir|chmod|chown|ln|tee)\b(.*)$/);
|
|
142
|
+
if (coreutilsMatch) {
|
|
143
|
+
const args = parseShellArgs(coreutilsMatch[2]);
|
|
144
|
+
for (const arg of args) {
|
|
145
|
+
if (!arg.startsWith('-')) targets.push(arg);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Redirect targets: > file or >> file (appear anywhere in the segment)
|
|
150
|
+
const redirects = seg.matchAll(/(?:^|[^&>])>>?\s*([^\s|;&]+)/g);
|
|
151
|
+
for (const m of redirects) targets.push(m[1]);
|
|
152
|
+
|
|
153
|
+
return targets;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Extract all candidate file paths a Bash command may mutate. Walks the
|
|
158
|
+
* command string in three passes:
|
|
159
|
+
*
|
|
160
|
+
* 1. Recursively process every `$(...)` and `\`...\`` subshell. The
|
|
161
|
+
* subshell is evaluated by the shell and its OUTPUT substitutes into
|
|
162
|
+
* the parent command — but the inner commands themselves ALSO run,
|
|
163
|
+
* so anything destructive inside is a target.
|
|
164
|
+
* 2. Strip subshells from the outer command to simplify splitting.
|
|
165
|
+
* 3. Split outer command on `&&`, `||`, `;`, `|` and feed each segment
|
|
166
|
+
* to extractTargetsFromSegment.
|
|
167
|
+
*
|
|
168
|
+
* Previous implementation called String.prototype.match() (returns only
|
|
169
|
+
* the first match) and a single regex with a `[^\\s|;&>]+` capture group.
|
|
170
|
+
* That missed:
|
|
171
|
+
* - chained commands (`rm safe.txt && rm secret`)
|
|
172
|
+
* - multi-arg destructive verbs (`rm a b c` — only `a` was extracted)
|
|
173
|
+
* - subshell content (`rm $(echo secret)`)
|
|
174
|
+
* - backtick command substitution (`rm \`echo secret\``)
|
|
175
|
+
*
|
|
176
|
+
* xargs bypass — `find protected -print0 | xargs -0 rm` — is NOT closed
|
|
177
|
+
* here, because the targets come from stdin which we can't model without
|
|
178
|
+
* a full pipeline shape analysis. The `find` segment will be checked but
|
|
179
|
+
* the subsequent xargs+rm segment carries no explicit path. Project policy
|
|
180
|
+
* should rely on `find <protected-dir>` being blocked at the upstream
|
|
181
|
+
* segment via the .git/** / reference/** globs, plus general operator
|
|
182
|
+
* caution. Future enhancement: scan pipeline for xargs-with-destructive
|
|
183
|
+
* verbs and require the upstream stage to not reference protected globs.
|
|
64
184
|
*/
|
|
65
185
|
function extractBashTargets(command) {
|
|
66
186
|
if (!command) return [];
|
|
187
|
+
|
|
67
188
|
const targets = [];
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.
|
|
83
|
-
|
|
189
|
+
|
|
190
|
+
// 1. Recursive subshell scan.
|
|
191
|
+
const SUBSHELL_RE = /\$\(([^()]*)\)|`([^`]*)`/g;
|
|
192
|
+
let m;
|
|
193
|
+
while ((m = SUBSHELL_RE.exec(command)) !== null) {
|
|
194
|
+
targets.push(...extractBashTargets(m[1] !== undefined ? m[1] : m[2]));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 2. Strip subshells from outer command.
|
|
198
|
+
const stripped = command.replace(SUBSHELL_RE, '');
|
|
199
|
+
|
|
200
|
+
// 3. Split on shell separators and process each segment.
|
|
201
|
+
const segments = stripped.split(/\s*(?:&&|\|\||;|\|)\s*/);
|
|
202
|
+
for (const segment of segments) {
|
|
203
|
+
const seg = segment.trim();
|
|
204
|
+
if (!seg) continue;
|
|
205
|
+
targets.push(...extractTargetsFromSegment(seg));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Dedup + strip surrounding quotes.
|
|
209
|
+
return [
|
|
210
|
+
...new Set(
|
|
211
|
+
targets
|
|
212
|
+
.filter(Boolean)
|
|
213
|
+
.map((p) => p.replace(/^['"]|['"]$/g, '')),
|
|
214
|
+
),
|
|
215
|
+
];
|
|
84
216
|
}
|
|
85
217
|
|
|
86
218
|
async function main() {
|
package/hooks/gdd-risk-gate.js
CHANGED
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Quantifies the confidence/risk of a writer action with the PURE scorer
|
|
10
10
|
* `scripts/lib/risk/compute-risk.cjs` (executor A), emits a `risk_assessment`
|
|
11
|
-
* telemetry event,
|
|
11
|
+
* telemetry event, writes a rolling-50 calibration row via
|
|
12
|
+
* `scripts/lib/risk/calibration.cjs` updateCalibration() (Phase 56 CAL-01 — this
|
|
13
|
+
* closes the calibration loop end-to-end: production traffic, not just tests,
|
|
14
|
+
* drives detectDrift), and routes by the scorer's `suggested_action`:
|
|
12
15
|
*
|
|
13
16
|
* allow -> { continue: true } (silent)
|
|
14
17
|
* review -> { continue: true, hookSpecificOutput: { … } } (advisory, non-blocking)
|
|
@@ -83,6 +86,85 @@ let _riskLoadError = null;
|
|
|
83
86
|
}
|
|
84
87
|
})();
|
|
85
88
|
|
|
89
|
+
// ── Calibration sibling resolver (same walk-up shape as the risk module) ────
|
|
90
|
+
// scripts/lib/risk/calibration.cjs is the rolling-50 per-agent calibration
|
|
91
|
+
// store (Phase 56 CAL-01). We call updateCalibration AFTER scoring so the
|
|
92
|
+
// store grows over time with real per-agent (risk, accepted) outcomes — that
|
|
93
|
+
// is what wires the calibration loop end-to-end (under_scoring / over_scoring
|
|
94
|
+
// drift becomes detectable from real traffic, not just from synthetic tests).
|
|
95
|
+
const CAL_REL = path.join('scripts', 'lib', 'risk', 'calibration.cjs');
|
|
96
|
+
|
|
97
|
+
function findCalibrationModule(startDir) {
|
|
98
|
+
let dir = startDir;
|
|
99
|
+
for (let i = 0; i < 64; i++) {
|
|
100
|
+
const candidate = path.join(dir, CAL_REL);
|
|
101
|
+
try {
|
|
102
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
103
|
+
} catch { /* keep climbing */ }
|
|
104
|
+
const parent = path.dirname(dir);
|
|
105
|
+
if (parent === dir) break;
|
|
106
|
+
dir = parent;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let _cal = null;
|
|
112
|
+
let _calLoadError = null;
|
|
113
|
+
(function loadCal() {
|
|
114
|
+
try {
|
|
115
|
+
const modPath = findCalibrationModule(__dirname);
|
|
116
|
+
if (!modPath) {
|
|
117
|
+
_calLoadError = `calibration.cjs not found above ${__dirname}`;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// eslint-disable-next-line global-require, import/no-dynamic-require
|
|
121
|
+
_cal = require(modPath);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
_calLoadError = err && err.message ? err.message : String(err);
|
|
124
|
+
}
|
|
125
|
+
})();
|
|
126
|
+
|
|
127
|
+
// ── Calibration write (best-effort, never throws) ───────────────────────────
|
|
128
|
+
// Records one (agent, risk, accepted) outcome for the rolling-50 window.
|
|
129
|
+
//
|
|
130
|
+
// The signal we can KNOW at PreToolUse time:
|
|
131
|
+
// * action === 'block' -> definitive accepted:false (the hook rejected the
|
|
132
|
+
// call; the user never sees the tool run).
|
|
133
|
+
// * action ∈ {allow, review, require_confirmation} -> accepted:true at the
|
|
134
|
+
// PreToolUse boundary. The action proceeds past the risk gate; a later
|
|
135
|
+
// hook may still block, and the user may later /gdd:override or undo, but
|
|
136
|
+
// for THIS gate's calibration loop "the risk gate let it through" IS the
|
|
137
|
+
// acceptance signal. user_undo / post_apply_correct are deliberately left
|
|
138
|
+
// null (unresolved) — a future PostToolUse pass can resolve them later.
|
|
139
|
+
//
|
|
140
|
+
// Agent gate: a calibration row needs an agent key. When the agent is unknown
|
|
141
|
+
// (the common case for a generic PreToolUse hook) we skip the write rather
|
|
142
|
+
// than pool everything into an 'unknown' bucket that would render drift
|
|
143
|
+
// detection meaningless. The risk_assessment event still fires either way.
|
|
144
|
+
//
|
|
145
|
+
// Always best-effort: a calibration write must NEVER break a tool call.
|
|
146
|
+
function recordCalibration(agent, assessment, cwd) {
|
|
147
|
+
try {
|
|
148
|
+
if (!_cal || typeof _cal.updateCalibration !== 'function') return;
|
|
149
|
+
if (!agent || typeof agent !== 'string') return;
|
|
150
|
+
const action = assessment && assessment.suggested_action;
|
|
151
|
+
if (!action) return;
|
|
152
|
+
const score = typeof assessment.score === 'number' ? assessment.score : 0;
|
|
153
|
+
_cal.updateCalibration(
|
|
154
|
+
agent,
|
|
155
|
+
{
|
|
156
|
+
risk: score,
|
|
157
|
+
accepted: action !== 'block',
|
|
158
|
+
user_undo: false,
|
|
159
|
+
post_apply_correct: null,
|
|
160
|
+
},
|
|
161
|
+
{ root: cwd || process.cwd() },
|
|
162
|
+
);
|
|
163
|
+
} catch {
|
|
164
|
+
/* swallow — calibration writes must never throw into the gate */
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
86
168
|
// ── Best-effort `risk_assessment` event emit ────────────────────────────────
|
|
87
169
|
// The firehose (`appendEvent`, sdk/event-stream) is the sink the wire-in tests
|
|
88
170
|
// read via GDD_EVENTS_PATH. `type` is free-form on the envelope, so emitting
|
|
@@ -358,6 +440,14 @@ async function main() {
|
|
|
358
440
|
sessionId,
|
|
359
441
|
);
|
|
360
442
|
|
|
443
|
+
// Update the rolling-50 per-agent calibration window with this outcome
|
|
444
|
+
// (Phase 56 CAL-01). Best-effort; no-op when the agent is unknown. This is
|
|
445
|
+
// what closes the calibration loop end-to-end: the store accrues real
|
|
446
|
+
// (risk, accepted) pairs across the writer agent's actions, so detectDrift
|
|
447
|
+
// can flag under_scoring / over_scoring from production traffic rather than
|
|
448
|
+
// only from synthetic test calls.
|
|
449
|
+
recordCalibration(agent, assessment, cwd);
|
|
450
|
+
|
|
361
451
|
// Mirror the decision onto the hook.fired row (allow|review|confirm|block).
|
|
362
452
|
const firedDecision = action === 'block' ? 'block' : 'allow';
|
|
363
453
|
emitHookFired(firedDecision, { suggested_action: action, score: assessment.score });
|
|
@@ -404,6 +494,8 @@ if (require.main === module) {
|
|
|
404
494
|
// Exported for tests — pure helpers + the resolver. main() owns the I/O + contract.
|
|
405
495
|
module.exports = {
|
|
406
496
|
findRiskModule,
|
|
497
|
+
findCalibrationModule,
|
|
498
|
+
recordCalibration,
|
|
407
499
|
buildMergedTables,
|
|
408
500
|
compileFileSensitivityExtra,
|
|
409
501
|
isReadOnlyAgent,
|
|
@@ -21,12 +21,29 @@
|
|
|
21
21
|
const fs = require('node:fs');
|
|
22
22
|
const path = require('node:path');
|
|
23
23
|
|
|
24
|
-
const SNAPSHOT_DIR = path.resolve(process.cwd(), '.design', 'snapshots');
|
|
25
|
-
const STATE_MD_PATH = path.resolve(process.cwd(), '.design', 'STATE.md');
|
|
26
|
-
const EVENTS_PATH = path.resolve(process.cwd(), '.design', 'telemetry', 'events.jsonl');
|
|
27
|
-
const RECAP_JSON_PATH = path.join(SNAPSHOT_DIR, 'last-recap.json');
|
|
28
24
|
const SCHEMA_VERSION = '1.0.0';
|
|
29
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the bundle of paths the hook reads/writes, anchored at `cwd`.
|
|
28
|
+
*
|
|
29
|
+
* Phase 27.6 originally resolved these at module load via `process.cwd()`,
|
|
30
|
+
* which is the wrong anchor when Claude Code invokes the hook from a
|
|
31
|
+
* worktree (the harness's cwd at module load can be the parent / `.claude`
|
|
32
|
+
* directory, not the project root). Resolving against `payload.cwd` matches
|
|
33
|
+
* how 8 sibling hooks already work (gdd-protected-paths, gdd-fact-force,
|
|
34
|
+
* gdd-decision-injector, gdd-mcp-circuit-breaker, gdd-a11y-gate,
|
|
35
|
+
* gdd-design-quality-check, gdd-risk-gate, gdd-turn-closeout).
|
|
36
|
+
*/
|
|
37
|
+
function computePaths(cwd) {
|
|
38
|
+
const snapshotDir = path.resolve(cwd, '.design', 'snapshots');
|
|
39
|
+
return {
|
|
40
|
+
snapshotDir,
|
|
41
|
+
stateMdPath: path.resolve(cwd, '.design', 'STATE.md'),
|
|
42
|
+
eventsPath: path.resolve(cwd, '.design', 'telemetry', 'events.jsonl'),
|
|
43
|
+
recapJsonPath: path.join(snapshotDir, 'last-recap.json'),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
// ---------------------------------------------------------------------------
|
|
31
48
|
// Harness detection (D-10) — mirrors gdd-precompact-snapshot.js
|
|
32
49
|
// ---------------------------------------------------------------------------
|
|
@@ -60,11 +77,11 @@ function getAppendEvent() {
|
|
|
60
77
|
// needs frontmatter + a flat decisions list for the diff)
|
|
61
78
|
// ---------------------------------------------------------------------------
|
|
62
79
|
|
|
63
|
-
function readStateMd() {
|
|
64
|
-
if (!fs.existsSync(
|
|
80
|
+
function readStateMd(paths) {
|
|
81
|
+
if (!fs.existsSync(paths.stateMdPath)) return { frontmatter: {}, decisions: [] };
|
|
65
82
|
let body;
|
|
66
83
|
try {
|
|
67
|
-
body = fs.readFileSync(
|
|
84
|
+
body = fs.readFileSync(paths.stateMdPath, 'utf8');
|
|
68
85
|
} catch {
|
|
69
86
|
return { frontmatter: {}, decisions: [] };
|
|
70
87
|
}
|
|
@@ -92,11 +109,11 @@ function readStateMd() {
|
|
|
92
109
|
// Snapshot discovery — highest-mtime *.json (excluding last-recap.json)
|
|
93
110
|
// ---------------------------------------------------------------------------
|
|
94
111
|
|
|
95
|
-
function findLatestSnapshot() {
|
|
96
|
-
if (!fs.existsSync(
|
|
112
|
+
function findLatestSnapshot(paths) {
|
|
113
|
+
if (!fs.existsSync(paths.snapshotDir)) return null;
|
|
97
114
|
let files;
|
|
98
115
|
try {
|
|
99
|
-
files = fs.readdirSync(
|
|
116
|
+
files = fs.readdirSync(paths.snapshotDir);
|
|
100
117
|
} catch {
|
|
101
118
|
return null;
|
|
102
119
|
}
|
|
@@ -108,7 +125,7 @@ function findLatestSnapshot() {
|
|
|
108
125
|
let best = null;
|
|
109
126
|
let bestMtime = -1;
|
|
110
127
|
for (const name of candidates) {
|
|
111
|
-
const full = path.join(
|
|
128
|
+
const full = path.join(paths.snapshotDir, name);
|
|
112
129
|
try {
|
|
113
130
|
const mtime = fs.statSync(full).mtimeMs;
|
|
114
131
|
if (mtime > bestMtime) {
|
|
@@ -126,11 +143,11 @@ function findLatestSnapshot() {
|
|
|
126
143
|
// Event count since snapshot timestamp (JSONL-tolerant)
|
|
127
144
|
// ---------------------------------------------------------------------------
|
|
128
145
|
|
|
129
|
-
function countEventsSince(isoTimestamp) {
|
|
130
|
-
if (!fs.existsSync(
|
|
146
|
+
function countEventsSince(paths, isoTimestamp) {
|
|
147
|
+
if (!fs.existsSync(paths.eventsPath)) return 0;
|
|
131
148
|
let body;
|
|
132
149
|
try {
|
|
133
|
-
body = fs.readFileSync(
|
|
150
|
+
body = fs.readFileSync(paths.eventsPath, 'utf8');
|
|
134
151
|
} catch {
|
|
135
152
|
return 0;
|
|
136
153
|
}
|
|
@@ -154,16 +171,34 @@ function countEventsSince(isoTimestamp) {
|
|
|
154
171
|
// Main
|
|
155
172
|
// ---------------------------------------------------------------------------
|
|
156
173
|
|
|
157
|
-
function main() {
|
|
174
|
+
async function main() {
|
|
158
175
|
const harness = detectHarness();
|
|
159
176
|
if (harness === 'codex') {
|
|
160
|
-
// D-10: SessionStart on Codex skips recap
|
|
161
|
-
// pre-large-context-action integration.
|
|
162
|
-
process.stderr.write('[gdd-sessionstart-recap] codex harness no-op
|
|
177
|
+
// D-10: SessionStart on Codex skips recap. Tracked in the runtime-parity
|
|
178
|
+
// matrix; full pre-large-context-action integration is on the roadmap.
|
|
179
|
+
process.stderr.write('[gdd-sessionstart-recap] codex harness no-op\n');
|
|
163
180
|
process.exit(0);
|
|
164
181
|
}
|
|
165
182
|
|
|
166
|
-
|
|
183
|
+
// Drain stdin and extract payload.cwd (Claude Code SessionStart pipes a JSON
|
|
184
|
+
// envelope). Falls back to process.cwd() when stdin is empty (unit tests,
|
|
185
|
+
// direct invocation).
|
|
186
|
+
let buf = '';
|
|
187
|
+
try {
|
|
188
|
+
for await (const chunk of process.stdin) buf += chunk;
|
|
189
|
+
} catch {
|
|
190
|
+
/* swallow — empty stdin is fine */
|
|
191
|
+
}
|
|
192
|
+
let payload = {};
|
|
193
|
+
try {
|
|
194
|
+
payload = JSON.parse(buf || '{}');
|
|
195
|
+
} catch {
|
|
196
|
+
/* malformed stdin → fall through with empty payload */
|
|
197
|
+
}
|
|
198
|
+
const cwd = (payload && typeof payload.cwd === 'string') ? payload.cwd : process.cwd();
|
|
199
|
+
const paths = computePaths(cwd);
|
|
200
|
+
|
|
201
|
+
const snapshotPath = findLatestSnapshot(paths);
|
|
167
202
|
if (!snapshotPath) {
|
|
168
203
|
process.stderr.write('[gdd-sessionstart-recap] no prior snapshot\n');
|
|
169
204
|
process.exit(0);
|
|
@@ -181,13 +216,13 @@ function main() {
|
|
|
181
216
|
process.exit(0);
|
|
182
217
|
}
|
|
183
218
|
|
|
184
|
-
const current = readStateMd();
|
|
219
|
+
const current = readStateMd(paths);
|
|
185
220
|
const priorDecisions = Array.isArray(snapshot.last_n_decisions)
|
|
186
221
|
? snapshot.last_n_decisions
|
|
187
222
|
: [];
|
|
188
223
|
const priorSet = new Set(priorDecisions);
|
|
189
224
|
const newDecisions = current.decisions.filter((d) => !priorSet.has(d));
|
|
190
|
-
const newEventCount = countEventsSince(snapshot.timestamp || '1970-01-01T00:00:00.000Z');
|
|
225
|
+
const newEventCount = countEventsSince(paths, snapshot.timestamp || '1970-01-01T00:00:00.000Z');
|
|
191
226
|
|
|
192
227
|
const priorCycle = snapshot.cycle_id || 'unknown';
|
|
193
228
|
const currentCycle = current.frontmatter.milestone || 'unknown';
|
|
@@ -226,9 +261,9 @@ function main() {
|
|
|
226
261
|
try {
|
|
227
262
|
// mkdir -p for safety — directory should exist if snapshotPath was found,
|
|
228
263
|
// but defensive ensure for race conditions.
|
|
229
|
-
fs.mkdirSync(
|
|
230
|
-
fs.writeFileSync(
|
|
231
|
-
fs.renameSync(
|
|
264
|
+
fs.mkdirSync(paths.snapshotDir, { recursive: true });
|
|
265
|
+
fs.writeFileSync(paths.recapJsonPath + '.tmp', JSON.stringify(recap, null, 2), 'utf8');
|
|
266
|
+
fs.renameSync(paths.recapJsonPath + '.tmp', paths.recapJsonPath);
|
|
232
267
|
} catch (err) {
|
|
233
268
|
process.stderr.write(
|
|
234
269
|
'[gdd-sessionstart-recap] sidecar write failed: ' +
|
package/hooks/hooks.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"hooks": [
|
|
6
6
|
{
|
|
7
7
|
"type": "command",
|
|
8
|
-
"command": "
|
|
8
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/bootstrap.cjs\""
|
|
9
9
|
}
|
|
10
10
|
]
|
|
11
11
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"hooks": [
|
|
14
14
|
{
|
|
15
15
|
"type": "command",
|
|
16
|
-
"command": "
|
|
16
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/update-check.cjs\""
|
|
17
17
|
}
|
|
18
18
|
]
|
|
19
19
|
},
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"hooks": [
|
|
22
22
|
{
|
|
23
23
|
"type": "command",
|
|
24
|
-
"command": "
|
|
24
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/first-run-nudge.cjs\""
|
|
25
25
|
}
|
|
26
26
|
]
|
|
27
27
|
},
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"hooks": [
|
|
39
39
|
{
|
|
40
40
|
"type": "command",
|
|
41
|
-
"command": "
|
|
41
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/inject-using-gdd.cjs\""
|
|
42
42
|
}
|
|
43
43
|
]
|
|
44
44
|
}
|
|
@@ -151,6 +151,15 @@
|
|
|
151
151
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gdd-design-quality-check.js\""
|
|
152
152
|
}
|
|
153
153
|
]
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"matcher": "Edit|Write",
|
|
157
|
+
"hooks": [
|
|
158
|
+
{
|
|
159
|
+
"type": "command",
|
|
160
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gdd-intel-trigger.js\""
|
|
161
|
+
}
|
|
162
|
+
]
|
|
154
163
|
}
|
|
155
164
|
],
|
|
156
165
|
"Stop": [
|