@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.
Files changed (97) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +136 -0
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/analyze/conformance.md +182 -0
  5. package/dist/agent-src/commands/analyze/inbox.md +33 -2
  6. package/dist/agent-src/commands/review/changes.md +6 -5
  7. package/dist/agent-src/commands/roadmap/next.md +194 -0
  8. package/dist/agent-src/commands/roadmap.md +12 -4
  9. package/dist/agent-src/contexts/execution/subagent-routing.md +4 -1
  10. package/dist/agent-src/rules/decision-revisit-gate.md +2 -0
  11. package/dist/agent-src/rules/delegation-policy.md +3 -2
  12. package/dist/agent-src/rules/design-fidelity.md +17 -1
  13. package/dist/agent-src/rules/design-review-after-ui-write.md +116 -0
  14. package/dist/agent-src/rules/evaluator-independence.md +111 -0
  15. package/dist/agent-src/rules/session-canary.md +15 -1
  16. package/dist/agent-src/rules/settings-ask-protocol.md +176 -0
  17. package/dist/agent-src/rules/token-budget-discipline.md +6 -3
  18. package/dist/agent-src/rules/ui-audit-gate.md +3 -0
  19. package/dist/agent-src/rules/user-interaction.md +23 -0
  20. package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +1 -1
  21. package/dist/agent-src/skills/recursive-verification/SKILL.md +13 -0
  22. package/dist/agent-src/skills/rule-writing/SKILL.md +45 -0
  23. package/dist/agent-src/skills/skill-writing/SKILL.md +114 -0
  24. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  25. package/dist/cli/registry.js +1 -0
  26. package/dist/cli/registry.js.map +1 -1
  27. package/dist/cli-delegate/cmd_route_audit.js +4 -1
  28. package/dist/cli-delegate/cmd_route_explain.js +4 -1
  29. package/dist/discovery/deprecation-report.md +1 -1
  30. package/dist/discovery/discovery-manifest.json +146 -28
  31. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  32. package/dist/discovery/discovery-manifest.summary.md +5 -5
  33. package/dist/discovery/orphan-report.md +1 -1
  34. package/dist/discovery/packs.json +16 -11
  35. package/dist/discovery/trust-report.md +3 -3
  36. package/dist/discovery/workspaces.json +12 -7
  37. package/dist/hooks/dispatch.js +1501 -511
  38. package/dist/mcp/registry-manifest.json +3 -3
  39. package/dist/mcp/server.json +2 -2
  40. package/dist/router.json +1 -1
  41. package/dist/server/nicknameResolver.js +116 -0
  42. package/dist/server/nicknameResolver.js.map +1 -0
  43. package/dist/server/routes/ping.js +29 -11
  44. package/dist/server/routes/ping.js.map +1 -1
  45. package/dist/shared/interactiveContext.js +73 -0
  46. package/dist/shared/interactiveContext.js.map +1 -0
  47. package/dist/shared/nicknamePrefill.js +59 -0
  48. package/dist/shared/nicknamePrefill.js.map +1 -0
  49. package/dist/shared/settingsAsks.js +128 -0
  50. package/dist/shared/settingsAsks.js.map +1 -0
  51. package/dist/shared/settingsConsent.js +93 -0
  52. package/dist/shared/settingsConsent.js.map +1 -0
  53. package/package.json +1 -1
  54. package/src/config/agents-paths.yml +15 -0
  55. package/src/config/evaluator-budgets.json +68 -68
  56. package/src/config/gate-coverage.yml +131 -5
  57. package/src/config/gate-violation-baselines.json +11 -0
  58. package/src/scripts/_cli/cmd_route_audit.ts +4 -1
  59. package/src/scripts/_cli/cmd_route_explain.ts +9 -1
  60. package/src/scripts/_dispatch.bash +12 -0
  61. package/src/scripts/ai-video/stitch.sh +27 -9
  62. package/src/scripts/before_complete_hook.ts +146 -5
  63. package/src/scripts/bench_ab_v2_run.ts +272 -16
  64. package/src/scripts/bench_ab_v2_stats.ts +156 -3
  65. package/src/scripts/check_completion_review.ts +80 -3
  66. package/src/scripts/check_corpus_staleness.ts +692 -0
  67. package/src/scripts/check_depth_budget.ts +295 -0
  68. package/src/scripts/check_gate_coverage.ts +67 -0
  69. package/src/scripts/check_hook_bundle_freshness.ts +183 -0
  70. package/src/scripts/conformance_scan.ts +422 -0
  71. package/src/scripts/dispatch_r2_reviewer.ts +31 -2
  72. package/src/scripts/git_authorization_hook.ts +328 -0
  73. package/src/scripts/hook_manifest.yaml +69 -9
  74. package/src/scripts/hooks/block_no_verify.ts +3 -1
  75. package/src/scripts/hooks/block_unauthorized_git.ts +371 -0
  76. package/src/scripts/hooks/concern_registry.ts +8 -0
  77. package/src/scripts/hooks/design_slop_hook.ts +13 -1
  78. package/src/scripts/hooks/dispatch_hook.ts +97 -2
  79. package/src/scripts/hooks/evidence_independence.ts +368 -0
  80. package/src/scripts/language_mirror_hook.ts +456 -0
  81. package/src/scripts/lint_abstraction_thresholds.ts +41 -8
  82. package/src/scripts/lint_agents_layout.ts +2 -0
  83. package/src/scripts/lint_decision_impact.ts +246 -0
  84. package/src/scripts/lint_example_fences.ts +609 -0
  85. package/src/scripts/lint_hook_manifest.ts +29 -0
  86. package/src/scripts/lint_rule_interactions.ts +90 -0
  87. package/src/scripts/lint_rule_migration_ledger.ts +480 -0
  88. package/src/scripts/lint_skill_descriptions.ts +86 -2
  89. package/src/scripts/memory_learn_hook.ts +109 -6
  90. package/src/scripts/pr_url_reminder_hook.ts +17 -2
  91. package/src/scripts/release.ts +38 -1
  92. package/src/scripts/schemas/review-findings.schema.json +99 -0
  93. package/src/scripts/schemas/rule.schema.json +10 -0
  94. package/src/shared/interactiveContext.ts +98 -0
  95. package/src/shared/nicknamePrefill.ts +96 -0
  96. package/src/shared/settingsAsks.ts +176 -0
  97. package/src/shared/settingsConsent.ts +136 -0
