@event4u/agent-config 9.24.0 → 9.26.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 +1 -1
- package/CHANGELOG.md +136 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/conformance.md +182 -0
- package/dist/agent-src/commands/analyze/inbox.md +33 -2
- package/dist/agent-src/commands/review/changes.md +6 -5
- package/dist/agent-src/commands/roadmap/next.md +194 -0
- package/dist/agent-src/commands/roadmap.md +12 -4
- package/dist/agent-src/contexts/execution/subagent-routing.md +4 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +2 -0
- package/dist/agent-src/rules/delegation-policy.md +3 -2
- package/dist/agent-src/rules/design-fidelity.md +17 -1
- package/dist/agent-src/rules/design-review-after-ui-write.md +116 -0
- package/dist/agent-src/rules/evaluator-independence.md +111 -0
- package/dist/agent-src/rules/session-canary.md +15 -1
- package/dist/agent-src/rules/settings-ask-protocol.md +176 -0
- package/dist/agent-src/rules/token-budget-discipline.md +6 -3
- package/dist/agent-src/rules/ui-audit-gate.md +3 -0
- package/dist/agent-src/rules/user-interaction.md +23 -0
- package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +1 -1
- package/dist/agent-src/skills/recursive-verification/SKILL.md +13 -0
- package/dist/agent-src/skills/rule-writing/SKILL.md +45 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +114 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/cmd_route_audit.js +4 -1
- package/dist/cli-delegate/cmd_route_explain.js +4 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +146 -28
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +5 -5
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +16 -11
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +12 -7
- package/dist/hooks/dispatch.js +1501 -511
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/router.json +1 -1
- package/dist/server/nicknameResolver.js +116 -0
- package/dist/server/nicknameResolver.js.map +1 -0
- package/dist/server/routes/ping.js +29 -11
- package/dist/server/routes/ping.js.map +1 -1
- package/dist/shared/interactiveContext.js +73 -0
- package/dist/shared/interactiveContext.js.map +1 -0
- package/dist/shared/nicknamePrefill.js +59 -0
- package/dist/shared/nicknamePrefill.js.map +1 -0
- package/dist/shared/settingsAsks.js +128 -0
- package/dist/shared/settingsAsks.js.map +1 -0
- package/dist/shared/settingsConsent.js +93 -0
- package/dist/shared/settingsConsent.js.map +1 -0
- package/package.json +1 -1
- package/src/config/agents-paths.yml +15 -0
- package/src/config/evaluator-budgets.json +68 -68
- package/src/config/gate-coverage.yml +131 -5
- package/src/config/gate-violation-baselines.json +11 -0
- package/src/scripts/_cli/cmd_route_audit.ts +4 -1
- package/src/scripts/_cli/cmd_route_explain.ts +9 -1
- package/src/scripts/_dispatch.bash +12 -0
- package/src/scripts/ai-video/stitch.sh +27 -9
- package/src/scripts/before_complete_hook.ts +146 -5
- package/src/scripts/bench_ab_v2_run.ts +272 -16
- package/src/scripts/bench_ab_v2_stats.ts +156 -3
- package/src/scripts/check_completion_review.ts +80 -3
- package/src/scripts/check_corpus_staleness.ts +692 -0
- package/src/scripts/check_depth_budget.ts +295 -0
- package/src/scripts/check_gate_coverage.ts +67 -0
- package/src/scripts/check_hook_bundle_freshness.ts +183 -0
- package/src/scripts/conformance_scan.ts +422 -0
- package/src/scripts/dispatch_r2_reviewer.ts +31 -2
- package/src/scripts/git_authorization_hook.ts +328 -0
- package/src/scripts/hook_manifest.yaml +69 -9
- package/src/scripts/hooks/block_no_verify.ts +3 -1
- package/src/scripts/hooks/block_unauthorized_git.ts +371 -0
- package/src/scripts/hooks/concern_registry.ts +8 -0
- package/src/scripts/hooks/design_slop_hook.ts +13 -1
- package/src/scripts/hooks/dispatch_hook.ts +97 -2
- package/src/scripts/hooks/evidence_independence.ts +368 -0
- package/src/scripts/language_mirror_hook.ts +456 -0
- package/src/scripts/lint_abstraction_thresholds.ts +41 -8
- package/src/scripts/lint_agents_layout.ts +2 -0
- package/src/scripts/lint_decision_impact.ts +246 -0
- package/src/scripts/lint_example_fences.ts +609 -0
- package/src/scripts/lint_hook_manifest.ts +29 -0
- package/src/scripts/lint_rule_interactions.ts +90 -0
- package/src/scripts/lint_rule_migration_ledger.ts +480 -0
- package/src/scripts/lint_skill_descriptions.ts +86 -2
- package/src/scripts/memory_learn_hook.ts +109 -6
- package/src/scripts/pr_url_reminder_hook.ts +17 -2
- package/src/scripts/release.ts +38 -1
- package/src/scripts/schemas/review-findings.schema.json +99 -0
- package/src/scripts/schemas/rule.schema.json +10 -0
- package/src/shared/interactiveContext.ts +98 -0
- package/src/shared/nicknamePrefill.ts +96 -0
- package/src/shared/settingsAsks.ts +176 -0
- package/src/shared/settingsConsent.ts +136 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Git-authorization ledger — `user_prompt_submit` concern.
|
|
4
|
+
*
|
|
5
|
+
* Records which git operations the user's OWN WORDS authorize this turn, so
|
|
6
|
+
* `hooks/block_unauthorized_git.ts` can check an operation against a fact
|
|
7
|
+
* instead of against the model's recollection of the conversation.
|
|
8
|
+
*
|
|
9
|
+
* WHY — the measured defect (30-session conformance audit, 2026-08-06):
|
|
10
|
+
*
|
|
11
|
+
* - A full release chain (prod-trunk merge, tag push, GitHub release, npm
|
|
12
|
+
* publish) executed after the agent itself wrote "das ist ein Hard-Floor-
|
|
13
|
+
* Schritt, dafür brauche ich Dein explizites Go" and never received one —
|
|
14
|
+
* the user's next turn was a pasted `git push … rejected` stack trace,
|
|
15
|
+
* which was read as implicit continuation.
|
|
16
|
+
* - Two complete PRs opened on turns carrying no git authorization at all.
|
|
17
|
+
* - Force-push over a bot's commits off a bare "fixe auch diese 4 pr's".
|
|
18
|
+
* - Twice the constraint was named in writing and contradicted minutes later.
|
|
19
|
+
*
|
|
20
|
+
* `commit-policy` and `scope-control` already forbid all of this in prose, and
|
|
21
|
+
* the prose was in context every time. The ledger is the missing fact, not a
|
|
22
|
+
* new obligation.
|
|
23
|
+
*
|
|
24
|
+
* State: `agents/state/git-authorization.json`
|
|
25
|
+
* { "session_id": str, "detected_at": iso8601, "authorized": [op, …],
|
|
26
|
+
* "evidence": { op: "phrase" }, "prompt_chars": int }
|
|
27
|
+
*
|
|
28
|
+
* Each user turn REPLACES the ledger — that is the point. `commit-policy`'s
|
|
29
|
+
* "one-shot authorization is not a standing license" says an authorization is
|
|
30
|
+
* spent on the operation it named; carrying it forward is the exact inference
|
|
31
|
+
* the rule forbids, so a new prompt with no authorization phrase yields an
|
|
32
|
+
* empty ledger rather than an inherited one.
|
|
33
|
+
*
|
|
34
|
+
* Never blocks. Exit 0 always. The blocking half lives in the pre_tool_use
|
|
35
|
+
* concern, which is where the operation is actually observable.
|
|
36
|
+
*/
|
|
37
|
+
import fs from "node:fs";
|
|
38
|
+
import path from "node:path";
|
|
39
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
40
|
+
|
|
41
|
+
import { atomic_write_json } from "./hooks/state_io.js";
|
|
42
|
+
import { readHookStdin } from "./hooks/hook_stdin.js";
|
|
43
|
+
|
|
44
|
+
const EXIT_ALLOW = 0;
|
|
45
|
+
|
|
46
|
+
export const STATE_FILE = path.join("agents", "state", "git-authorization.json");
|
|
47
|
+
|
|
48
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
49
|
+
type JsonObject = { [k: string]: JsonValue };
|
|
50
|
+
|
|
51
|
+
function _isObject(v: unknown): v is JsonObject {
|
|
52
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The operations the ledger can authorize. */
|
|
56
|
+
export type GitOp =
|
|
57
|
+
| "commit"
|
|
58
|
+
| "push"
|
|
59
|
+
| "branch"
|
|
60
|
+
| "pr-create"
|
|
61
|
+
| "pr-merge"
|
|
62
|
+
| "tag"
|
|
63
|
+
| "release"
|
|
64
|
+
| "publish";
|
|
65
|
+
|
|
66
|
+
export const ALL_OPS: readonly GitOp[] = [
|
|
67
|
+
"commit",
|
|
68
|
+
"push",
|
|
69
|
+
"branch",
|
|
70
|
+
"pr-create",
|
|
71
|
+
"pr-merge",
|
|
72
|
+
"tag",
|
|
73
|
+
"release",
|
|
74
|
+
"publish",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Prose phrases, German and English, that authorize an operation.
|
|
79
|
+
*
|
|
80
|
+
* Deliberately a keyword list rather than an LLM judge: the classifier runs on
|
|
81
|
+
* every prompt and must be cheap, deterministic and reviewable. A miss costs a
|
|
82
|
+
* warn (or, on the irreversible subset, one confirmation); an LLM judge would
|
|
83
|
+
* cost latency on every turn and be unauditable.
|
|
84
|
+
*/
|
|
85
|
+
const PHRASES: ReadonlyArray<{ op: GitOp; re: RegExp }> = [
|
|
86
|
+
{ op: "commit", re: /\b(commit(e|et|te|ten)?|committe|einchecken)\b/i },
|
|
87
|
+
{ op: "push", re: /\b(push(e|en|t)?|hochladen|hochschieben|raufschieben)\b/i },
|
|
88
|
+
{ op: "branch", re: /\b(branch|feature-branch|zweig)\b/i },
|
|
89
|
+
// A creation verb is required — "schau dir den PR an" is not "open a PR".
|
|
90
|
+
{
|
|
91
|
+
op: "pr-create",
|
|
92
|
+
re: /\b(erstell(e|en)?|mach(e|en)?|leg(e|en)?\s+an|(er)?(ö|oe)ffne|open|create|raise|aufmachen)\b[^.\n]{0,30}\b(pr|pull[- ]request)\b|\b(pr|pull[- ]request)\b[^.\n]{0,20}\b(erstellen|aufmachen|anlegen|(er)?(ö|oe)ffnen)\b/i,
|
|
93
|
+
},
|
|
94
|
+
// `merge` as an ACTION, never as the noun in "merge conflict" / "merge commit".
|
|
95
|
+
{ op: "pr-merge", re: /\b(merge|merg(e|en|st|t)|zusammenf(ü|ue)hren|reinmergen)\b(?!\s*[- ]?(conflict|konflikt|commit|base|queue|state|status))/i },
|
|
96
|
+
// A tag is an ACTION here — bare "Tag" is the German word for day, and
|
|
97
|
+
// "Version" is an ordinary noun. Both authorized a BLOCK op before this.
|
|
98
|
+
{ op: "tag", re: /\b(tagge(n|st)?|tag\s+(setzen|anlegen|erstellen)|git\s+tag|--tags|--follow-tags)\b/i },
|
|
99
|
+
// "die release notes sind falsch" is a noun phrase, not an authorization.
|
|
100
|
+
{
|
|
101
|
+
op: "release",
|
|
102
|
+
re: /\b(releasen?|ver(ö|oe)ffentlich(e|en))\b(?!\s*[- ]?(notes?|branch|candidate|pr\b|datum|date|zweig))/i,
|
|
103
|
+
},
|
|
104
|
+
{ op: "publish", re: /\b(publish(e|en)?|publiziere[n]?)\b/i },
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Lines that are pasted TOOL OUTPUT rather than the user instructing.
|
|
109
|
+
*
|
|
110
|
+
* Round-2 adversarial review: an UNFENCED paste of a `git push … rejected`
|
|
111
|
+
* trace authorized `push` through the prose matcher — i.e. the gate
|
|
112
|
+
* pre-authorized the exact scenario it was built to stop. Fenced pastes were
|
|
113
|
+
* already handled; prose was not.
|
|
114
|
+
*/
|
|
115
|
+
const OUTPUT_LINE =
|
|
116
|
+
/^\s*(To\s+\S+|remote:|error:|fatal:|hint:|warning:|!\s|\?\?\s|\s*\^|[-+]{3}\s|@@\s|\$\s|>\s|\d+\s+(pass|fail)|npm ERR!|Error:|Traceback|at\s+\S+:\d+)/i;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A prompt that ASKS about an operation does not authorize it.
|
|
120
|
+
*
|
|
121
|
+
* `question-not-instruction` states this for the agent; the ledger needs it
|
|
122
|
+
* too. "was macht npm publish eigentlich genau?" authorized a real publish
|
|
123
|
+
* before this check.
|
|
124
|
+
*/
|
|
125
|
+
export function isInterrogative(prose: string): boolean {
|
|
126
|
+
const t = prose.trim();
|
|
127
|
+
if (!t) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
const hasImperative =
|
|
131
|
+
/\b(mach|mache|bitte|leg|lege|erstell|erstelle|f(ü|ue)hr|f(ü|ue)hre|setz|setze|starte|los|jetzt|go ahead|do it|ja[,.]?\s|ok[,.]?\s)\b/i.test(
|
|
132
|
+
t,
|
|
133
|
+
);
|
|
134
|
+
if (hasImperative) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return (
|
|
138
|
+
/\?\s*$/.test(t) ||
|
|
139
|
+
/^(was|wie|warum|wieso|weshalb|wann|wer|welche[rs]?|wo|ist|sind|kann|kannst|k(ö|oe)nnen|soll|sollen|darf|d(ü|ue)rfen|why|what|how|when|which|who|is|are|can|could|should|does|do)\b/i.test(
|
|
140
|
+
t,
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Executable commands a user may paste, mapped to the op they authorize. */
|
|
146
|
+
const PASTED_COMMANDS: ReadonlyArray<{ op: GitOp; re: RegExp }> = [
|
|
147
|
+
{ op: "publish", re: /\bnpm\s+publish\b/i },
|
|
148
|
+
{ op: "tag", re: /\bgit\s+push\s+[^\n]*--tags\b|\bgit\s+tag\s+-a\b/i },
|
|
149
|
+
{ op: "release", re: /\bgh\s+release\s+create\b/i },
|
|
150
|
+
{ op: "pr-merge", re: /\bgh\s+pr\s+merge\b/i },
|
|
151
|
+
{ op: "pr-create", re: /\bgh\s+pr\s+create\b/i },
|
|
152
|
+
{ op: "push", re: /\bgit\s+push\b/i },
|
|
153
|
+
{ op: "commit", re: /\bgit\s+commit\b/i },
|
|
154
|
+
{ op: "branch", re: /\bgit\s+(checkout\s+-b|switch\s+-c|branch)\b/i },
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Split a prompt into prose and fenced-code regions.
|
|
159
|
+
*
|
|
160
|
+
* The council's point: a user who PASTES an executable `git push origin main`
|
|
161
|
+
* is authorizing that command; the same literal appearing inside a log line or
|
|
162
|
+
* an error trace is not authorization. The discriminator used here is whether
|
|
163
|
+
* the line reads as a command invocation (starts at the command, optionally
|
|
164
|
+
* after a shell prompt marker) rather than being embedded in prose or in a
|
|
165
|
+
* `remote:` / `error:` / `hint:` diagnostic line.
|
|
166
|
+
*/
|
|
167
|
+
export function splitFences(prompt: string): { prose: string; fences: string[] } {
|
|
168
|
+
const fences: string[] = [];
|
|
169
|
+
const prose = prompt.replace(/```[^\n]*\n([\s\S]*?)```/g, (_m, body: string) => {
|
|
170
|
+
fences.push(body);
|
|
171
|
+
return " ";
|
|
172
|
+
});
|
|
173
|
+
return { prose, fences };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** A fenced line that is an actual command invocation, not diagnostic output. */
|
|
177
|
+
function _commandLines(fence: string): string[] {
|
|
178
|
+
return fence
|
|
179
|
+
.split("\n")
|
|
180
|
+
.map((l) => l.trim())
|
|
181
|
+
.filter((l) => l.length > 0)
|
|
182
|
+
// Drop git/npm diagnostic output — this is what "pasted an error trace" looks like.
|
|
183
|
+
.filter((l) => !/^(remote:|error:|hint:|fatal:|warning:|To |!\s|\s*\^)/i.test(l))
|
|
184
|
+
// Strip a leading shell prompt marker.
|
|
185
|
+
.map((l) => l.replace(/^[$>#]\s*/, ""))
|
|
186
|
+
// Keep only lines that START with the command.
|
|
187
|
+
.filter((l) => /^(git|gh|npm|pnpm|yarn|task)\b/.test(l));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface Ledger extends JsonObject {
|
|
191
|
+
session_id: string;
|
|
192
|
+
detected_at: string;
|
|
193
|
+
authorized: GitOp[];
|
|
194
|
+
evidence: { [op: string]: string };
|
|
195
|
+
prompt_chars: number;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Classify which ops a prompt authorizes. Exported for direct testing. */
|
|
199
|
+
export function classifyAuthorization(prompt: string): {
|
|
200
|
+
authorized: GitOp[];
|
|
201
|
+
evidence: Record<string, string>;
|
|
202
|
+
} {
|
|
203
|
+
const { prose, fences } = splitFences(prompt);
|
|
204
|
+
const authorized = new Set<GitOp>();
|
|
205
|
+
const evidence: Record<string, string> = {};
|
|
206
|
+
|
|
207
|
+
// Drop pasted tool output from the prose before matching (C4), then refuse
|
|
208
|
+
// to read a question as an instruction (C3).
|
|
209
|
+
const instruction = prose
|
|
210
|
+
.split("\n")
|
|
211
|
+
.filter((l) => !OUTPUT_LINE.test(l))
|
|
212
|
+
.join("\n");
|
|
213
|
+
|
|
214
|
+
for (const { op, re } of isInterrogative(instruction) ? [] : PHRASES) {
|
|
215
|
+
const m = re.exec(instruction);
|
|
216
|
+
if (m) {
|
|
217
|
+
authorized.add(op);
|
|
218
|
+
evidence[op] = `prose: "${m[0]}"`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const fence of fences) {
|
|
223
|
+
// A fence that also carries tool OUTPUT is a transcript of something that
|
|
224
|
+
// already happened, not a command the user is handing over. Pasting
|
|
225
|
+
// $ git push origin main
|
|
226
|
+
// ! [rejected] …
|
|
227
|
+
// is showing a failure, and reading it as "authorized push" is exactly the
|
|
228
|
+
// implicit-continuation misread this gate exists to stop.
|
|
229
|
+
if (fence.split("\n").some((l) => OUTPUT_LINE.test(l))) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
for (const line of _commandLines(fence)) {
|
|
233
|
+
for (const { op, re } of PASTED_COMMANDS) {
|
|
234
|
+
if (re.test(line)) {
|
|
235
|
+
authorized.add(op);
|
|
236
|
+
evidence[op] = `pasted command: "${line.slice(0, 80)}"`;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { authorized: [...authorized], evidence };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function run(stdin_text: string, options: { consumer_root: string }): number {
|
|
246
|
+
let envelope: JsonObject = {};
|
|
247
|
+
if (stdin_text.trim()) {
|
|
248
|
+
try {
|
|
249
|
+
const decoded = JSON.parse(stdin_text) as unknown;
|
|
250
|
+
if (_isObject(decoded)) {
|
|
251
|
+
envelope = decoded;
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
return EXIT_ALLOW;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const payload = _isObject(envelope["payload"]) ? (envelope["payload"] as JsonObject) : envelope;
|
|
259
|
+
let prompt = "";
|
|
260
|
+
for (const key of ["prompt", "userPrompt", "user_prompt", "message", "text"]) {
|
|
261
|
+
const v = payload[key];
|
|
262
|
+
if (typeof v === "string" && v.trim()) {
|
|
263
|
+
prompt = v;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (!prompt) {
|
|
268
|
+
return EXIT_ALLOW;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const { authorized, evidence } = classifyAuthorization(prompt);
|
|
272
|
+
const ledger: Ledger = {
|
|
273
|
+
session_id: typeof envelope["session_id"] === "string" ? envelope["session_id"] : "",
|
|
274
|
+
detected_at: new Date().toISOString(),
|
|
275
|
+
authorized,
|
|
276
|
+
evidence,
|
|
277
|
+
prompt_chars: prompt.length,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
atomic_write_json(path.join(options.consumer_root, STATE_FILE), ledger);
|
|
282
|
+
} catch {
|
|
283
|
+
// Observability only — a failed write degrades the gate to "no ledger",
|
|
284
|
+
// which the pre_tool_use concern treats as "not authorized" for the
|
|
285
|
+
// irreversible subset and as a warn for the rest.
|
|
286
|
+
}
|
|
287
|
+
return EXIT_ALLOW;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function main(argv?: string[]): number {
|
|
291
|
+
const args = argv ?? process.argv.slice(2);
|
|
292
|
+
let consumer_root = process.cwd();
|
|
293
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
294
|
+
const a = args[i];
|
|
295
|
+
if (a === "--project-dir" && args[i + 1] !== undefined) {
|
|
296
|
+
consumer_root = args[i + 1] as string;
|
|
297
|
+
i += 1;
|
|
298
|
+
} else if (a !== undefined && a.startsWith("--project-dir=")) {
|
|
299
|
+
consumer_root = a.slice("--project-dir=".length);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return run(readHookStdin(), { consumer_root });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
|
|
306
|
+
function _isCliEntry(): boolean {
|
|
307
|
+
if (typeof __AGENT_CONFIG_BUNDLE__ !== "undefined" && __AGENT_CONFIG_BUNDLE__) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
if (process.argv[1] === undefined) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
314
|
+
if (import.meta.url === argvUrl) {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
319
|
+
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
320
|
+
return here === argv;
|
|
321
|
+
} catch {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (_isCliEntry()) {
|
|
327
|
+
process.exit(main());
|
|
328
|
+
}
|
|
@@ -163,6 +163,25 @@ concerns:
|
|
|
163
163
|
args: []
|
|
164
164
|
fail_closed: false
|
|
165
165
|
severity: advisory
|
|
166
|
+
# Per-concern tool filter — the dispatcher skips this concern in-process on
|
|
167
|
+
# any other tool. See `_concern_matches_tool` in hooks/dispatch_hook.ts for
|
|
168
|
+
# the semantics (absent = every event, "*" = the same, non-tool events are
|
|
169
|
+
# never filtered) and for why the filter is NOT a host `matcher`.
|
|
170
|
+
#
|
|
171
|
+
# This set is not a guess: the hook's entire branch surface is
|
|
172
|
+
# `name === 'Grep' || name === 'Glob'` and `name === 'Read'`
|
|
173
|
+
# (code_graph_nudge_hook.ts), and a test pins the manifest list against it.
|
|
174
|
+
#
|
|
175
|
+
# Deliberately NOT declared on the three blocking PreToolUse guards
|
|
176
|
+
# (block-no-verify, block-kernel-rule-writes, block-config-weakening): their
|
|
177
|
+
# tool sets span host-specific naming variants (`Bash` / `BashTool` /
|
|
178
|
+
# `launch-process` / `launch_process`, `str-replace-editor` /
|
|
179
|
+
# `str_replace_editor` / `save-file` / …), so a list that misses one variant
|
|
180
|
+
# silently disables a security guard on that host. The concerns already
|
|
181
|
+
# re-read `tool_name` and return early; paying one dispatch to keep a guard
|
|
182
|
+
# correct is the right trade, and the cheap filter is reserved for advisory
|
|
183
|
+
# concerns whose set is provable from their own source.
|
|
184
|
+
tools: [Grep, Glob, Read]
|
|
166
185
|
# road-to-reachable-code-memory Phase 4 — session_end learning-sidecar
|
|
167
186
|
# aggregation. Joins the EXISTING session_end concern set (no new platform
|
|
168
187
|
# wiring). Aggregates agents/memory/intake/*.jsonl through the learning
|
|
@@ -240,6 +259,45 @@ concerns:
|
|
|
240
259
|
args: []
|
|
241
260
|
fail_closed: false
|
|
242
261
|
severity: advisory
|
|
262
|
+
# Language-mirror pin — carries the ONE fact the transcript cannot express:
|
|
263
|
+
# which language the human actually typed. 47 skill/slash-command bodies were
|
|
264
|
+
# measured arriving in the `user` role across 21 of 30 audited sessions, and
|
|
265
|
+
# in the worst case a 4,196-char English skill body was the last user-role
|
|
266
|
+
# content before 136 consecutive English replies to a German prompt.
|
|
267
|
+
# `user_prompt_submit` is the only event a skill body never reaches.
|
|
268
|
+
# Advisory: it supplies a fact, it never blocks.
|
|
269
|
+
language-mirror:
|
|
270
|
+
script: src/scripts/language_mirror_hook.ts
|
|
271
|
+
args: []
|
|
272
|
+
fail_closed: false
|
|
273
|
+
severity: advisory
|
|
274
|
+
# Git-authorization ledger — records which git ops the user's OWN WORDS
|
|
275
|
+
# authorize this turn. Writes state only; never blocks.
|
|
276
|
+
git-authorization:
|
|
277
|
+
script: src/scripts/git_authorization_hook.ts
|
|
278
|
+
args: []
|
|
279
|
+
fail_closed: false
|
|
280
|
+
severity: advisory
|
|
281
|
+
# The blocking half. BLOCKS only the irreversible subset that
|
|
282
|
+
# non-destructive-by-default already declares never-autonomous (npm publish,
|
|
283
|
+
# tag push, gh release create, gh pr merge); WARNS on commit / push /
|
|
284
|
+
# pr-create / branch. Severity, not reach — see the header of the script.
|
|
285
|
+
block-unauthorized-git:
|
|
286
|
+
script: src/scripts/hooks/block_unauthorized_git.ts
|
|
287
|
+
args: []
|
|
288
|
+
fail_closed: true
|
|
289
|
+
severity: blocking
|
|
290
|
+
# Evaluator independence. Gates EVALUATION dispatches only — a verdict
|
|
291
|
+
# pre-loaded into the prompt, or a second evaluation of the same work in one
|
|
292
|
+
# turn. Ordinary parallel fan-out is untouched: the council's "second dispatch
|
|
293
|
+
# of any kind" criterion would have blocked 6 of the 7 analysis subagents in
|
|
294
|
+
# the very session that produced this guard. fail_closed: false — a malformed
|
|
295
|
+
# envelope must never block a dispatch.
|
|
296
|
+
evidence-independence:
|
|
297
|
+
script: src/scripts/hooks/evidence_independence.ts
|
|
298
|
+
args: []
|
|
299
|
+
fail_closed: false
|
|
300
|
+
severity: blocking
|
|
243
301
|
# session_start canary injection — deterministic carrier for the
|
|
244
302
|
# session-canary rule (greet-by-name at task start + reply-close markers
|
|
245
303
|
# as a user-observable context-degradation signal). Gated on
|
|
@@ -258,15 +316,15 @@ platforms:
|
|
|
258
316
|
session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
259
317
|
session_end: [chat-history, memory-learn]
|
|
260
318
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
261
|
-
pre_tool_use: [block-no-verify, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
319
|
+
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
262
320
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
263
321
|
|
|
264
322
|
claude:
|
|
265
323
|
session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
266
324
|
session_end: [chat-history, memory-learn]
|
|
267
325
|
stop: [chat-history, hot-context, verify-before-complete, team-review-gate]
|
|
268
|
-
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
269
|
-
pre_tool_use: [block-no-verify, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
326
|
+
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization]
|
|
327
|
+
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
270
328
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
271
329
|
|
|
272
330
|
# Cowork — the Claude desktop app's local-agent-mode runtime, built
|
|
@@ -287,8 +345,8 @@ platforms:
|
|
|
287
345
|
session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
288
346
|
session_end: [chat-history, memory-learn]
|
|
289
347
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
290
|
-
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
291
|
-
pre_tool_use: [block-no-verify, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
348
|
+
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization]
|
|
349
|
+
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
292
350
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
293
351
|
|
|
294
352
|
# Phase 7.5 — Cursor. `.cursor/hooks.json` (project) is read by the
|
|
@@ -302,7 +360,7 @@ platforms:
|
|
|
302
360
|
session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
303
361
|
session_end: [chat-history, memory-learn]
|
|
304
362
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
305
|
-
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
363
|
+
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization]
|
|
306
364
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
307
365
|
|
|
308
366
|
# Phase 7.6 — Cline. Hooks live under `.clinerules/hooks/<HookName>`
|
|
@@ -317,7 +375,7 @@ platforms:
|
|
|
317
375
|
session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
318
376
|
session_end: [chat-history, memory-learn]
|
|
319
377
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
320
|
-
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
378
|
+
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization]
|
|
321
379
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
322
380
|
|
|
323
381
|
# Phase 7.7 — Windsurf (Cascade). Hooks live at `.windsurf/hooks.json`
|
|
@@ -335,7 +393,9 @@ platforms:
|
|
|
335
393
|
windsurf:
|
|
336
394
|
session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, profile-staleness, wrapper-freshness, surface-probe]
|
|
337
395
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
338
|
-
|
|
396
|
+
# language-mirror IS bound here: unlike minimal-safe-diff it needs only the
|
|
397
|
+
# user_prompt_submit slot, which Windsurf has.
|
|
398
|
+
user_prompt_submit: [chat-history, verify-before-complete, language-mirror, git-authorization]
|
|
339
399
|
|
|
340
400
|
# Phase 7.8 — Gemini CLI. Hooks live at `.gemini/settings.json`
|
|
341
401
|
# (project) or `~/.gemini/settings.json` (user). Per Gemini docs
|
|
@@ -351,7 +411,7 @@ platforms:
|
|
|
351
411
|
session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
352
412
|
session_end: [chat-history, memory-learn]
|
|
353
413
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
354
|
-
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
414
|
+
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization]
|
|
355
415
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
356
416
|
|
|
357
417
|
# Phase 7.9 — Copilot has no hook surface. Concerns route through
|
|
@@ -339,7 +339,9 @@ function _check_command(cmd: string): [boolean, string] {
|
|
|
339
339
|
if (/\bgit\b/.test(cmd)) {
|
|
340
340
|
return [
|
|
341
341
|
true,
|
|
342
|
-
'command parse failed (shlex) on a git-containing command — fail-closed (git-history-discipline)'
|
|
342
|
+
'command parse failed (shlex) on a git-containing command — fail-closed (git-history-discipline). \n'
|
|
343
|
+
+ 'Most common cause: an apostrophe or backtick inside a heredoc commit message in the SAME compound command as `git`. \n'
|
|
344
|
+
+ 'Fix: write the message to a file and use `git commit -F <file>` — do not retry the heredoc.',
|
|
343
345
|
];
|
|
344
346
|
}
|
|
345
347
|
return [false, ''];
|