continuous-improvement 3.12.3 → 3.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +31 -1
- package/LICENSE +21 -21
- package/QUICKSTART.md +1 -1
- package/README.md +25 -5
- package/bin/analyze.sh +161 -161
- package/bin/backfill.mjs +19 -19
- package/bin/check-doc-runtime-claims.mjs +0 -0
- package/bin/check-docs-substrings.mjs +0 -0
- package/bin/check-everything-mirror.mjs +0 -0
- package/bin/check-routing-targets.mjs +0 -0
- package/bin/check-scripts-citation-drift.mjs +0 -0
- package/bin/check-skill-count.mjs +32 -1
- package/bin/check-skill-law-tag.mjs +0 -0
- package/bin/check-skill-mirror.mjs +0 -0
- package/bin/check-skill-tiers.mjs +0 -0
- package/bin/check-test-imports-only.mjs +1 -1
- package/bin/check-third-party-shape.mjs +0 -0
- package/bin/check-tool-count.mjs +0 -0
- package/bin/companion-preference-status.mjs +0 -0
- package/bin/gateguard-clear.mjs +0 -0
- package/bin/generate-plugin-manifests.mjs +2 -1
- package/bin/harvest-friction.mjs +0 -0
- package/bin/hook-stats.mjs +21 -21
- package/bin/install.mjs +74 -1
- package/bin/lint-transcript.mjs +0 -0
- package/bin/mcp-server.mjs +65 -8
- package/bin/observe.mjs +0 -0
- package/bin/plan-pack.mjs +77 -0
- package/bin/pre-commit-block-strays.sh +0 -0
- package/bin/refresh-third-party.mjs +0 -0
- package/bin/unified-cli.mjs +55 -410
- package/commands/continuous-improvement.md +115 -115
- package/commands/dashboard.md +56 -56
- package/commands/model-forward.md +13 -0
- package/commands/production-readiness-review.md +53 -0
- package/commands/seven-laws.md +16 -16
- package/commands/ship.md +57 -0
- package/commands/workspace-surface-audit.md +77 -77
- package/hooks/companion-preference.mjs +31 -19
- package/hooks/gateguard.mjs +38 -16
- package/hooks/goal-drift-stop.mjs +0 -0
- package/hooks/hook-pack.mjs +110 -0
- package/hooks/observe.sh +0 -0
- package/hooks/recall-briefing.mjs +0 -0
- package/hooks/route-prompt.mjs +0 -0
- package/hooks/session.sh +106 -106
- package/hooks/three-section-close.mjs +0 -0
- package/instinct-packs/go.json +58 -58
- package/instinct-packs/meta.json +16 -16
- package/instinct-packs/python.json +58 -58
- package/instinct-packs/react.json +58 -58
- package/lib/cli-anything.mjs +0 -0
- package/lib/gateguard-state.mjs +62 -13
- package/lib/goal-drift-gate.mjs +0 -0
- package/lib/goal-state.mjs +8 -4
- package/lib/hook-pack-gate.mjs +65 -0
- package/lib/install-targets.mjs +121 -0
- package/lib/observe-event.mjs +0 -0
- package/lib/plan-review-packet.mjs +96 -0
- package/lib/plugin-metadata.mjs +23 -2
- package/lib/pm-marketplace.mjs +0 -0
- package/lib/recall-briefing.mjs +0 -0
- package/lib/recall-index.mjs +0 -0
- package/lib/resolve-home-dir.mjs +0 -0
- package/lib/skill-distill.mjs +141 -0
- package/lib/skill-tiers.mjs +0 -0
- package/llms.txt +2 -2
- package/package.json +4 -3
- package/plugins/beginner.json +2 -2
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +2 -2
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +2 -2
- package/plugins/continuous-improvement/LICENSE +21 -21
- package/plugins/continuous-improvement/README.md +1 -1
- package/plugins/continuous-improvement/bin/backfill.mjs +19 -19
- package/plugins/continuous-improvement/bin/gateguard-clear.mjs +0 -0
- package/plugins/continuous-improvement/bin/mcp-server.mjs +65 -8
- package/plugins/continuous-improvement/bin/observe.mjs +0 -0
- package/plugins/continuous-improvement/commands/continuous-improvement.md +115 -115
- package/plugins/continuous-improvement/commands/dashboard.md +56 -56
- package/plugins/continuous-improvement/commands/model-forward.md +13 -0
- package/plugins/continuous-improvement/commands/production-readiness-review.md +53 -0
- package/plugins/continuous-improvement/commands/seven-laws.md +16 -16
- package/plugins/continuous-improvement/commands/ship.md +57 -0
- package/plugins/continuous-improvement/commands/workspace-surface-audit.md +77 -77
- package/plugins/continuous-improvement/hooks/companion-preference.mjs +31 -19
- package/plugins/continuous-improvement/hooks/gateguard.mjs +38 -16
- package/plugins/continuous-improvement/hooks/goal-drift-stop.mjs +0 -0
- package/plugins/continuous-improvement/hooks/hook-pack.mjs +110 -0
- package/plugins/continuous-improvement/hooks/hooks.json +10 -0
- package/plugins/continuous-improvement/hooks/observe.sh +0 -0
- package/plugins/continuous-improvement/hooks/recall-briefing.mjs +0 -0
- package/plugins/continuous-improvement/hooks/route-prompt.mjs +0 -0
- package/plugins/continuous-improvement/hooks/session.sh +106 -106
- package/plugins/continuous-improvement/hooks/three-section-close.mjs +0 -0
- package/plugins/continuous-improvement/instinct-packs/go.json +58 -58
- package/plugins/continuous-improvement/instinct-packs/meta.json +16 -16
- package/plugins/continuous-improvement/instinct-packs/python.json +58 -58
- package/plugins/continuous-improvement/instinct-packs/react.json +58 -58
- package/plugins/continuous-improvement/lib/gateguard-state.mjs +62 -13
- package/plugins/continuous-improvement/lib/goal-drift-gate.mjs +0 -0
- package/plugins/continuous-improvement/lib/goal-state.mjs +8 -4
- package/plugins/continuous-improvement/lib/hook-pack-gate.mjs +65 -0
- package/plugins/continuous-improvement/lib/observe-event.mjs +0 -0
- package/plugins/continuous-improvement/lib/plugin-metadata.mjs +23 -2
- package/plugins/continuous-improvement/lib/recall-briefing.mjs +0 -0
- package/plugins/continuous-improvement/lib/recall-index.mjs +0 -0
- package/plugins/continuous-improvement/lib/resolve-home-dir.mjs +0 -0
- package/plugins/continuous-improvement/lib/skill-distill.mjs +141 -0
- package/plugins/continuous-improvement/skills/README.md +1 -1
- package/plugins/continuous-improvement/skills/gateguard/SKILL.md +4 -4
- package/plugins/continuous-improvement/skills/goal-monitor/SKILL.md +1 -1
- package/plugins/continuous-improvement/skills/handoff/SKILL.md +0 -1
- package/plugins/continuous-improvement/skills/model-forward/SKILL.md +44 -0
- package/plugins/continuous-improvement/skills/tdd-workflow/SKILL.md +411 -411
- package/plugins/expert.json +5 -1
- package/skills/README.md +4 -2
- package/skills/gateguard.md +4 -4
- package/skills/goal-monitor.md +1 -1
- package/skills/handoff.md +0 -1
- package/skills/model-forward.md +44 -0
- package/skills/tdd-workflow.md +411 -411
- package/lib/compound-engineering.mjs +0 -831
- package/lib/pm-skills.mjs +0 -1274
- package/lib/unified-plugin.mjs +0 -924
- package/plugins/continuous-improvement/skills/para-memory-files/SKILL.md +0 -108
- package/skills/para-memory-files.md +0 -108
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: workspace-surface-audit
|
|
3
|
-
description: "Audit workspace capabilities and recommend continuous-improvement-native skills, hooks, and workflows"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# /workspace-surface-audit
|
|
7
|
-
|
|
8
|
-
Audit the active repo, MCP servers, plugins, connectors, env surfaces, and harness setup. Returns a 5-section report with specific next moves.
|
|
9
|
-
|
|
10
|
-
## Usage
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
/workspace-surface-audit
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## What It Checks
|
|
17
|
-
|
|
18
|
-
1. **Repo surface** — `package.json`, lockfiles, framework config, `.mcp.json`, `.claude/settings*.json`, `AGENTS.md`
|
|
19
|
-
2. **Environment surface** — `.env*` files (key names only, no secrets)
|
|
20
|
-
3. **Connected tools** — installed plugins, MCP servers, LSPs, app integrations
|
|
21
|
-
4. **continuous-improvement surface** — existing skills, commands, hooks, agents
|
|
22
|
-
|
|
23
|
-
## Output Format
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
=== Workspace Surface Audit ===
|
|
27
|
-
|
|
28
|
-
## Current Surface
|
|
29
|
-
- [What is usable right now]
|
|
30
|
-
|
|
31
|
-
## Parity
|
|
32
|
-
- [Where continuous-improvement matches or exceeds benchmarks]
|
|
33
|
-
|
|
34
|
-
## Primitive-Only Gaps
|
|
35
|
-
- [Tools exist, but continuous-improvement lacks clean operator skills]
|
|
36
|
-
|
|
37
|
-
## Missing Integrations
|
|
38
|
-
- [Capabilities not available yet]
|
|
39
|
-
|
|
40
|
-
## Top 3-5 Next Moves
|
|
41
|
-
1. [Concrete continuous-improvement-native addition]
|
|
42
|
-
2. [Ordered by impact]
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Rules
|
|
46
|
-
|
|
47
|
-
- **Never print secrets** — only provider names, capability names, file paths
|
|
48
|
-
- **Prefer continuous-improvement** over generic "install another plugin" advice
|
|
49
|
-
- **Organize by workflows**, not API brands
|
|
50
|
-
- **Specific recommendations** — concrete enough to implement without another discovery pass
|
|
51
|
-
|
|
52
|
-
## Example Output
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
## Current Surface
|
|
56
|
-
- MCP: filesystem, github, playwright
|
|
57
|
-
- Plugins: beginner, expert (continuous-improvement)
|
|
58
|
-
- Env: STRIPE_API_KEY, FAL_KEY configured
|
|
59
|
-
- Framework: Next.js 15, React 19, TypeScript
|
|
60
|
-
|
|
61
|
-
## Parity
|
|
62
|
-
- Continuous improvement: covered (native)
|
|
63
|
-
- Testing discipline: covered (TDD workflows)
|
|
64
|
-
|
|
65
|
-
## Primitive-Only Gaps
|
|
66
|
-
- Stripe connected but no billing-operator skill
|
|
67
|
-
- GitHub MCP available but no issue-triage workflow
|
|
68
|
-
|
|
69
|
-
## Missing Integrations
|
|
70
|
-
- No Linear connector detected
|
|
71
|
-
- No Slack webhook configured
|
|
72
|
-
|
|
73
|
-
## Top 3-5 Next Moves
|
|
74
|
-
1. Create billing-operator skill (Stripe integration)
|
|
75
|
-
2. Create github-ops workflow for issue triage
|
|
76
|
-
3. Add Linear connector skill for project management
|
|
77
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: workspace-surface-audit
|
|
3
|
+
description: "Audit workspace capabilities and recommend continuous-improvement-native skills, hooks, and workflows"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /workspace-surface-audit
|
|
7
|
+
|
|
8
|
+
Audit the active repo, MCP servers, plugins, connectors, env surfaces, and harness setup. Returns a 5-section report with specific next moves.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
/workspace-surface-audit
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## What It Checks
|
|
17
|
+
|
|
18
|
+
1. **Repo surface** — `package.json`, lockfiles, framework config, `.mcp.json`, `.claude/settings*.json`, `AGENTS.md`
|
|
19
|
+
2. **Environment surface** — `.env*` files (key names only, no secrets)
|
|
20
|
+
3. **Connected tools** — installed plugins, MCP servers, LSPs, app integrations
|
|
21
|
+
4. **continuous-improvement surface** — existing skills, commands, hooks, agents
|
|
22
|
+
|
|
23
|
+
## Output Format
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
=== Workspace Surface Audit ===
|
|
27
|
+
|
|
28
|
+
## Current Surface
|
|
29
|
+
- [What is usable right now]
|
|
30
|
+
|
|
31
|
+
## Parity
|
|
32
|
+
- [Where continuous-improvement matches or exceeds benchmarks]
|
|
33
|
+
|
|
34
|
+
## Primitive-Only Gaps
|
|
35
|
+
- [Tools exist, but continuous-improvement lacks clean operator skills]
|
|
36
|
+
|
|
37
|
+
## Missing Integrations
|
|
38
|
+
- [Capabilities not available yet]
|
|
39
|
+
|
|
40
|
+
## Top 3-5 Next Moves
|
|
41
|
+
1. [Concrete continuous-improvement-native addition]
|
|
42
|
+
2. [Ordered by impact]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Rules
|
|
46
|
+
|
|
47
|
+
- **Never print secrets** — only provider names, capability names, file paths
|
|
48
|
+
- **Prefer continuous-improvement** over generic "install another plugin" advice
|
|
49
|
+
- **Organize by workflows**, not API brands
|
|
50
|
+
- **Specific recommendations** — concrete enough to implement without another discovery pass
|
|
51
|
+
|
|
52
|
+
## Example Output
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
## Current Surface
|
|
56
|
+
- MCP: filesystem, github, playwright
|
|
57
|
+
- Plugins: beginner, expert (continuous-improvement)
|
|
58
|
+
- Env: STRIPE_API_KEY, FAL_KEY configured
|
|
59
|
+
- Framework: Next.js 15, React 19, TypeScript
|
|
60
|
+
|
|
61
|
+
## Parity
|
|
62
|
+
- Continuous improvement: covered (native)
|
|
63
|
+
- Testing discipline: covered (TDD workflows)
|
|
64
|
+
|
|
65
|
+
## Primitive-Only Gaps
|
|
66
|
+
- Stripe connected but no billing-operator skill
|
|
67
|
+
- GitHub MCP available but no issue-triage workflow
|
|
68
|
+
|
|
69
|
+
## Missing Integrations
|
|
70
|
+
- No Linear connector detected
|
|
71
|
+
- No Slack webhook configured
|
|
72
|
+
|
|
73
|
+
## Top 3-5 Next Moves
|
|
74
|
+
1. Create billing-operator skill (Stripe integration)
|
|
75
|
+
2. Create github-ops workflow for issue triage
|
|
76
|
+
3. Add Linear connector skill for project management
|
|
77
|
+
```
|
|
@@ -12,11 +12,17 @@
|
|
|
12
12
|
* (when installed) or the install hint (when not).
|
|
13
13
|
*
|
|
14
14
|
* Fail-open: any unexpected error reading stdin / parsing settings / probing
|
|
15
|
-
* the filesystem →
|
|
16
|
-
*
|
|
15
|
+
* the filesystem → exit 0 with empty stdout (allow). The hook never blocks
|
|
16
|
+
* on its own bugs.
|
|
17
17
|
*
|
|
18
18
|
* Stdin : JSON { tool_name, tool_input }
|
|
19
|
-
* Stdout :
|
|
19
|
+
* Stdout : empty on allow (no output = no opinion, the call proceeds); on
|
|
20
|
+
* deny, the documented PreToolUse shape:
|
|
21
|
+
* { hookSpecificOutput: { hookEventName: "PreToolUse",
|
|
22
|
+
* permissionDecision: "deny", permissionDecisionReason } }
|
|
23
|
+
* A bare { decision: "allow" } is not schema-valid for PreToolUse
|
|
24
|
+
* (the deprecated enum is "approve" | "block") and surfaces as a
|
|
25
|
+
* "Hook JSON output validation failed" error on every tool call.
|
|
20
26
|
* Exit : 0 always — decision lives in stdout, never in the exit code.
|
|
21
27
|
*
|
|
22
28
|
* OVERRIDES map must stay row-aligned with skills/superpowers.md
|
|
@@ -50,8 +56,20 @@ const OVERRIDES = {
|
|
|
50
56
|
plugin: "oh-my-claudecode",
|
|
51
57
|
},
|
|
52
58
|
};
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
// Allow = empty stdout + exit 0. Emitting JSON here is wrong: PreToolUse has
|
|
60
|
+
// no { decision: "allow" } shape, and the invalid output surfaces as a
|
|
61
|
+
// per-tool-call "Hook JSON output validation failed" error in the client.
|
|
62
|
+
function emitAllowAndExit() {
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
function emitDenyAndExit(reason) {
|
|
66
|
+
process.stdout.write(`${JSON.stringify({
|
|
67
|
+
hookSpecificOutput: {
|
|
68
|
+
hookEventName: "PreToolUse",
|
|
69
|
+
permissionDecision: "deny",
|
|
70
|
+
permissionDecisionReason: reason,
|
|
71
|
+
},
|
|
72
|
+
})}\n`);
|
|
55
73
|
process.exit(0);
|
|
56
74
|
}
|
|
57
75
|
function readStdin() {
|
|
@@ -137,19 +155,19 @@ function main() {
|
|
|
137
155
|
payload = JSON.parse(raw);
|
|
138
156
|
}
|
|
139
157
|
catch {
|
|
140
|
-
|
|
158
|
+
emitAllowAndExit();
|
|
141
159
|
}
|
|
142
160
|
if (payload.tool_name !== "Skill") {
|
|
143
|
-
|
|
161
|
+
emitAllowAndExit();
|
|
144
162
|
}
|
|
145
163
|
const skill = payload.tool_input?.skill;
|
|
146
164
|
if (typeof skill !== "string" || skill.length === 0) {
|
|
147
|
-
|
|
165
|
+
emitAllowAndExit();
|
|
148
166
|
}
|
|
149
167
|
const normalized = normalizeSkill(skill);
|
|
150
168
|
const override = OVERRIDES[normalized];
|
|
151
169
|
if (!override) {
|
|
152
|
-
|
|
170
|
+
emitAllowAndExit();
|
|
153
171
|
}
|
|
154
172
|
const home = resolveHome();
|
|
155
173
|
const mode = readMode(home);
|
|
@@ -166,25 +184,19 @@ function main() {
|
|
|
166
184
|
// Shadow row: what companions-first would have done. This is the data
|
|
167
185
|
// set that earns a future default-flip decision.
|
|
168
186
|
writeTelemetry(home, { ...baseEvent, mode, action: "observation" });
|
|
169
|
-
|
|
187
|
+
emitAllowAndExit();
|
|
170
188
|
}
|
|
171
189
|
if (mode === "companions-first") {
|
|
172
190
|
process.stderr.write(`[continuous-improvement] companion_preference=companions-first → prefer \`${override.companion}\` over \`ci:${normalized}\`.\n`);
|
|
173
191
|
writeTelemetry(home, { ...baseEvent, mode, action: "advisory" });
|
|
174
|
-
|
|
192
|
+
emitAllowAndExit();
|
|
175
193
|
}
|
|
176
194
|
// strict-companions: always block; reason depends on install state.
|
|
177
195
|
if (installed) {
|
|
178
196
|
writeTelemetry(home, { ...baseEvent, mode, action: "block" });
|
|
179
|
-
|
|
180
|
-
decision: "block",
|
|
181
|
-
reason: `companion_preference=strict-companions: route to \`${override.companion}\` instead of \`ci:${normalized}\`. The CI fallback is suppressed by your setting.`,
|
|
182
|
-
});
|
|
197
|
+
emitDenyAndExit(`companion_preference=strict-companions: route to \`${override.companion}\` instead of \`ci:${normalized}\`. The CI fallback is suppressed by your setting.`);
|
|
183
198
|
}
|
|
184
199
|
writeTelemetry(home, { ...baseEvent, mode, action: "block-not-installed" });
|
|
185
|
-
|
|
186
|
-
decision: "block",
|
|
187
|
-
reason: `companion_preference=strict-companions: companion plugin \`${override.plugin}\` is not installed. Install with \`/plugin install ${override.plugin}@continuous-improvement\` or relax the setting to \`companions-first\` or \`ci-first\` in ~/.claude/settings.json.`,
|
|
188
|
-
});
|
|
200
|
+
emitDenyAndExit(`companion_preference=strict-companions: companion plugin \`${override.plugin}\` is not installed. Install with \`/plugin install ${override.plugin}@continuous-improvement\` or relax the setting to \`companions-first\` or \`ci-first\` in ~/.claude/settings.json.`);
|
|
189
201
|
}
|
|
190
202
|
main();
|
package/hooks/gateguard.mjs
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
* Runtime PreToolUse gateguard hook.
|
|
4
4
|
*
|
|
5
5
|
* Stdin : JSON { tool_name, tool_input }
|
|
6
|
-
* Stdout :
|
|
6
|
+
* Stdout : empty on allow (no output = no opinion, the call proceeds); on
|
|
7
|
+
* deny, the documented PreToolUse shape:
|
|
8
|
+
* { hookSpecificOutput: { hookEventName: "PreToolUse",
|
|
9
|
+
* permissionDecision: "deny", permissionDecisionReason } }
|
|
10
|
+
* A bare { decision: "allow" } is not schema-valid for PreToolUse
|
|
11
|
+
* (the deprecated enum is "approve" | "block") and surfaces as a
|
|
12
|
+
* "Hook JSON output validation failed" error on every tool call.
|
|
7
13
|
* Exit : 0 always (decision is in stdout, fail-open on parse error).
|
|
8
14
|
*
|
|
9
15
|
* Three-stage gate per skills/gateguard.md:
|
|
@@ -132,7 +138,7 @@ function buildMutatingFileReason(toolName, filePaths, stateFilePath) {
|
|
|
132
138
|
"",
|
|
133
139
|
"Then clear the gate and retry the same call. Either route works:",
|
|
134
140
|
` A. Bash (always works — run verbatim): node ${quotePath(CLEAR_CLI_PATH)} --state ${quotePath(stateFilePath)} ${quoted.join(" ")}`,
|
|
135
|
-
` B. MCP tool (when the continuous-improvement server is connected): ci_gateguard_clear {file_paths: [${quoted.join(", ")}]}`,
|
|
141
|
+
` B. MCP tool (when the continuous-improvement server is connected): ci_gateguard_clear {file_paths: [${quoted.join(", ")}], state_path: ${quotePath(stateFilePath)}}`,
|
|
136
142
|
" Both canonicalize paths — drive-letter case and separators don't matter.",
|
|
137
143
|
" (Harnesses that forward unknown tool params may instead retry the call with",
|
|
138
144
|
" `_gateguard_facts_presented: true`; Claude Code's strict schema rejects that, so use A or B.)",
|
|
@@ -151,13 +157,26 @@ function buildDestructiveBashReason(command) {
|
|
|
151
157
|
}
|
|
152
158
|
function buildCapReachedReason() {
|
|
153
159
|
return [
|
|
154
|
-
`Gateguard
|
|
155
|
-
"
|
|
156
|
-
"stuck-loop or rogue-agent clearance
|
|
160
|
+
`Gateguard clearance cap reached (${MAX_CLEARED_FILES} distinct files this session).`,
|
|
161
|
+
"The cap is per-session: start a new Claude Code session to reset it, or wait",
|
|
162
|
+
"for the state file to self-heal. It bounds stuck-loop or rogue-agent clearance",
|
|
163
|
+
"from compounding within one session without affecting your other sessions.",
|
|
157
164
|
].join("\n");
|
|
158
165
|
}
|
|
159
|
-
|
|
160
|
-
|
|
166
|
+
// Allow = empty stdout + exit 0. Emitting JSON here is wrong: PreToolUse has
|
|
167
|
+
// no { decision: "allow" } shape, and the invalid output surfaces as a
|
|
168
|
+
// per-tool-call "Hook JSON output validation failed" error in the client.
|
|
169
|
+
function emitAllow() {
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
function emitDeny(reason) {
|
|
173
|
+
process.stdout.write(`${JSON.stringify({
|
|
174
|
+
hookSpecificOutput: {
|
|
175
|
+
hookEventName: "PreToolUse",
|
|
176
|
+
permissionDecision: "deny",
|
|
177
|
+
permissionDecisionReason: reason,
|
|
178
|
+
},
|
|
179
|
+
})}\n`);
|
|
161
180
|
process.exit(0);
|
|
162
181
|
}
|
|
163
182
|
function main() {
|
|
@@ -166,7 +185,7 @@ function main() {
|
|
|
166
185
|
raw = readFileSync(0, "utf8");
|
|
167
186
|
}
|
|
168
187
|
catch {
|
|
169
|
-
|
|
188
|
+
emitAllow();
|
|
170
189
|
return;
|
|
171
190
|
}
|
|
172
191
|
let payload;
|
|
@@ -174,23 +193,26 @@ function main() {
|
|
|
174
193
|
payload = JSON.parse(raw);
|
|
175
194
|
}
|
|
176
195
|
catch {
|
|
177
|
-
|
|
196
|
+
emitAllow(); // fail-open
|
|
178
197
|
return;
|
|
179
198
|
}
|
|
180
199
|
const toolName = typeof payload.tool_name === "string" ? payload.tool_name : "";
|
|
181
200
|
const toolInput = payload.tool_input ?? {};
|
|
182
201
|
const gate = classifyTool(toolName, toolInput);
|
|
183
202
|
if (gate === "allow") {
|
|
184
|
-
|
|
203
|
+
emitAllow();
|
|
185
204
|
return;
|
|
186
205
|
}
|
|
187
206
|
if (gate === "destructive-bash") {
|
|
188
207
|
const cmd = typeof toolInput.command === "string" ? toolInput.command : "";
|
|
189
|
-
|
|
208
|
+
emitDeny(buildDestructiveBashReason(cmd));
|
|
190
209
|
return;
|
|
191
210
|
}
|
|
192
|
-
// mutating-file
|
|
193
|
-
|
|
211
|
+
// mutating-file — scope state to THIS session so the clearance cap never
|
|
212
|
+
// bleeds across concurrent same-day sessions. session_id is the standard hook
|
|
213
|
+
// stdin field (see recall-briefing / observe-event); absent → legacy dir.
|
|
214
|
+
const sessionId = typeof payload.session_id === "string" ? payload.session_id : undefined;
|
|
215
|
+
const sessionDir = resolveSessionDir(sessionId);
|
|
194
216
|
const stateFilePath = join(sessionDir, "gateguard-session.json");
|
|
195
217
|
const state = loadState(sessionDir);
|
|
196
218
|
const filePaths = extractFilePaths(toolInput);
|
|
@@ -199,12 +221,12 @@ function main() {
|
|
|
199
221
|
const alreadyCleared = filePaths.length > 0 && filePaths.every((path) => isFileCleared(state, path));
|
|
200
222
|
const newFileCount = countDistinctNewFilePaths(filePaths, state);
|
|
201
223
|
if (!factsFlagged && !alreadyCleared) {
|
|
202
|
-
|
|
224
|
+
emitDeny(buildMutatingFileReason(toolName, filePaths.length > 0 ? filePaths : [filePath], stateFilePath));
|
|
203
225
|
return;
|
|
204
226
|
}
|
|
205
227
|
if (factsFlagged && !alreadyCleared) {
|
|
206
228
|
if (isCapReached(state) || Object.keys(state.cleared_files).length + newFileCount > MAX_CLEARED_FILES) {
|
|
207
|
-
|
|
229
|
+
emitDeny(buildCapReachedReason());
|
|
208
230
|
return;
|
|
209
231
|
}
|
|
210
232
|
let nextState = state;
|
|
@@ -214,6 +236,6 @@ function main() {
|
|
|
214
236
|
if (filePaths.length > 0)
|
|
215
237
|
saveState(sessionDir, nextState);
|
|
216
238
|
}
|
|
217
|
-
|
|
239
|
+
emitAllow();
|
|
218
240
|
}
|
|
219
241
|
main();
|
|
File without changes
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Runtime PreToolUse enforcing-hook pack (v1).
|
|
4
|
+
*
|
|
5
|
+
* Stdin : JSON { tool_name, tool_input: { command } }
|
|
6
|
+
* Stdout : empty on allow / warn (warn prints to stderr); on block, the
|
|
7
|
+
* documented PreToolUse shape:
|
|
8
|
+
* { hookSpecificOutput: { hookEventName: "PreToolUse",
|
|
9
|
+
* permissionDecision: "deny", permissionDecisionReason } }
|
|
10
|
+
* A bare { decision: "allow" } is not schema-valid for PreToolUse.
|
|
11
|
+
* Exit : 0 always (decision is in stdout; fail-open on any error).
|
|
12
|
+
*
|
|
13
|
+
* Two gates over Bash git commands (pure logic in ../lib/hook-pack-gate.mjs):
|
|
14
|
+
* - push-to-main : a direct `git push` to main/master/release-*
|
|
15
|
+
* - commit-size : a `git commit` staging more than COMMIT_FILE_LIMIT files
|
|
16
|
+
*
|
|
17
|
+
* Mode via CLAUDE_CI_HOOKPACK_GATE: "warn" (default) | "block" | "off".
|
|
18
|
+
* warn : print a one-line notice to stderr; never blocks.
|
|
19
|
+
* block : emit the PreToolUse deny shape.
|
|
20
|
+
* off : no-op.
|
|
21
|
+
*
|
|
22
|
+
* Only `git push` / `git commit` commands spawn a git subprocess; every other
|
|
23
|
+
* Bash command and non-Bash tool returns immediately. No network. Fail-open.
|
|
24
|
+
*/
|
|
25
|
+
import { execFileSync } from "node:child_process";
|
|
26
|
+
import { readFileSync } from "node:fs";
|
|
27
|
+
import { COMMIT_FILE_LIMIT, classifyCommand, decide, evaluateCommitSize, evaluatePush, parseMode, } from "../lib/hook-pack-gate.mjs";
|
|
28
|
+
function readStdinSync() {
|
|
29
|
+
try {
|
|
30
|
+
return readFileSync(0, "utf8");
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function safeJsonParse(text) {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(text);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function gitCurrentBranch() {
|
|
45
|
+
try {
|
|
46
|
+
const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
47
|
+
encoding: "utf8",
|
|
48
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
49
|
+
}).trim();
|
|
50
|
+
return branch || null;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function gitStagedCount() {
|
|
57
|
+
try {
|
|
58
|
+
const out = execFileSync("git", ["diff", "--cached", "--name-only"], {
|
|
59
|
+
encoding: "utf8",
|
|
60
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
61
|
+
});
|
|
62
|
+
return out.split(/\r?\n/).filter(Boolean).length;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const ALLOW = { action: "allow", reason: "" };
|
|
69
|
+
function compute() {
|
|
70
|
+
const mode = parseMode(process.env.CLAUDE_CI_HOOKPACK_GATE);
|
|
71
|
+
if (mode === "off")
|
|
72
|
+
return ALLOW;
|
|
73
|
+
const payload = safeJsonParse(readStdinSync());
|
|
74
|
+
if (!payload || typeof payload !== "object")
|
|
75
|
+
return ALLOW;
|
|
76
|
+
const obj = payload;
|
|
77
|
+
if (obj.tool_name !== "Bash")
|
|
78
|
+
return ALLOW;
|
|
79
|
+
const input = (obj.tool_input && typeof obj.tool_input === "object"
|
|
80
|
+
? obj.tool_input
|
|
81
|
+
: {});
|
|
82
|
+
const command = typeof input.command === "string" ? input.command : "";
|
|
83
|
+
const kind = classifyCommand(command);
|
|
84
|
+
if (kind === "push") {
|
|
85
|
+
const res = evaluatePush(command, gitCurrentBranch());
|
|
86
|
+
return decide(mode, res.gated, `Direct push to protected branch "${res.branch}" — open a feature branch + PR instead.`);
|
|
87
|
+
}
|
|
88
|
+
if (kind === "commit") {
|
|
89
|
+
const res = evaluateCommitSize(command, gitStagedCount());
|
|
90
|
+
return decide(mode, res.gated, `This commit stages more than ${COMMIT_FILE_LIMIT} files — exceeds the one-concern limit. Split by layer, or set CLAUDE_CI_HOOKPACK_GATE=off for genuinely generated bulk.`);
|
|
91
|
+
}
|
|
92
|
+
return ALLOW;
|
|
93
|
+
}
|
|
94
|
+
function main() {
|
|
95
|
+
const decision = compute();
|
|
96
|
+
if (decision.action === "block") {
|
|
97
|
+
process.stdout.write(JSON.stringify({
|
|
98
|
+
hookSpecificOutput: {
|
|
99
|
+
hookEventName: "PreToolUse",
|
|
100
|
+
permissionDecision: "deny",
|
|
101
|
+
permissionDecisionReason: decision.reason,
|
|
102
|
+
},
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
else if (decision.action === "warn") {
|
|
106
|
+
process.stderr.write(`[hook-pack] ${decision.reason}\n`);
|
|
107
|
+
}
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
main();
|
package/hooks/observe.sh
CHANGED
|
File without changes
|
|
File without changes
|
package/hooks/route-prompt.mjs
CHANGED
|
File without changes
|