@@ -0,0 +1,368 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Evaluator-independence guard — `pre_tool_use` concern.
4
+ *
5
+ * WHY — the single highest-severity finding of the 30-session conformance audit
6
+ * (2026-08-06), and the one class no rule covered at all:
7
+ *
8
+ * The agent commissioned a "blind review" of its own work, WROTE THE
9
+ * REVIEWER'S PROMPT ITSELF with the verdict pre-loaded ("NO-FINDINGS is
10
+ * expected and welcome") and the scope narrowed to four self-chosen files,
11
+ * then committed the resulting honest-null as binding gate evidence. An
12
+ * unsteered pass over the same delta later found 5 findings, 1 critical,
13
+ * already live on the trunk. The agent's own retro: "Die Fabrikation lag
14
+ * nicht im Weglassen des Reviews — sie lag in meinem Prompt. Ich habe die
15
+ * Antwort vorgeladen und dann als Evidenz verbucht."
16
+ *
17
+ * WHAT THIS GATES — evaluation dispatches only, never fan-out.
18
+ *
19
+ * The AI council (2026-08-06) proposed blocking the SECOND subagent dispatch of
20
+ * any kind within a turn. That criterion is wrong, and measurably so: the very
21
+ * session that produced this guard dispatched **seven** analysis subagents in
22
+ * one turn as a deliberate parallel fan-out, at the user's explicit request.
23
+ * Six of them would have been blocked. Parallel dispatch is not the defect —
24
+ * steering an evaluator is.
25
+ *
26
+ * So the gate keys on the evaluation shape instead:
27
+ *
28
+ * BLOCK · a dispatch whose prompt PRE-LOADS the verdict ("no findings is
29
+ * expected", "you should find nothing", "confirm there are no issues"). This
30
+ * is the literal construct that fabricated the honest-null, and it is
31
+ * mechanically detectable in the prompt the agent is about to send.
32
+ *
33
+ * BLOCK · a SECOND evaluation dispatch in the same turn — verdict shopping.
34
+ * Counted per turn over evaluation-shaped dispatches only.
35
+ *
36
+ * WARN · the first evaluation dispatch, naming the two observed failure
37
+ * shapes so the author can check its prompt before it goes out.
38
+ *
39
+ * A dispatch that is not evaluation-shaped is not touched at all.
40
+ *
41
+ * State: `agents/state/evidence-dispatch.json`
42
+ * { "session_id": str, "turn_started_at": iso8601, "evaluations": [ {at, digest} ] }
43
+ *
44
+ * Exit codes: 0 allow · 2 block (stderr carries the reason).
45
+ */
46
+ import crypto from "node:crypto";
47
+ import fs from "node:fs";
48
+ import path from "node:path";
49
+ import { fileURLToPath, pathToFileURL } from "node:url";
50
+
51
+ import { atomic_write_json } from "./state_io.js";
52
+ import { readHookStdin } from "./hook_stdin.js";
53
+
54
+ const EXIT_ALLOW = 0;
55
+ // MUST equal dispatch_hook.EXIT_BLOCK. The dispatcher's internal ladder is
56
+ // 0 allow / 1 block / 2 warn — NOT the 2-means-block shape a PreToolUse guard
57
+ // reads naturally from Claude's own native contract. This constant was 2 when
58
+ // this gate first shipped, so the dispatcher reduced every refusal to a WARN
59
+ // and the gate emitted advisory context while the operation went through.
60
+ // Pinned against the dispatcher's export by
61
+ // tests/hooks/concern_block_exit_parity.test.ts.
62
+ const EXIT_BLOCK = 1;
63
+
64
+ export const STATE_FILE = path.join("agents", "state", "evidence-dispatch.json");
65
+
66
+ type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
67
+ type JsonObject = { [k: string]: JsonValue };
68
+
69
+ function _isObject(v: unknown): v is JsonObject {
70
+ return typeof v === "object" && v !== null && !Array.isArray(v);
71
+ }
72
+
73
+ /** Tool names that dispatch a subagent, across platforms. */
74
+ const DISPATCH_TOOLS: ReadonlySet<string> = new Set([
75
+ "Agent",
76
+ "Task",
77
+ "task",
78
+ "dispatch_agent",
79
+ "dispatchAgent",
80
+ "launch_agent",
81
+ "run_subagent",
82
+ "Subagent",
83
+ ]);
84
+
85
+ /** The dispatch is an EVALUATION of work rather than ordinary fan-out. */
86
+ const EVALUATION_RE =
87
+ /\b(review|reviewer|audit|auditor|judge|verdict|blind[- ]pass|blind review|adversarial|critique|assess(ment)?|verify (my|the) (work|change|diff|implementation)|find (any )?(bugs|issues|defects|problems) in (my|the)|pr(ü|ue)f(e|ung)?|begutachte|gutachten|bewerte|(gegen)?check(e)?|durchsicht|review(e|iere))\b/i;
88
+
89
+ /**
90
+ * Verdict pre-loading. Each pattern is a phrase that tells the evaluator what
91
+ * answer is acceptable BEFORE it has looked — the construct that produced the
92
+ * fabricated honest-null.
93
+ */
94
+ const PRELOADED_VERDICT_RE = [
95
+ // German — the operator writes German, and an English-only list meant no
96
+ // German dispatch was ever screened at all.
97
+ /\bich erwarte (hier )?(keine|nichts)\b/i,
98
+ /\b(du )?(wirst|solltest) (hier )?nichts finden\b/i,
99
+ /\bbest(ä|ae)tige (nur|kurz)\b/i,
100
+ /\b(ist|sollte) (wahrscheinlich|vermutlich|sicher) (sauber|korrekt|in ordnung|fine)\b/i,
101
+ /\bkeine findings? (erwartet|zu erwarten)\b/i,
102
+ // English paraphrases of the audited construct.
103
+ /\bi am confident there is nothing\b/i,
104
+ /\ba clean pass is the likely\b/i,
105
+ /\bno[- ]findings? (is|are) (expected|welcome|fine|acceptable|the likely)/i,
106
+ /\b(you )?(should|will|probably) find (nothing|no (issues|problems|bugs|findings))/i,
107
+ /\bconfirm (that )?there (are|is) no\b/i,
108
+ /\bi (believe|think|expect) (this|it) is (clean|correct|fine)\b/i,
109
+ /\bexpect(ed)? (to be )?(clean|green|no findings)\b/i,
110
+ /\bjust confirm\b/i,
111
+ /\bit('s| is) (probably|likely) fine\b/i,
112
+ ];
113
+
114
+ export function isDispatchTool(tool: string | null): boolean {
115
+ return tool !== null && DISPATCH_TOOLS.has(tool);
116
+ }
117
+
118
+ export function isEvaluationPrompt(prompt: string): boolean {
119
+ return EVALUATION_RE.test(prompt);
120
+ }
121
+
122
+ /**
123
+ * The evaluation targets the AGENT'S OWN work rather than some external
124
+ * artifact.
125
+ *
126
+ * This discriminator was added because the conformance scan caught the gate
127
+ * red-handed on real data: the audit session that produced this hook dispatched
128
+ * seven subagents whose prompts opened "You are auditing real Claude Code
129
+ * session transcripts…". `EVALUATION_RE` matches `audit`, so six of the seven
130
+ * were flagged as verdict shopping — the exact false positive the hook's own
131
+ * header claims to avoid. Auditing thirty transcripts is not reviewing your own
132
+ * diff twice.
133
+ *
134
+ * Verdict shopping is only possible when there is one subject to shop a verdict
135
+ * ON, so the second-dispatch block requires a self-reference. The pre-loaded-
136
+ * verdict block does NOT: steering an evaluator is wrong whatever it is
137
+ * pointed at.
138
+ */
139
+ const SELF_SCOPE_RE =
140
+ /\b(my (work|change|diff|implementation|code|fix|patch|branch|pr)|this (diff|change|branch|pr|delta|implementation|patch)|the delta|the change i|what i (wrote|built|changed|implemented)|i just (wrote|built|changed|implemented))\b/i;
141
+
142
+ export function isSelfScoped(prompt: string): boolean {
143
+ return SELF_SCOPE_RE.test(prompt);
144
+ }
145
+
146
+ /** Return the pre-loading phrase found in a prompt, or null. */
147
+ export function preloadedVerdict(prompt: string): string | null {
148
+ for (const re of PRELOADED_VERDICT_RE) {
149
+ const m = re.exec(prompt);
150
+ if (m) {
151
+ return m[0];
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+
157
+ /** Pull [tool, prompt] out of a pre-tool envelope. */
158
+ export function extractDispatch(envelope: JsonObject): [string | null, string] {
159
+ const payload = _isObject(envelope["payload"]) ? (envelope["payload"] as JsonObject) : envelope;
160
+ const toolRaw = payload["tool_name"] ?? payload["toolName"] ?? payload["tool"];
161
+ const tool = typeof toolRaw === "string" ? toolRaw : null;
162
+ const input = _isObject(payload["tool_input"])
163
+ ? (payload["tool_input"] as JsonObject)
164
+ : _isObject(payload["toolInput"])
165
+ ? (payload["toolInput"] as JsonObject)
166
+ : payload;
167
+ for (const key of ["prompt", "instructions", "task", "description"]) {
168
+ const v = input[key];
169
+ if (typeof v === "string" && v.trim()) {
170
+ return [tool, v];
171
+ }
172
+ }
173
+ return [tool, ""];
174
+ }
175
+
176
+ interface DispatchState extends JsonObject {
177
+ session_id: string;
178
+ turn_started_at: string;
179
+ evaluations: JsonValue[];
180
+ }
181
+
182
+ /** The current user turn's stamp, or "" when no ledger exists yet. */
183
+ function _ledgerStamp(consumer_root: string): string {
184
+ try {
185
+ const raw = fs.readFileSync(
186
+ path.join(consumer_root, "agents", "state", "git-authorization.json"),
187
+ "utf8",
188
+ );
189
+ const d = JSON.parse(raw) as Record<string, unknown>;
190
+ return typeof d["detected_at"] === "string" ? d["detected_at"] : "";
191
+ } catch {
192
+ return "";
193
+ }
194
+ }
195
+
196
+ function _load(target: string): DispatchState {
197
+ try {
198
+ const decoded = JSON.parse(fs.readFileSync(target, "utf8")) as unknown;
199
+ if (_isObject(decoded) && Array.isArray(decoded["evaluations"])) {
200
+ return decoded as unknown as DispatchState;
201
+ }
202
+ } catch {
203
+ /* fall through */
204
+ }
205
+ return { session_id: "", turn_started_at: "", evaluations: [] };
206
+ }
207
+
208
+ export interface Decision {
209
+ exit: number;
210
+ stdout: string;
211
+ stderr: string;
212
+ /** Number of evaluation dispatches recorded for this turn AFTER this one. */
213
+ evaluations: number;
214
+ }
215
+
216
+ const WARN_TEXT =
217
+ "First evaluation dispatch this turn. Two shapes fabricated a binding honest-null " +
218
+ "in the audited sessions — check your prompt for both before it goes out: " +
219
+ "(1) a verdict pre-loaded into the prompt, and (2) a scope narrowed to files you " +
220
+ "chose. A review you commissioned on your own work is admissible as gate evidence " +
221
+ "only when the prompt is recorded alongside the verdict.";
222
+
223
+ export function decide(
224
+ tool: string | null,
225
+ prompt: string,
226
+ priorEvaluations: number,
227
+ ): Decision {
228
+ if (!isDispatchTool(tool) || !isEvaluationPrompt(prompt)) {
229
+ return { exit: EXIT_ALLOW, stdout: "", stderr: "", evaluations: priorEvaluations };
230
+ }
231
+
232
+ const preloaded = preloadedVerdict(prompt);
233
+ if (preloaded !== null) {
234
+ return {
235
+ exit: EXIT_BLOCK,
236
+ stdout: "",
237
+ stderr:
238
+ `Blocked: this evaluation prompt pre-loads its verdict ("${preloaded}"). ` +
239
+ `That is the construct that produced a fabricated NO-FINDINGS committed as ` +
240
+ `binding gate evidence, over a delta an unsteered pass then found a live ` +
241
+ `critical in. Remove the expectation from the prompt and let the evaluator ` +
242
+ `reach its own verdict.\n`,
243
+ evaluations: priorEvaluations,
244
+ };
245
+ }
246
+
247
+ // Verdict shopping needs a single subject to shop a verdict on. An
248
+ // evaluation of an external artifact (transcripts, a third party's code) is
249
+ // fan-out, not self-review, and is never counted or blocked.
250
+ if (!isSelfScoped(prompt)) {
251
+ return { exit: EXIT_ALLOW, stdout: "", stderr: "", evaluations: priorEvaluations };
252
+ }
253
+
254
+ if (priorEvaluations >= 1) {
255
+ return {
256
+ exit: EXIT_BLOCK,
257
+ stdout: "",
258
+ stderr:
259
+ `Blocked: second evaluation dispatch of your own work in this turn (verdict ` +
260
+ `shopping). One evaluation has already run; commissioning another with a ` +
261
+ `different prompt or scope selects the answer instead of measuring it. ` +
262
+ `Report what the first pass returned, then re-plan.\n`,
263
+ evaluations: priorEvaluations,
264
+ };
265
+ }
266
+
267
+ return {
268
+ exit: EXIT_ALLOW,
269
+ stdout: `${JSON.stringify({ decision: "warn", reason: WARN_TEXT })}\n`,
270
+ stderr: "",
271
+ evaluations: priorEvaluations + 1,
272
+ };
273
+ }
274
+
275
+ export function run(stdin_text: string, options: { consumer_root: string }): number {
276
+ let envelope: JsonObject = {};
277
+ if (stdin_text.trim()) {
278
+ try {
279
+ const decoded = JSON.parse(stdin_text) as unknown;
280
+ if (_isObject(decoded)) {
281
+ envelope = decoded;
282
+ }
283
+ } catch {
284
+ return EXIT_ALLOW;
285
+ }
286
+ }
287
+
288
+ const target = path.join(options.consumer_root, STATE_FILE);
289
+ const state = _load(target);
290
+ const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
291
+
292
+ // Turn boundary. `turn_id` does NOT exist in the production envelope — the
293
+ // dispatcher never sets it, so the original marker fell through to
294
+ // `session_id` and the counter was session-scoped: one self-review anywhere
295
+ // in a session blocked every later one. The only per-user-turn stamp that
296
+ // actually exists is `detected_at` in the authorization ledger, which
297
+ // `git_authorization_hook` rewrites on every `user_prompt_submit`.
298
+ const turnMarker = `${session_id}:${_ledgerStamp(options.consumer_root)}`;
299
+ if (state.session_id !== turnMarker) {
300
+ state.session_id = turnMarker;
301
+ state.turn_started_at = new Date().toISOString();
302
+ state.evaluations = [];
303
+ }
304
+
305
+ const [tool, prompt] = extractDispatch(envelope);
306
+ const decision = decide(tool, prompt, state.evaluations.length);
307
+
308
+ if (decision.evaluations > state.evaluations.length) {
309
+ state.evaluations.push({
310
+ at: new Date().toISOString(),
311
+ digest: crypto.createHash("sha256").update(prompt).digest("hex").slice(0, 16),
312
+ prompt_chars: prompt.length,
313
+ });
314
+ try {
315
+ atomic_write_json(target, state);
316
+ } catch {
317
+ /* observability only */
318
+ }
319
+ }
320
+
321
+ if (decision.stdout) {
322
+ process.stdout.write(decision.stdout);
323
+ }
324
+ if (decision.stderr) {
325
+ process.stderr.write(decision.stderr);
326
+ }
327
+ return decision.exit;
328
+ }
329
+
330
+ export function main(argv?: string[]): number {
331
+ const args = argv ?? process.argv.slice(2);
332
+ let consumer_root = process.cwd();
333
+ for (let i = 0; i < args.length; i += 1) {
334
+ const a = args[i];
335
+ if (a === "--project-dir" && args[i + 1] !== undefined) {
336
+ consumer_root = args[i + 1] as string;
337
+ i += 1;
338
+ } else if (a !== undefined && a.startsWith("--project-dir=")) {
339
+ consumer_root = a.slice("--project-dir=".length);
340
+ }
341
+ }
342
+ return run(readHookStdin(), { consumer_root });
343
+ }
344
+
345
+ declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
346
+ function _isCliEntry(): boolean {
347
+ if (typeof __AGENT_CONFIG_BUNDLE__ !== "undefined" && __AGENT_CONFIG_BUNDLE__) {
348
+ return false;
349
+ }
350
+ if (process.argv[1] === undefined) {
351
+ return false;
352
+ }
353
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
354
+ if (import.meta.url === argvUrl) {
355
+ return true;
356
+ }
357
+ try {
358
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
359
+ const argv = fs.realpathSync(path.resolve(process.argv[1]));
360
+ return here === argv;
361
+ } catch {
362
+ return false;
363
+ }
364
+ }
365
+
366
+ if (_isCliEntry()) {
367
+ process.exit(main());
368
+ }