@haposoft/cafekit 0.18.0 → 0.18.2
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/README.md +12 -1
- package/bin/__tests__/claude-settings.test.js +123 -0
- package/bin/__tests__/codex-ask-guard.test.js +123 -0
- package/bin/__tests__/codex-ask-projection.test.js +51 -0
- package/bin/__tests__/codex-hooks-ownership.test.js +80 -1
- package/bin/__tests__/codex-hooks.test.js +938 -237
- package/bin/__tests__/codex-native-hook-boundaries.test.js +136 -0
- package/bin/__tests__/codex-native.test.js +222 -149
- package/bin/__tests__/codex-projection-parity.test.js +314 -0
- package/bin/__tests__/codex-quiet-hooks.test.js +170 -0
- package/bin/__tests__/codex-research-projection.test.js +70 -0
- package/bin/__tests__/codex-specs-projection.test.js +76 -0
- package/bin/__tests__/develop-contract.test.js +102 -1362
- package/bin/__tests__/docs-sync-on-demand.test.js +25 -20
- package/bin/__tests__/inspect-paths.test.js +183 -0
- package/bin/__tests__/installer-safety.test.js +335 -0
- package/bin/__tests__/legacy-authority-decoupled.test.js +14 -19
- package/bin/__tests__/legacy-hooks-retired.test.js +18 -8
- package/bin/__tests__/omp-bridge.test.js +66 -12
- package/bin/__tests__/omp-hooks.test.js +11 -5
- package/bin/__tests__/orca-session.test.js +7 -0
- package/bin/__tests__/package-inventory.test.js +1 -395
- package/bin/__tests__/privacy-paths.test.js +317 -0
- package/bin/__tests__/spec-narrowing.test.js +30 -134
- package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -372
- package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
- package/bin/__tests__/usage-hook-retired.test.js +5 -3
- package/bin/__tests__/validator-grounding.test.js +17 -2929
- package/bin/install.js +25 -0
- package/bin/lib/codex-install.js +134 -12
- package/bin/lib/context.js +1 -1
- package/bin/lib/managed-writer.js +2 -3
- package/bin/phases/claude-runtime.js +91 -25
- package/bin/phases/claude-settings.js +22 -1
- package/bin/phases/codex-hooks.js +96 -33
- package/bin/phases/codex-runtime.js +45 -49
- package/bin/phases/copy-payload.js +11 -28
- package/bin/phases/omp-runtime.js +6 -3
- package/package.json +2 -3
- package/src/claude/CLAUDE.md +4 -0
- package/src/claude/agents/code-auditor.md +9 -6
- package/src/claude/agents/debugger.md +3 -9
- package/src/claude/agents/docs-keeper.md +4 -7
- package/src/claude/agents/git-ops.md +2 -4
- package/src/claude/agents/implementer.md +6 -7
- package/src/claude/agents/inspector.md +2 -0
- package/src/claude/agents/project-manager.md +2 -2
- package/src/claude/agents/spec-maker.md +4 -4
- package/src/claude/agents/test-runner.md +8 -11
- package/src/claude/agents/ui-ux-designer.md +0 -1
- package/src/claude/gitignore +3 -0
- package/src/claude/hooks/__tests__/installed-root.test.js +111 -0
- package/src/claude/hooks/__tests__/precompact.test.js +206 -10
- package/src/claude/hooks/__tests__/privacy-block.test.js +27 -0
- package/src/claude/hooks/__tests__/session-writeenv.test.js +25 -0
- package/src/claude/hooks/__tests__/spec-gate.test.js +311 -340
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +230 -8
- package/src/claude/hooks/__tests__/state.test.js +207 -7
- package/src/claude/hooks/__tests__/update-check.test.js +88 -0
- package/src/claude/hooks/agent.cjs +7 -6
- package/src/claude/hooks/inspect-block.cjs +15 -63
- package/src/claude/hooks/lib/inspect-paths.cjs +304 -0
- package/src/claude/hooks/lib/parser.cjs +1 -1
- package/src/claude/hooks/lib/privacy-command-analysis.cjs +191 -17
- package/src/claude/hooks/lib/privacy-paths.cjs +313 -0
- package/src/claude/hooks/lib/project-root.cjs +15 -0
- package/src/claude/hooks/lib/rules-reservation.cjs +36 -0
- package/src/claude/hooks/lib/spec-touch.cjs +67 -0
- package/src/claude/hooks/lib/update-check.cjs +77 -0
- package/src/claude/hooks/precompact.cjs +8 -15
- package/src/claude/hooks/privacy-block.cjs +23 -205
- package/src/claude/hooks/rules.cjs +6 -28
- package/src/claude/hooks/session.cjs +34 -117
- package/src/claude/hooks/spec-gate.cjs +17 -89
- package/src/claude/hooks/spec-state.cjs +89 -96
- package/src/claude/hooks/state.cjs +96 -51
- package/src/claude/migration-manifest.json +20 -8
- package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
- package/src/claude/output-styles/coding-level-1-junior.md +3 -3
- package/src/claude/output-styles/coding-level-2-mid.md +3 -3
- package/src/claude/output-styles/coding-level-3-senior.md +2 -2
- package/src/claude/output-styles/coding-level-4-lead.md +5 -5
- package/src/claude/output-styles/coding-level-5-god.md +6 -6
- package/src/claude/rules/ai-dev-rules.md +1 -2
- package/src/claude/rules/hook-protocols.md +1 -1
- package/src/claude/rules/workflow.md +1 -1
- package/src/claude/runtime.json +0 -3
- package/src/claude/scripts/generate-skill-catalog.cjs +2 -2
- package/src/claude/scripts/provenance.cjs +14 -0
- package/src/claude/scripts/spec-receipt.cjs +0 -114
- package/src/claude/scripts/spec-resolver.cjs +41 -457
- package/src/claude/scripts/workflow-policy.cjs +3 -1801
- package/src/claude/settings/settings.json +1 -1
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/ai-multimodal/scripts/check_setup.py +8 -68
- package/src/claude/skills/ai-multimodal/scripts/gemini_batch_process.py +6 -28
- package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
- package/src/claude/skills/code-review/SKILL.md +25 -108
- package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -7
- package/src/claude/skills/develop/SKILL.md +22 -70
- package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
- package/src/claude/skills/git/SKILL.md +1 -1
- package/src/claude/skills/git/references/worktree-blueprint.md +1 -1
- package/src/claude/skills/research/SKILL.md +24 -55
- package/src/claude/skills/sync/SKILL.md +12 -34
- package/src/claude/skills/sync/references/rebind-and-audit.md +2 -2
- package/src/claude/skills/test/SKILL.md +39 -123
- package/src/claude/skills/test/references/execution-strategy.md +11 -22
- package/src/claude/skills/ui-ux/SKILL.md +1 -1
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +24 -79
- package/src/claude/skills/web-testing/SKILL.md +2 -20
- package/src/codex/AGENTS.md +8 -0
- package/src/codex/hooks/ask-guard.cjs +54 -0
- package/src/codex/hooks/inspect-block.cjs +11 -7
- package/src/codex/hooks/lib/inspect-paths.cjs +1 -199
- package/src/codex/hooks/lib/privacy-paths.cjs +1 -0
- package/src/codex/hooks/lib/privacy-state.cjs +98 -16
- package/src/codex/hooks/lib/rules-reservation.cjs +38 -0
- package/src/codex/hooks/lib/secret-keywords.cjs +1 -0
- package/src/codex/hooks/lib/spec-receipt.cjs +2 -30
- package/src/codex/hooks/lib/spec-session-touch.cjs +100 -0
- package/src/codex/hooks/lib/spec-touch.cjs +3 -0
- package/src/codex/hooks/lib/spec-utils.cjs +6 -45
- package/src/codex/hooks/lib/update-check.cjs +1 -0
- package/src/codex/hooks/precompact.cjs +39 -0
- package/src/codex/hooks/privacy-approval.cjs +28 -17
- package/src/codex/hooks/privacy-block.cjs +9 -215
- package/src/codex/hooks/rules.cjs +4 -30
- package/src/codex/hooks/secret-output-guardrail.cjs +1 -1
- package/src/codex/hooks/session.cjs +47 -11
- package/src/codex/hooks/spec-gate.cjs +32 -74
- package/src/codex/hooks/spec-state.cjs +68 -46
- package/src/codex/hooks/state.cjs +25 -4
- package/src/codex/hooks.json +39 -7
- package/src/codex/rules/hook-protocols.md +13 -6
- package/src/common/AGENTS.md +4 -10
- package/src/omp/extensions/cafekit-bridge.mjs +12 -4
- package/src/omp/hooks/privacy-block.cjs +24 -206
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
- package/src/claude/hooks/lib/context.cjs +0 -527
- package/src/claude/hooks/lib/detect.cjs +0 -474
- package/src/claude/scripts/change-firewall.cjs +0 -1748
- package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
- package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
- package/src/claude/scripts/spec-final-state.cjs +0 -290
- package/src/claude/scripts/spec-ground.cjs +0 -650
- package/src/claude/scripts/spec-readiness.cjs +0 -425
- package/src/claude/scripts/spec-scaffold.cjs +0 -2317
- package/src/claude/scripts/spec-semantic-model.cjs +0 -396
- package/src/claude/scripts/validate-spec-output.cjs +0 -3914
- package/src/claude/skills/code-review/references/verification-gate.md +0 -41
- package/src/claude/skills/specs/templates/design.md +0 -82
- package/src/claude/skills/specs/templates/requirements-init.md +0 -8
- package/src/claude/skills/specs/templates/requirements.md +0 -42
- package/src/claude/skills/specs/templates/spec-state.json +0 -56
- package/src/claude/skills/specs/templates/task.md +0 -47
- package/src/codex/rules/state-sync.md +0 -60
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
+
const { projectRoot } = require('./lib/project-root.cjs');
|
|
17
|
+
const { packetAt, namedPackets, explicitFailure } = require('./lib/spec-touch.cjs');
|
|
16
18
|
const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
|
|
17
19
|
|
|
18
20
|
function logCrash(error) {
|
|
@@ -40,44 +42,6 @@ function sessionIdentity(payload) {
|
|
|
40
42
|
return candidates.find((value) => typeof value === 'string' && value.length > 0) || null;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
function canonicalPath(value) {
|
|
44
|
-
try { return fs.realpathSync(value); } catch { return null; }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Real path of `value`, resolving its deepest existing ancestor (the file may not exist yet). */
|
|
48
|
-
function canonicalTarget(value) {
|
|
49
|
-
if (typeof value !== 'string' || !value) return null;
|
|
50
|
-
let current = path.resolve(value);
|
|
51
|
-
const rest = [];
|
|
52
|
-
while (!fs.existsSync(current)) {
|
|
53
|
-
const parent = path.dirname(current);
|
|
54
|
-
if (parent === current) return null;
|
|
55
|
-
rest.unshift(path.basename(current));
|
|
56
|
-
current = parent;
|
|
57
|
-
}
|
|
58
|
-
const real = canonicalPath(current);
|
|
59
|
-
return real ? path.join(real, ...rest) : null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function escapeRegExp(text) {
|
|
63
|
-
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Packets the prompt names: `specs/<name>`, a `/cf:<command> <name>` token, or the bare
|
|
68
|
-
* name when it carries `-`, `_` or a digit. A plain word such as `docs` must use `specs/`,
|
|
69
|
-
* so an ordinary sentence does not touch a packet.
|
|
70
|
-
*/
|
|
71
|
-
function packetsNamedIn(text, names) {
|
|
72
|
-
if (typeof text !== 'string' || !text) return [];
|
|
73
|
-
return names.filter((name) => {
|
|
74
|
-
const n = escapeRegExp(name);
|
|
75
|
-
if (new RegExp(`(^|[^\\w-])specs/${n}(?![\\w-])`).test(text)) return true;
|
|
76
|
-
if (new RegExp(`/cf:[\\w-]+\\s+(?:specs/)?${n}(?![\\w-])`).test(text)) return true;
|
|
77
|
-
return /[-_0-9]/.test(name) && new RegExp(`(^|[^\\w/-])${n}(?![\\w-])`).test(text);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
45
|
try {
|
|
82
46
|
// ── Main ──────────────────────────────────────────────────────────────────
|
|
83
47
|
|
|
@@ -94,19 +58,22 @@ try {
|
|
|
94
58
|
let RECEIPT;
|
|
95
59
|
try {
|
|
96
60
|
POLICY = require(path.join(__dirname, '..', 'scripts', 'workflow-policy.cjs'));
|
|
61
|
+
require(path.join(__dirname, '..', 'scripts', 'provenance.cjs')).enableSnapshotMemo();
|
|
97
62
|
RESOLVER = require(path.join(__dirname, '..', 'scripts', 'spec-resolver.cjs'));
|
|
98
63
|
RECEIPT = require(path.join(__dirname, '..', 'scripts', 'spec-receipt.cjs'));
|
|
99
|
-
if (typeof
|
|
64
|
+
if (typeof RESOLVER.resolveWorkflowCandidate !== 'function') throw new Error('shared spec resolver has no resolveWorkflowCandidate function');
|
|
100
65
|
} catch (error) {
|
|
101
66
|
logCrash(error);
|
|
102
67
|
emitControlledFailure(error.message);
|
|
103
68
|
process.exit(0);
|
|
104
69
|
}
|
|
105
70
|
|
|
71
|
+
const baseDir = projectRoot(payload);
|
|
72
|
+
|
|
106
73
|
// Read runtime configuration if exists
|
|
107
74
|
let runtime = {};
|
|
108
75
|
try {
|
|
109
|
-
const p = runtimePath(
|
|
76
|
+
const p = runtimePath(baseDir, 'runtime.json');
|
|
110
77
|
if (fs.existsSync(p)) runtime = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
111
78
|
} catch { /* ignore */ }
|
|
112
79
|
|
|
@@ -114,20 +81,17 @@ try {
|
|
|
114
81
|
// (The Stop completion gate has its own toggle: spec.completion_gate.)
|
|
115
82
|
if (runtime.spec && runtime.spec.tollgate === false) process.exit(0);
|
|
116
83
|
|
|
117
|
-
// Same rule as spec-gate: an installed hook trusts its own location over the environment.
|
|
118
|
-
const installedRoot = path.basename(path.resolve(__dirname, '..')).startsWith('.') ? path.resolve(__dirname, '..', '..') : null;
|
|
119
|
-
const baseDir = installedRoot || process.env.PROJECT_ROOT || cwd;
|
|
120
84
|
const explicitFeature = payload.featureName || payload.feature || payload.explicitFeature || null;
|
|
121
|
-
const explicitPath = payload.specPath || payload.spec_path || payload.featurePath || null;
|
|
85
|
+
const explicitPath = payload.explicitPath || payload.specPath || payload.spec_path || payload.featurePath || null;
|
|
122
86
|
|
|
123
|
-
// ── Per-session touch set (Claude
|
|
124
|
-
// Several sessions can share one checkout. On Claude the tollgate speaks only
|
|
125
|
-
// packet this session touched.
|
|
126
|
-
// `sessionId` before normalization, so
|
|
87
|
+
// ── Per-session touch set (Claude and omp) ────────────────────────────────
|
|
88
|
+
// Several sessions can share one checkout. On Claude and omp the tollgate speaks only
|
|
89
|
+
// about a packet this session touched. Codex runs its own copy, and grok sends
|
|
90
|
+
// `sessionId` before normalization, so both keep the old behaviour.
|
|
127
91
|
const claudeSession = rawPayload && typeof rawPayload.session_id === 'string' && rawPayload.session_id
|
|
128
92
|
? rawPayload.session_id
|
|
129
93
|
: null;
|
|
130
|
-
const touchFilter =
|
|
94
|
+
const touchFilter = ['.claude', '.omp'].includes(runtimeDirName()) && claudeSession !== null;
|
|
131
95
|
const touchFile = touchFilter
|
|
132
96
|
? path.join(
|
|
133
97
|
require('./lib/hook-state-dir.cjs').hookStateDir(),
|
|
@@ -155,32 +119,73 @@ try {
|
|
|
155
119
|
|
|
156
120
|
if (payload.hook_event_name === 'PostToolUse') {
|
|
157
121
|
// Record only; no resolve, no git, no tollgate cache.
|
|
158
|
-
if (touchFilter && specsRoot && /^(Edit|Write|MultiEdit)$/.test(payload.tool_name || '')) {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
if (realRoot && realTarget) {
|
|
162
|
-
const rel = path.relative(realRoot, realTarget);
|
|
163
|
-
const parts = rel.split(path.sep);
|
|
164
|
-
if (rel && parts[0] !== '..' && !path.isAbsolute(rel) && parts.length > 1) addTouched([parts[0]]);
|
|
165
|
-
}
|
|
122
|
+
if (touchFilter && specsRoot && !explicitFailure(payload) && /^(Edit|Write|MultiEdit)$/.test(payload.tool_name || '')) {
|
|
123
|
+
const name = packetAt(payload.tool_input && payload.tool_input.file_path, specsRoot, cwd);
|
|
124
|
+
if (name) addTouched([name]);
|
|
166
125
|
}
|
|
167
126
|
process.exit(0);
|
|
168
127
|
}
|
|
169
128
|
|
|
129
|
+
// A legacy packet (a specs/<x>/ directory without plan.md that the old flow left
|
|
130
|
+
// behind) is no longer read. Say so once per session instead of on every prompt.
|
|
131
|
+
const legacyPackets = typeof RESOLVER.findLegacyPackets === 'function'
|
|
132
|
+
? RESOLVER.findLegacyPackets(baseDir, runtime)
|
|
133
|
+
: [];
|
|
134
|
+
if (legacyPackets.length > 0) {
|
|
135
|
+
const sessionKey = sessionIdentity(payload);
|
|
136
|
+
const noticeFile = sessionKey
|
|
137
|
+
? path.join(
|
|
138
|
+
require('./lib/hook-state-dir.cjs').hookStateDir(),
|
|
139
|
+
`legacy-notice-${require('crypto').createHash('sha256').update(sessionKey).digest('hex')}.json`,
|
|
140
|
+
)
|
|
141
|
+
: null;
|
|
142
|
+
let noticed = [];
|
|
143
|
+
if (noticeFile) {
|
|
144
|
+
try { noticed = JSON.parse(fs.readFileSync(noticeFile, 'utf8')); } catch { noticed = []; }
|
|
145
|
+
if (!Array.isArray(noticed)) noticed = [];
|
|
146
|
+
}
|
|
147
|
+
const fresh = legacyPackets.filter((name) => !noticed.includes(name));
|
|
148
|
+
if (fresh.length > 0) {
|
|
149
|
+
for (const name of fresh) {
|
|
150
|
+
console.log(`\n> ⚠️ \`specs/${name}\` is a legacy Specs packet without plan.md; CafeKit no longer reads it. Move ongoing work to a process-first plan.md packet.`);
|
|
151
|
+
}
|
|
152
|
+
if (noticeFile) {
|
|
153
|
+
try {
|
|
154
|
+
fs.mkdirSync(path.dirname(noticeFile), { recursive: true });
|
|
155
|
+
fs.writeFileSync(noticeFile, JSON.stringify([...noticed, ...fresh].sort()));
|
|
156
|
+
} catch { /* an unwritable state dir only repeats the notice */ }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
170
161
|
if (touchFilter) {
|
|
171
162
|
let packets = [];
|
|
172
163
|
try {
|
|
173
164
|
packets = fs.readdirSync(specsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
174
165
|
} catch { /* no specs root */ }
|
|
175
|
-
const named =
|
|
166
|
+
const named = specsRoot ? namedPackets(payload.prompt || payload.user_prompt, packets, specsRoot, baseDir) : [];
|
|
176
167
|
if (typeof explicitFeature === 'string' && explicitFeature) named.push(explicitFeature);
|
|
168
|
+
const fromPath = specsRoot ? packetAt(explicitPath, specsRoot, baseDir) : null;
|
|
169
|
+
if (fromPath) named.push(fromPath);
|
|
177
170
|
if (named.length) addTouched(named);
|
|
178
171
|
}
|
|
179
172
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
173
|
+
let resolved = RESOLVER.resolveWorkflowCandidate({ projectRoot: baseDir, runtime, explicitFeature, explicitPath });
|
|
174
|
+
if (touchFilter && !explicitFeature && !explicitPath
|
|
175
|
+
&& ['multiple_active', 'invalid_specs'].includes(resolved?.error)) {
|
|
176
|
+
const touched = readTouched();
|
|
177
|
+
const candidates = resolved.candidates || [];
|
|
178
|
+
if (!candidates.includes('<specs>') && !candidates.some(name => touched.has(name))) {
|
|
179
|
+
const selected = [...touched].map(name => RESOLVER.resolveWorkflowCandidate({
|
|
180
|
+
projectRoot: baseDir, runtime, explicitFeature: name,
|
|
181
|
+
}));
|
|
182
|
+
resolved = selected.find(candidate => candidate?.error);
|
|
183
|
+
const active = selected.filter(candidate => candidate && !candidate.error && !candidate.allTasksDone);
|
|
184
|
+
if (!resolved) resolved = active.length > 1
|
|
185
|
+
? { error: 'multiple_active', candidates: active.map(candidate => candidate.featureName) }
|
|
186
|
+
: active[0] || null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
184
189
|
|
|
185
190
|
if (!resolved) {
|
|
186
191
|
process.exit(0); // No active spec, do nothing
|
|
@@ -194,7 +199,7 @@ try {
|
|
|
194
199
|
|
|
195
200
|
if (resolved.error === 'invalid_specs') {
|
|
196
201
|
const cands = resolved.candidates.join(', ');
|
|
197
|
-
console.log(`\n> ⚠️ Invalid
|
|
202
|
+
console.log(`\n> ⚠️ Invalid workflow packet detected: ${cands}. ${resolved.reason}. Fix the packet's plan.md or task files before continuing. Tollgate paused.\n`);
|
|
198
203
|
process.exit(0);
|
|
199
204
|
}
|
|
200
205
|
|
|
@@ -208,22 +213,19 @@ try {
|
|
|
208
213
|
process.exit(0);
|
|
209
214
|
}
|
|
210
215
|
|
|
211
|
-
const activeSpec = resolved.spec || {};
|
|
212
|
-
const processWorkflow = resolved.layoutKind === 'process-v3';
|
|
213
216
|
const featureName = resolved.featureName;
|
|
214
217
|
if (touchFilter && !readTouched().has(featureName)) process.exit(0);
|
|
215
218
|
const specsPath = resolved.specsDir;
|
|
216
219
|
const runtimeContext = POLICY.deriveRuntimeContext({
|
|
217
220
|
projectRoot: baseDir,
|
|
218
221
|
specsRoot: specsPath,
|
|
219
|
-
specFile: resolved.stateFile
|
|
222
|
+
specFile: resolved.stateFile,
|
|
220
223
|
featureName,
|
|
221
224
|
runtimeSession: sessionIdentity(payload),
|
|
222
225
|
});
|
|
223
226
|
|
|
224
|
-
const phase = resolved.phase ||
|
|
225
|
-
const taskRegistry = resolved.taskRegistry ||
|
|
226
|
-
const flashTasks = POLICY.flashState(taskRegistry);
|
|
227
|
+
const phase = resolved.phase || 'unknown';
|
|
228
|
+
const taskRegistry = resolved.taskRegistry || {};
|
|
227
229
|
const taskEntries = Object.entries(taskRegistry);
|
|
228
230
|
const taskCounts = taskEntries.reduce((acc, [, task]) => {
|
|
229
231
|
const status = task?.status || 'pending';
|
|
@@ -231,10 +233,7 @@ try {
|
|
|
231
233
|
return acc;
|
|
232
234
|
}, {});
|
|
233
235
|
const featureDir = path.join(specsPath, featureName);
|
|
234
|
-
const
|
|
235
|
-
const dependencyProof = processWorkflow
|
|
236
|
-
? RECEIPT.workflowDependencyProofState(featureDir, taskRegistry, runtimeContext, POLICY)
|
|
237
|
-
: {};
|
|
236
|
+
const dependencyProof = RECEIPT.workflowDependencyProofState(featureDir, taskRegistry, runtimeContext, POLICY);
|
|
238
237
|
const taskState = taskEntries.map(([taskPath, task]) => [
|
|
239
238
|
taskPath,
|
|
240
239
|
task?.status || 'pending',
|
|
@@ -250,12 +249,9 @@ try {
|
|
|
250
249
|
const nextUnblocked = taskEntries.find(([, task]) => {
|
|
251
250
|
const status = task?.status || 'pending';
|
|
252
251
|
const deps = Array.isArray(task?.dependencies) ? task.dependencies : [];
|
|
253
|
-
return resolved.queueReady !== false && status === 'pending'
|
|
254
|
-
|
|
255
|
-
));
|
|
252
|
+
return resolved.queueReady !== false && status === 'pending'
|
|
253
|
+
&& deps.every((dep) => dependencyProof[dep]?.eligible === true);
|
|
256
254
|
});
|
|
257
|
-
const featureReceiptPresent = !processWorkflow
|
|
258
|
-
&& RECEIPT.safeRead(featureDir, 'feature-receipt.md').status === 'ok';
|
|
259
255
|
|
|
260
256
|
// ── State-change gate: only emit the full tollgate when spec state changed ──
|
|
261
257
|
// The cache is shared by installed hook copies, so its key must carry the
|
|
@@ -278,9 +274,11 @@ try {
|
|
|
278
274
|
proofState,
|
|
279
275
|
done: taskCounts.done || 0,
|
|
280
276
|
total: taskEntries.length,
|
|
281
|
-
featureReceiptPresent,
|
|
282
277
|
});
|
|
283
|
-
const
|
|
278
|
+
const cacheName = touchFilter
|
|
279
|
+
? `tollgate-last-${require('crypto').createHash('sha256').update(claudeSession).digest('hex').slice(0, 16)}.txt`
|
|
280
|
+
: 'tollgate-last.txt';
|
|
281
|
+
const cacheFile = path.join(require('./lib/hook-state-dir.cjs').hookStateDir(), cacheName);
|
|
284
282
|
|
|
285
283
|
let lastKey = '';
|
|
286
284
|
if (stateKey) {
|
|
@@ -288,11 +286,10 @@ try {
|
|
|
288
286
|
}
|
|
289
287
|
|
|
290
288
|
if (stateKey && lastKey === stateKey) {
|
|
291
|
-
const
|
|
292
|
-
const migration = processWorkflow && resolved.queueReady === false
|
|
289
|
+
const migration = resolved.queueReady === false
|
|
293
290
|
? ' Add `Specs-Contract: process-first-ready-v1` after the plan title before tasks can enter Next.'
|
|
294
291
|
: '';
|
|
295
|
-
console.log(`\n> 🔵 Spec \`${featureName}\` @ \`${phase}\` (${taskCounts.done || 0}/${taskEntries.length} tasks done). Tollgate active — sync
|
|
292
|
+
console.log(`\n> 🔵 Spec \`${featureName}\` @ \`${phase}\` (${taskCounts.done || 0}/${taskEntries.length} tasks done). Tollgate active — sync task Markdown when state changes.${migration}\n`);
|
|
296
293
|
process.exit(0);
|
|
297
294
|
}
|
|
298
295
|
|
|
@@ -312,26 +309,22 @@ try {
|
|
|
312
309
|
if (nextUnblocked) {
|
|
313
310
|
lines.push(`- Next unblocked: \`${nextUnblocked[0]}\``);
|
|
314
311
|
}
|
|
315
|
-
if (
|
|
316
|
-
lines.push(
|
|
317
|
-
}
|
|
318
|
-
if (processWorkflow) {
|
|
319
|
-
if (resolved.queueReady === false) {
|
|
320
|
-
lines.push('- Migration required: add `Specs-Contract: process-first-ready-v1` after the plan title before tasks can enter Next.');
|
|
321
|
-
}
|
|
322
|
-
lines.push('- Sync each flat task `Status:` only after verified work; task proof belongs in that task\'s inline `## Receipt`.');
|
|
323
|
-
lines.push(`- Workflow source: \`specs/${featureName}/plan.md\`; Stop re-checks every done receipt, binding Base/Head to the runtime only while a committed task file no longer matches its committed bytes.`);
|
|
324
|
-
} else {
|
|
325
|
-
lines.push('- Sync `spec.json` + task Markdown status after verified work; task proof belongs in `receipts/<task-basename>.md`.');
|
|
326
|
-
lines.push(`- Create \`feature-receipt.md\` once after final integration proof${featureReceiptPresent ? ' (present)' : ' (not required before closeout)'}.`);
|
|
327
|
-
lines.push(`- Validate with \`node ${runtimeDirName()}/scripts/validate-spec-output.cjs specs/${featureName}\`; hooks revalidate receipt bytes but never grant approval.`);
|
|
312
|
+
if (resolved.queueReady === false) {
|
|
313
|
+
lines.push('- Migration required: add `Specs-Contract: process-first-ready-v1` after the plan title before tasks can enter Next.');
|
|
328
314
|
}
|
|
315
|
+
lines.push('- Sync each flat task `Status:` only after verified work; task proof belongs in that task\'s inline `## Receipt`.');
|
|
316
|
+
lines.push(`- Workflow source: \`specs/${featureName}/plan.md\`; Stop re-checks every done receipt, binding Base/Head to the runtime only while a committed task file no longer matches its committed bytes.`);
|
|
317
|
+
lines.push('- Hooks revalidate receipt bytes but never grant approval.');
|
|
329
318
|
lines.push('');
|
|
330
319
|
|
|
331
320
|
console.log(lines.join('\n'));
|
|
332
321
|
process.exit(0);
|
|
333
322
|
|
|
334
323
|
} catch (e) {
|
|
324
|
+
if (e.code === 'no_git_history') {
|
|
325
|
+
console.log('Repo has no commits yet; create the first commit, then record Base/Head after it.');
|
|
326
|
+
process.exit(0);
|
|
327
|
+
}
|
|
335
328
|
logCrash(e);
|
|
336
329
|
emitControlledFailure(e.message);
|
|
337
330
|
process.exit(0);
|
|
@@ -8,14 +8,26 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Events:
|
|
10
10
|
* SessionStart → load previous state and print to context
|
|
11
|
-
* PostToolUse → refresh state after Task/TaskCreate/TaskUpdate/TodoWrite
|
|
11
|
+
* PostToolUse → refresh state after Agent/Task/TaskCreate/TaskUpdate/TodoWrite
|
|
12
12
|
* Stop → persist full session state and archive
|
|
13
13
|
* SubagentStop → append agent completion note to current state
|
|
14
14
|
*
|
|
15
|
-
* Storage: <runtime>/session-state/latest.md (+ archive/)
|
|
15
|
+
* Storage: <runtime>/session-state/[session hash/]latest.md (+ archive/)
|
|
16
16
|
* Exit: 0 always (fail-open)
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
function logCrash(error) {
|
|
20
|
+
try {
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const logDir = require('./lib/hook-state-dir.cjs').hookStateDir();
|
|
24
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
25
|
+
fs.appendFileSync(path.join(logDir, 'hook-log.jsonl'), JSON.stringify({
|
|
26
|
+
ts: new Date().toISOString(), hook: 'state', status: 'crash', error: error.message,
|
|
27
|
+
}) + '\n');
|
|
28
|
+
} catch (_) {}
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
try {
|
|
20
32
|
const fs = require('fs');
|
|
21
33
|
const path = require('path');
|
|
@@ -23,34 +35,73 @@ try {
|
|
|
23
35
|
const crypto = require('crypto');
|
|
24
36
|
const { execSync } = require('child_process');
|
|
25
37
|
const { parseTranscript } = require('./lib/parser.cjs');
|
|
38
|
+
const { projectRoot } = require('./lib/project-root.cjs');
|
|
39
|
+
const { containsSecretKeyword } = require('./lib/secret-keywords.cjs');
|
|
26
40
|
const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
|
|
27
41
|
|
|
28
42
|
const EXPIRY_DAYS = 7;
|
|
29
43
|
const MAX_ARCHIVES = 5;
|
|
30
44
|
const MAX_AGENT_SECTIONS = 3;
|
|
31
45
|
const MAX_MODIFIED_FILES_DISPLAY = 5;
|
|
32
|
-
const TRACKED_POST_TOOL_EVENTS = new Set(['Task', 'TaskCreate', 'TaskUpdate', 'TodoWrite']);
|
|
46
|
+
const TRACKED_POST_TOOL_EVENTS = new Set(['Agent', 'Task', 'TaskCreate', 'TaskUpdate', 'TodoWrite']);
|
|
47
|
+
const WAIT_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
33
48
|
|
|
34
|
-
function
|
|
49
|
+
function withStateLock(dir, callback) {
|
|
50
|
+
const lock = path.join(dir, '.write-lock');
|
|
51
|
+
const deadline = Date.now() + 3000;
|
|
52
|
+
while (Date.now() < deadline) {
|
|
53
|
+
try {
|
|
54
|
+
fs.mkdirSync(lock);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error.code !== 'EEXIST') return;
|
|
57
|
+
try {
|
|
58
|
+
if (Date.now() - fs.statSync(lock).mtimeMs > 15000) {
|
|
59
|
+
fs.rmSync(lock, { recursive: true, force: true });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
} catch { continue; }
|
|
63
|
+
Atomics.wait(WAIT_BUFFER, 0, 0, 20);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
try { return callback(); } finally {
|
|
67
|
+
try { fs.rmdirSync(lock); } catch { /* already released */ }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function assistantSummary(value) {
|
|
73
|
+
if (value === null || value === undefined) return '';
|
|
74
|
+
let text;
|
|
75
|
+
if (typeof value === 'object') {
|
|
76
|
+
const preferred = value.message ?? value.output ?? value.result ?? value.content;
|
|
77
|
+
text = typeof preferred === 'string' ? preferred : JSON.stringify(value);
|
|
78
|
+
} else {
|
|
79
|
+
text = String(value);
|
|
80
|
+
}
|
|
81
|
+
if (containsSecretKeyword(text)) return '[omitted: possible secret]';
|
|
82
|
+
const clean = text.replace(/\0/g, '').trim();
|
|
83
|
+
return clean.length > 600 ? `${clean.slice(0, 600)}…` : clean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function stateDir(cwd, sessionId, create = true) {
|
|
35
87
|
try {
|
|
36
|
-
|
|
88
|
+
let dir;
|
|
37
89
|
if (fs.existsSync(runtimeDir(cwd))) {
|
|
38
|
-
|
|
39
|
-
|
|
90
|
+
dir = runtimePath(cwd, 'session-state');
|
|
91
|
+
} else {
|
|
92
|
+
const hash = crypto.createHash('md5').update(cwd).digest('hex').slice(0, 12);
|
|
93
|
+
dir = path.join(os.homedir(), '.claude', 'session-states', hash);
|
|
40
94
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (!fs.existsSync(global)) fs.mkdirSync(global, { recursive: true });
|
|
45
|
-
return global;
|
|
95
|
+
if (sessionId) dir = path.join(dir, crypto.createHash('sha256').update(sessionId).digest('hex').slice(0, 16));
|
|
96
|
+
if (create && !fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
97
|
+
return dir;
|
|
46
98
|
} catch {
|
|
47
99
|
return null;
|
|
48
100
|
}
|
|
49
101
|
}
|
|
50
102
|
|
|
51
|
-
function loadLatest(
|
|
103
|
+
function loadLatest(dir) {
|
|
52
104
|
try {
|
|
53
|
-
const dir = stateDir(cwd);
|
|
54
105
|
if (!dir) return null;
|
|
55
106
|
|
|
56
107
|
const file = path.join(dir, 'latest.md');
|
|
@@ -109,7 +160,8 @@ try {
|
|
|
109
160
|
timestamp: new Date().toISOString(),
|
|
110
161
|
branch: process.env.GIT_BRANCH || '',
|
|
111
162
|
todos: [],
|
|
112
|
-
modifiedFiles: []
|
|
163
|
+
modifiedFiles: [],
|
|
164
|
+
lastAssistant: assistantSummary(stdinData.last_assistant_message),
|
|
113
165
|
};
|
|
114
166
|
|
|
115
167
|
if (stdinData.transcript_path && fs.existsSync(stdinData.transcript_path)) {
|
|
@@ -178,6 +230,7 @@ try {
|
|
|
178
230
|
"## What's Left",
|
|
179
231
|
...(pending.length ? pending.map((todo) => `- [ ] ${todo.content}`) : ['- (All tasks completed)']),
|
|
180
232
|
'',
|
|
233
|
+
...(data.lastAssistant ? ['## Latest Assistant Message', data.lastAssistant, ''] : []),
|
|
181
234
|
'## Key Files Modified',
|
|
182
235
|
...(data.modifiedFiles.length ? data.modifiedFiles.map((file) => `- ${file}`) : ['- (No file changes detected)']),
|
|
183
236
|
''
|
|
@@ -186,9 +239,10 @@ try {
|
|
|
186
239
|
|
|
187
240
|
function buildAgentSection(data) {
|
|
188
241
|
const agentType = data.agent_type || '';
|
|
189
|
-
|
|
242
|
+
const message = assistantSummary(data.last_assistant_message);
|
|
243
|
+
if (!agentType || agentType === 'unknown') return message ? `\n## Latest Assistant Message\n${message}\n` : '';
|
|
190
244
|
const time = new Date().toISOString().slice(11, 19);
|
|
191
|
-
return `\n## Agent Result: ${agentType} (${time})\n- Completed at ${time}\n`;
|
|
245
|
+
return `\n## Agent Result: ${agentType} (${time})\n- ${message || `Completed at ${time}`}\n`;
|
|
192
246
|
}
|
|
193
247
|
|
|
194
248
|
function takeRecentAgentSections(sections) {
|
|
@@ -229,11 +283,13 @@ try {
|
|
|
229
283
|
}
|
|
230
284
|
|
|
231
285
|
async function persistSnapshot(dir, data, options = {}) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
286
|
+
withStateLock(dir, () => {
|
|
287
|
+
const file = path.join(dir, 'latest.md');
|
|
288
|
+
const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
289
|
+
const content = mergeAgentSections(existing, buildStateContent(data));
|
|
290
|
+
writeAtomic(file, content);
|
|
291
|
+
if (options.archive) archive(dir);
|
|
292
|
+
});
|
|
237
293
|
}
|
|
238
294
|
|
|
239
295
|
async function main() {
|
|
@@ -244,14 +300,14 @@ try {
|
|
|
244
300
|
const rawPayload = JSON.parse(stdin);
|
|
245
301
|
const data = normalizeHookPayload(rawPayload);
|
|
246
302
|
const event = data.hook_event_name || '';
|
|
247
|
-
const cwd = data
|
|
248
|
-
const
|
|
303
|
+
const cwd = projectRoot(data);
|
|
304
|
+
const claudeSession = ['.claude', '.omp'].includes(runtimeDirName())
|
|
305
|
+
&& typeof rawPayload.session_id === 'string' && rawPayload.session_id.length > 0;
|
|
306
|
+
const dir = stateDir(cwd, claudeSession ? rawPayload.session_id : null, event !== 'SessionStart');
|
|
249
307
|
|
|
250
308
|
if (event === 'SessionStart') {
|
|
251
|
-
const previous = loadLatest(
|
|
252
|
-
// Claude
|
|
253
|
-
const claudeSession = runtimeDirName() === '.claude'
|
|
254
|
-
&& typeof rawPayload.session_id === 'string' && rawPayload.session_id.length > 0;
|
|
309
|
+
const previous = loadLatest(dir);
|
|
310
|
+
// Claude and omp: grok (camelCase sessionId) and Codex keep printing the block.
|
|
255
311
|
if (previous && (!claudeSession || hasPriorContent(previous))) {
|
|
256
312
|
console.log('\n=== Prior Execution Context ===');
|
|
257
313
|
console.log(previous.trim());
|
|
@@ -275,13 +331,15 @@ try {
|
|
|
275
331
|
const agentSection = buildAgentSection(data);
|
|
276
332
|
if (!agentSection) process.exit(0);
|
|
277
333
|
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
?
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
334
|
+
const sessionData = await extractSessionData(data);
|
|
335
|
+
withStateLock(dir, () => {
|
|
336
|
+
const file = path.join(dir, 'latest.md');
|
|
337
|
+
const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
338
|
+
const updated = existing
|
|
339
|
+
? appendAgentSection(existing, agentSection)
|
|
340
|
+
: `${buildStateContent(sessionData)}\n${agentSection}`;
|
|
341
|
+
writeAtomic(file, updated);
|
|
342
|
+
});
|
|
285
343
|
process.exit(0);
|
|
286
344
|
}
|
|
287
345
|
|
|
@@ -294,24 +352,11 @@ try {
|
|
|
294
352
|
process.exit(0);
|
|
295
353
|
}
|
|
296
354
|
|
|
297
|
-
main().catch(() => {
|
|
355
|
+
main().catch((error) => {
|
|
356
|
+
logCrash(error);
|
|
298
357
|
process.exit(0);
|
|
299
358
|
});
|
|
300
359
|
} catch (error) {
|
|
301
|
-
|
|
302
|
-
const fs = require('fs');
|
|
303
|
-
const path = require('path');
|
|
304
|
-
const logDir = require('./lib/hook-state-dir.cjs').hookStateDir();
|
|
305
|
-
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
|
306
|
-
fs.appendFileSync(
|
|
307
|
-
path.join(logDir, 'hook-log.jsonl'),
|
|
308
|
-
JSON.stringify({
|
|
309
|
-
ts: new Date().toISOString(),
|
|
310
|
-
hook: 'state',
|
|
311
|
-
status: 'crash',
|
|
312
|
-
error: error.message
|
|
313
|
-
}) + '\n'
|
|
314
|
-
);
|
|
315
|
-
} catch (_) {}
|
|
360
|
+
logCrash(error);
|
|
316
361
|
process.exit(0);
|
|
317
362
|
}
|
|
@@ -59,15 +59,10 @@
|
|
|
59
59
|
"validate-docs.cjs",
|
|
60
60
|
"validate-docs-reconstruct.cjs",
|
|
61
61
|
"generate-skill-catalog.cjs",
|
|
62
|
-
"validate-spec-output.cjs",
|
|
63
62
|
"scan-staged-secrets.cjs",
|
|
64
63
|
"workflow-policy.cjs",
|
|
65
64
|
"spec-resolver.cjs",
|
|
66
65
|
"spec-receipt.cjs",
|
|
67
|
-
"spec-ground.cjs",
|
|
68
|
-
"spec-semantic-model.cjs",
|
|
69
|
-
"spec-final-state.cjs",
|
|
70
|
-
"spec-readiness.cjs",
|
|
71
66
|
"provenance.cjs"
|
|
72
67
|
]
|
|
73
68
|
},
|
|
@@ -97,6 +92,18 @@
|
|
|
97
92
|
"codex": ["agents/god_developer.toml"]
|
|
98
93
|
},
|
|
99
94
|
"runtimeFiles": [
|
|
95
|
+
"scripts/validate-spec-output.cjs",
|
|
96
|
+
"scripts/spec-ground.cjs",
|
|
97
|
+
"scripts/spec-authoring-digest.cjs",
|
|
98
|
+
"scripts/spec-semantic-model.cjs",
|
|
99
|
+
"scripts/spec-final-state.cjs",
|
|
100
|
+
"skills/code-review/references/verification-gate.md",
|
|
101
|
+
"scripts/change-firewall.cjs",
|
|
102
|
+
"scripts/spec-scaffold.cjs",
|
|
103
|
+
"scripts/spec-readiness.cjs",
|
|
104
|
+
"scripts/spec-authoring-validation.cjs",
|
|
105
|
+
"hooks/lib/context.cjs",
|
|
106
|
+
"hooks/lib/detect.cjs",
|
|
100
107
|
"hooks/skill-router.cjs",
|
|
101
108
|
"hooks/lib/skill-router-routes.cjs",
|
|
102
109
|
"scripts/browser-tool.cjs",
|
|
@@ -136,7 +143,8 @@
|
|
|
136
143
|
".codex\\hooks\\semantic-review-authority.cjs",
|
|
137
144
|
".claude/hooks/task-scaffold-guard.cjs",
|
|
138
145
|
".codex/hooks/task-scaffold-guard.cjs",
|
|
139
|
-
".codex\\hooks\\task-scaffold-guard.cjs"
|
|
146
|
+
".codex\\hooks\\task-scaffold-guard.cjs",
|
|
147
|
+
".claude/hooks/inspector-block.cjs"
|
|
140
148
|
]
|
|
141
149
|
},
|
|
142
150
|
"runtime": {
|
|
@@ -161,12 +169,16 @@
|
|
|
161
169
|
"hooks/lib/counter.cjs",
|
|
162
170
|
"hooks/lib/git.cjs",
|
|
163
171
|
"hooks/lib/config.cjs",
|
|
164
|
-
"hooks/lib/context.cjs",
|
|
165
|
-
"hooks/lib/detect.cjs",
|
|
166
172
|
"hooks/lib/hook-state-dir.cjs",
|
|
167
173
|
"hooks/lib/runtime-dir.cjs",
|
|
174
|
+
"hooks/lib/spec-touch.cjs",
|
|
175
|
+
"hooks/lib/project-root.cjs",
|
|
176
|
+
"hooks/lib/update-check.cjs",
|
|
177
|
+
"hooks/lib/rules-reservation.cjs",
|
|
168
178
|
"hooks/lib/hook-payload.cjs",
|
|
179
|
+
"hooks/lib/inspect-paths.cjs",
|
|
169
180
|
"hooks/lib/privacy-command-analysis.cjs",
|
|
181
|
+
"hooks/lib/privacy-paths.cjs",
|
|
170
182
|
"hooks/lib/secret-keywords.cjs",
|
|
171
183
|
"hooks/lib/runtime-path-safety.cjs"
|
|
172
184
|
]
|