@event4u/agent-config 14.18.0 → 14.20.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 (210) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +120 -129
  5. package/README.md +71 -149
  6. package/dist/agent-src/README.md +1 -1
  7. package/dist/agent-src/commands/agent-handoff.md +44 -43
  8. package/dist/agent-src/commands/review/changes.md +19 -19
  9. package/dist/agent-src/commands/review.md +2 -2
  10. package/dist/agent-src/contexts/execution/host-capability-manifest.md +37 -4
  11. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  12. package/dist/agent-src/rules/git-history-discipline.md +2 -2
  13. package/dist/agent-src/rules/guidelines.md +1 -1
  14. package/dist/agent-src/rules/session-canary.md +2 -2
  15. package/dist/agent-src/rules/token-efficiency.md +1 -1
  16. package/dist/agent-src/rules/untrusted-input-defense.md +0 -2
  17. package/dist/agent-src/skills/adversarial-review/SKILL.md +0 -8
  18. package/dist/agent-src/skills/canvas-design/SKILL.md +1 -1
  19. package/dist/agent-src/skills/design-intelligence/SKILL.md +1 -1
  20. package/dist/agent-src/skills/design-intelligence/data/app-interface.csv +1 -1
  21. package/dist/agent-src/skills/design-intelligence/data/motion.csv +17 -17
  22. package/dist/agent-src/skills/design-intelligence/data/ux-guidelines.csv +2 -2
  23. package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +17 -0
  24. package/dist/agent-src/skills/design-intelligence/references/design-rules-checklist.md +4 -4
  25. package/dist/agent-src/skills/design-review/SKILL.md +1 -1
  26. package/dist/agent-src/skills/design-variations/SKILL.md +1 -1
  27. package/dist/agent-src/skills/evaluate-llm-feature/SKILL.md +1 -1
  28. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +1 -1
  29. package/dist/agent-src/skills/fe-design/SKILL.md +32 -5
  30. package/dist/agent-src/skills/fe-design/references/craft-floor.md +7 -0
  31. package/dist/agent-src/skills/fe-design/references/design-patterns.md +47 -2
  32. package/dist/agent-src/skills/laravel-scheduling/SKILL.md +3 -1
  33. package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
  34. package/dist/agent-src/skills/project-analyzer/SKILL.md +0 -6
  35. package/dist/agent-src/skills/react-native-setup/SKILL.md +3 -3
  36. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
  37. package/dist/agent-src/skills/sequential-thinking/SKILL.md +18 -8
  38. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +28 -6
  39. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  40. package/dist/agent-src/skills/test-driven-development/SKILL.md +1 -3
  41. package/dist/agent-src/skills/ui-apply-generic/SKILL.md +1 -1
  42. package/dist/agent-src/skills/ui-component-architect/SKILL.md +1 -1
  43. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +17 -2
  44. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  45. package/dist/agent-src/templates/roadmaps.md +14 -1
  46. package/dist/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
  47. package/dist/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
  48. package/dist/agent-src/templates/scripts/telemetry_report.ts +22 -2
  49. package/dist/cli/commands/recordTriggerEval.js +2 -2
  50. package/dist/cli/main.js +1 -1
  51. package/dist/cli/mcp/dispatch.js +10 -0
  52. package/dist/cli/mcp/stdio.js +33 -2
  53. package/dist/cli/mcp/telemetry.js +227 -0
  54. package/dist/cli-delegate/{chunk-AKJGUQNO.js → chunk-C6IPZRDA.js} +484 -353
  55. package/dist/cli-delegate/{chunk-CYH5SP6M.js → chunk-I6RHFQEG.js} +151 -16
  56. package/dist/cli-delegate/{chunk-7RXIKF62.js → chunk-NUMQHLS5.js} +304 -136
  57. package/dist/cli-delegate/{chunk-PMEKGDED.js → chunk-PI2GU3DP.js} +163 -1
  58. package/dist/cli-delegate/{chunk-7PRE6ZV2.js → chunk-YS6XTLEK.js} +199 -4
  59. package/dist/cli-delegate/chunk-ZVBOHSVF.js +1109 -0
  60. package/dist/cli-delegate/cmd_adr_effective.js +318 -81
  61. package/dist/cli-delegate/cmd_conformance.js +489 -121
  62. package/dist/cli-delegate/cmd_doctor.js +2 -2
  63. package/dist/cli-delegate/cmd_export.js +2 -2
  64. package/dist/cli-delegate/cmd_handoff.js +42 -12
  65. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  66. package/dist/cli-delegate/cmd_prune.js +2 -2
  67. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  68. package/dist/cli-delegate/cmd_session_recycle.js +59 -902
  69. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  70. package/dist/cli-delegate/cmd_sync.js +2 -2
  71. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  72. package/dist/cli-delegate/cmd_validate.js +2 -2
  73. package/dist/discovery/deprecation-report.md +1 -1
  74. package/dist/discovery/discovery-manifest.json +34 -33
  75. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  76. package/dist/discovery/discovery-manifest.summary.md +1 -1
  77. package/dist/discovery/dormancy-report.md +1 -1
  78. package/dist/discovery/orphan-report.md +1 -1
  79. package/dist/discovery/packs.json +2 -2
  80. package/dist/discovery/trust-report.md +1 -1
  81. package/dist/discovery/workspaces.json +2 -2
  82. package/dist/hooks/dispatch.js +4455 -3025
  83. package/dist/install/install.mjs +975 -689
  84. package/dist/mcp/registry-manifest.json +2 -2
  85. package/dist/mcp/server.json +2 -2
  86. package/dist/mcp/server.mjs +2087 -1134
  87. package/dist/scripts/_lib/model_tier.js +1 -1
  88. package/dist/scripts/mcp_server/telemetry.js +148 -0
  89. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +1 -1
  90. package/docs/guidelines/agent-infra/naming.md +1 -1
  91. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  92. package/docs/guidelines/agent-infra/security-lint-containment.md +46 -6
  93. package/docs/guidelines/agent-infra/self-improvement-pipeline.md +4 -4
  94. package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +1 -1
  95. package/docs/guidelines/design-antipatterns.md +87 -64
  96. package/docs/guidelines/redundancy-taxonomy.md +28 -0
  97. package/package.json +1 -1
  98. package/src/agent-src/README.md +1 -1
  99. package/src/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
  100. package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
  101. package/src/agent-src/templates/scripts/telemetry_report.ts +22 -2
  102. package/src/config/assurance-capability-registry.json +9 -0
  103. package/src/config/ci-local-parity.yml +6 -0
  104. package/src/config/gate-coverage.yml +225 -0
  105. package/src/config/gate-violation-baselines.json +18 -6
  106. package/src/config/release-gate-locality.yml +12 -0
  107. package/src/scripts/_cli/cmd_doctor.ts +2 -2
  108. package/src/scripts/_cli/cmd_handoff.ts +1 -1
  109. package/src/scripts/_cli/cmd_session_recycle.ts +51 -3
  110. package/src/scripts/_cli/cmd_update.ts +1 -1
  111. package/src/scripts/_cli/cmd_versions.ts +1 -1
  112. package/src/scripts/_lib/adr_frontmatter.ts +44 -0
  113. package/src/scripts/_lib/audit_field_provenance.ts +44 -3
  114. package/src/scripts/_lib/consequence_objects.ts +152 -0
  115. package/src/scripts/_lib/exec_evidence.ts +7 -0
  116. package/src/scripts/_lib/global_deploy_inventory.ts +2 -2
  117. package/src/scripts/_lib/host_capability.ts +38 -0
  118. package/src/scripts/_lib/host_permission_checks.ts +251 -0
  119. package/src/scripts/_lib/install_reach_checks.ts +33 -1
  120. package/src/scripts/_lib/mcp_bridge.ts +222 -10
  121. package/src/scripts/_lib/mcp_consent_residual.ts +173 -0
  122. package/src/scripts/_lib/model_tier.ts +1 -1
  123. package/src/scripts/_lib/obligation_frequency.ts +2 -2
  124. package/src/scripts/_lib/orchestration_record.ts +23 -1
  125. package/src/scripts/_lib/recycle_envelope_paths.ts +190 -0
  126. package/src/scripts/_lib/release_highlights.ts +93 -0
  127. package/src/scripts/_lib/release_material.ts +64 -1
  128. package/src/scripts/_lib/release_position.ts +96 -0
  129. package/src/scripts/_lib/review_skipped_record.ts +17 -1
  130. package/src/scripts/_lib/runtime_journal.ts +26 -3
  131. package/src/scripts/_lib/runtime_wiring_checks.ts +12 -1
  132. package/src/scripts/_lib/security_lint.ts +179 -7
  133. package/src/scripts/_lib/self_repair.ts +141 -6
  134. package/src/scripts/_lib/self_repair_store.ts +273 -7
  135. package/src/scripts/_lib/session_register.ts +11 -6
  136. package/src/scripts/_lib/sqlite_schema.ts +22 -0
  137. package/src/scripts/_lib/subagent_capsule.ts +155 -3
  138. package/src/scripts/_lib/surface.ts +86 -0
  139. package/src/scripts/_lib/ui_authority.ts +116 -0
  140. package/src/scripts/adr/regenerate_index.ts +54 -1
  141. package/src/scripts/adr_cite_check.ts +71 -15
  142. package/src/scripts/ai_council/clients.ts +1 -1
  143. package/src/scripts/autonomy_friction_corpus.ts +229 -0
  144. package/src/scripts/build_proof.ts +15 -2
  145. package/src/scripts/build_rule_trigger_matrix.ts +5 -0
  146. package/src/scripts/check_beta_review_markers.ts +141 -17
  147. package/src/scripts/check_enforcement_coverage.ts +1 -1
  148. package/src/scripts/check_enforcement_denominator.ts +121 -11
  149. package/src/scripts/check_finding_dispositions.ts +66 -9
  150. package/src/scripts/check_gate_coverage.ts +96 -0
  151. package/src/scripts/check_mcp_doc_drift.ts +217 -0
  152. package/src/scripts/check_pack_size.ts +321 -3
  153. package/src/scripts/check_release_highlights.ts +74 -1
  154. package/src/scripts/check_skill_activation_claim.ts +179 -0
  155. package/src/scripts/check_trigger_evals.ts +1 -1
  156. package/src/scripts/conformance_scan.ts +44 -3
  157. package/src/scripts/explain_run.ts +1 -1
  158. package/src/scripts/generate_capability_matrix.ts +1 -1
  159. package/src/scripts/git_authorization_hook.ts +75 -1
  160. package/src/scripts/handoff_context_hook.ts +54 -7
  161. package/src/scripts/hook_manifest.json +1 -1
  162. package/src/scripts/hook_manifest.yaml +14 -11
  163. package/src/scripts/hooks/augment-dispatcher.sh +1 -1
  164. package/src/scripts/hooks/category_a.ts +291 -0
  165. package/src/scripts/hooks/cline-dispatcher.sh +1 -1
  166. package/src/scripts/hooks/cursor-dispatcher.sh +11 -5
  167. package/src/scripts/hooks/dispatch_hook.ts +17 -22
  168. package/src/scripts/hooks/git_command_classifier.ts +156 -1
  169. package/src/scripts/hooks/host_lowering.ts +215 -0
  170. package/src/scripts/hooks/host_lowering.yaml +185 -0
  171. package/src/scripts/hooks/host_semantics.ts +105 -37
  172. package/src/scripts/hooks/journal_record_hook.ts +10 -0
  173. package/src/scripts/hooks/permission_gate.ts +117 -0
  174. package/src/scripts/hooks/session_eol_hook.ts +18 -7
  175. package/src/scripts/hooks_status.ts +12 -10
  176. package/src/scripts/install.ts +57 -86
  177. package/src/scripts/install_anthropic_key.sh +2 -2
  178. package/src/scripts/lint_agent_security.ts +316 -25
  179. package/src/scripts/lint_confusables.ts +42 -6
  180. package/src/scripts/lint_design_antipattern_parity.ts +80 -0
  181. package/src/scripts/lint_evidence_artifacts.ts +45 -1
  182. package/src/scripts/lint_handoffs.ts +0 -132
  183. package/src/scripts/lint_hidden_unicode.ts +49 -7
  184. package/src/scripts/lint_hook_manifest.ts +87 -3
  185. package/src/scripts/lint_instruction_smuggling.ts +42 -6
  186. package/src/scripts/lint_mcp_config_security.ts +42 -6
  187. package/src/scripts/lint_motion_authority_drift.ts +731 -0
  188. package/src/scripts/lint_readme_size.ts +158 -12
  189. package/src/scripts/lint_reasoning_fixtures.ts +216 -0
  190. package/src/scripts/lint_roadmap_later_disposition.ts +217 -27
  191. package/src/scripts/lint_skill_frontmatter_safety.ts +43 -7
  192. package/src/scripts/lint_skill_trigger_contradiction.ts +161 -0
  193. package/src/scripts/mcp_server/resources.ts +41 -0
  194. package/src/scripts/mcp_server/telemetry.ts +19 -4
  195. package/src/scripts/measure_sibling_naming.ts +150 -0
  196. package/src/scripts/orchestration_record.ts +12 -1
  197. package/src/scripts/redact_hook_capture.ts +1 -1
  198. package/src/scripts/release.ts +24 -24
  199. package/src/scripts/release_publication.ts +6 -2
  200. package/src/scripts/report_skill_activation.ts +319 -7
  201. package/src/scripts/schemas/review-findings.schema.json +5 -0
  202. package/src/scripts/schemas/skill.schema.json +2 -2
  203. package/src/scripts/self_repair_cli.ts +31 -1
  204. package/src/scripts/session_canary_hook.ts +1 -1
  205. package/src/scripts/session_register_hook.ts +4 -3
  206. package/src/scripts/skill_scout.ts +69 -0
  207. package/src/scripts/trigger_eval_grandfather.json +5 -1
  208. package/src/scripts/validate_agent_settings.ts +1 -1
  209. package/dist/cli-delegate/chunk-2GNWGMAL.js +0 -172
  210. package/dist/cli-delegate/chunk-LPPWO5CE.js +0 -161
@@ -0,0 +1,117 @@
1
+ /**
2
+ * permission_gate — whether a `pre_tool_use` call gets an explicit
3
+ * `permissionDecision`, and which one.
4
+ *
5
+ * Four independent conditions, each able to withhold an allow on its own: the
6
+ * host offers the field on `pre_tool_use` only; the dispatcher's reduced
7
+ * severity must already be `allow`; no concern may have voted `ask` or `deny`;
8
+ * and the call itself must be category A. An allow is emitted only when all
9
+ * four hold, so widening any one of them is a visible edit rather than an
10
+ * emergent effect.
11
+ *
12
+ * Separate from `dispatch_hook` because that file is against the per-file
13
+ * source-size budget, and because the seam is real: the dispatcher runs
14
+ * concerns and reduces exit codes, this module reads the result and decides
15
+ * what the host is told about permission.
16
+ */
17
+ import { isCategoryA, type ToolInput } from "./category_a.js";
18
+ import {
19
+ composePermissionDecision,
20
+ type PermissionDecision,
21
+ type PermissionEmission,
22
+ } from "./host_semantics.js";
23
+
24
+ type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
25
+ type JsonObject = { [k: string]: JsonValue };
26
+
27
+ function _isObject(v: unknown): v is JsonObject {
28
+ return typeof v === "object" && v !== null && !Array.isArray(v);
29
+ }
30
+
31
+ /** Mirrors `dispatch_hook.EXIT_ALLOW` / `EXIT_BLOCK`; imported would be a cycle. */
32
+ const EXIT_ALLOW = 0;
33
+ const EXIT_BLOCK = 1;
34
+
35
+ /** The host's `tool_name`, or `""` when the event carries none. */
36
+ export function _payload_tool_name(envelope: JsonObject): string {
37
+ const payload = envelope["payload"];
38
+ if (!_isObject(payload)) return "";
39
+ const raw = payload["tool_name"];
40
+ return typeof raw === "string" ? raw : "";
41
+ }
42
+
43
+ /** The host's `tool_input` object, or `{}` when the event carries none. */
44
+ export function _payload_tool_input(envelope: JsonObject): ToolInput {
45
+ const payload = envelope["payload"];
46
+ if (!_isObject(payload)) return {};
47
+ const raw = payload["tool_input"];
48
+ return _isObject(raw) ? (raw as ToolInput) : {};
49
+ }
50
+
51
+ /**
52
+ * The root every category-A path argument must resolve inside.
53
+ *
54
+ * The host's own `cwd` wins over `workspace_root` because the latter is the
55
+ * dispatcher's process cwd, which in a worktree is the parent checkout rather
56
+ * than the tree the user is working in — confining against it would admit a
57
+ * path outside the tree the call actually runs in.
58
+ */
59
+ export function _working_tree_root(envelope: JsonObject): string {
60
+ const payload = envelope["payload"];
61
+ if (_isObject(payload) && typeof payload["cwd"] === "string" && payload["cwd"].trim()) {
62
+ return payload["cwd"];
63
+ }
64
+ const workspace = envelope["workspace_root"];
65
+ return typeof workspace === "string" ? workspace : "";
66
+ }
67
+
68
+ /**
69
+ * One concern's permission verdict.
70
+ *
71
+ * A block is a deny. A concern may also say `{"decision":"ask"}` on stdout —
72
+ * the field has been recorded in the feedback file since v1 and read by
73
+ * nothing, so an advisory concern that wanted a confirmation got silence.
74
+ * `ask` is honoured regardless of the exit code: an advisory concern cannot
75
+ * block and must not have to in order to be heard. Everything else abstains,
76
+ * which composes as `allow`.
77
+ */
78
+ export function _concern_permission_verdict(
79
+ rc: number,
80
+ reply: JsonObject,
81
+ ): PermissionDecision {
82
+ if (rc === EXIT_BLOCK) return "deny";
83
+ const stated = String(reply["decision"] ?? "").trim().toLowerCase();
84
+ if (stated === "ask") return "ask";
85
+ if (stated === "deny") return "deny";
86
+ return "allow";
87
+ }
88
+
89
+ /**
90
+ * The permission field to hand the host, or `null` to keep the legacy envelope.
91
+ *
92
+ * Four independent conditions, each able to withhold the allow on its own:
93
+ * the host offers the field on `pre_tool_use` only; the reduced severity must
94
+ * already be `allow`; no concern may have voted `ask` or `deny`; and the call
95
+ * itself must be category A. An allow is emitted only when all four hold, so
96
+ * widening any one of them is a visible edit rather than an emergent effect.
97
+ */
98
+ export function _permission_for(
99
+ event: string,
100
+ envelope: JsonObject,
101
+ final_rc: number,
102
+ verdicts: readonly PermissionDecision[],
103
+ ): PermissionEmission | null {
104
+ if (event !== "pre_tool_use") return null;
105
+ if (final_rc !== EXIT_ALLOW) return null;
106
+ if (composePermissionDecision(verdicts) !== "allow") return null;
107
+ const tool_name = _payload_tool_name(envelope);
108
+ if (!isCategoryA(tool_name, _payload_tool_input(envelope), _working_tree_root(envelope))) {
109
+ return null;
110
+ }
111
+ return {
112
+ decision: "allow",
113
+ reason:
114
+ `agent-config: category-A ${tool_name} inside the working tree — ` +
115
+ `no concern gates it and none asked to.`,
116
+ };
117
+ }
@@ -45,7 +45,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
45
45
 
46
46
  import recycleThresholdConfig from '../../config/recycle-threshold-budget.json';
47
47
 
48
- import { RECYCLE_ENVELOPE_REL } from '../_lib/recycle_envelope_paths.js';
48
+ import { recycle_envelope_rel } from '../_lib/recycle_envelope_paths.js';
49
49
  import {
50
50
  emptyCounters,
51
51
  eolSessionKey,
@@ -180,8 +180,16 @@ export function readState(file: string): SessionEolState {
180
180
  * session's state — a wrong resume instead of an empty one. An envelope whose
181
181
  * `written_at` predates the advisory is not this session's.
182
182
  */
183
- export function envelopeExists(workspaceRoot: string, since: string | null = null): boolean {
184
- const target = path.join(workspaceRoot, RECYCLE_ENVELOPE_REL);
183
+ export function envelopeExists(
184
+ workspaceRoot: string,
185
+ since: string | null = null,
186
+ sessionId: string | null = null,
187
+ ): boolean {
188
+ // Phase 2.1: the record is keyed by session, so the counter-check must look
189
+ // at THIS session's path. Checking the shared name would read a peer's
190
+ // record as proof that this session wrote one — the same cross-session
191
+ // confusion the key exists to remove, re-created in the checker.
192
+ const target = path.join(workspaceRoot, recycle_envelope_rel(sessionId));
185
193
  let raw: string;
186
194
  try {
187
195
  raw = fs.readFileSync(target, 'utf-8');
@@ -212,10 +220,10 @@ export function envelopeExists(workspaceRoot: string, since: string | null = nul
212
220
  * the next action the advisory recommended is the one that destroys the
213
221
  * session.
214
222
  */
215
- export function buildMissingEnvelopeLine(workspaceRoot: string): string {
223
+ export function buildMissingEnvelopeLine(workspaceRoot: string, sessionId: string | null = null): string {
216
224
  return (
217
225
  `recycle advised earlier, but no envelope exists at ` +
218
- `${path.join(workspaceRoot, RECYCLE_ENVELOPE_REL)} — /clear now starts the successor ` +
226
+ `${path.join(workspaceRoot, recycle_envelope_rel(sessionId))} — /clear now starts the successor ` +
219
227
  `from nothing. Re-run \`agent-config session:recycle\` and check that it prints the ` +
220
228
  `absolute path it wrote before clearing.`
221
229
  );
@@ -334,11 +342,14 @@ export function main(): number {
334
342
  // `AGENT_RECYCLE_THRESHOLD_TOKENS=0` is documented as the emergency switch
335
343
  // for the whole advisory lane, and a lane that keeps one more warn after
336
344
  // being switched off is not off.
345
+ // Raw id, not `sessionKey`: the record path is built from the id the host
346
+ // exported (`safe_stem`), never from the hashed state-file key.
347
+ const rawSessionId = payloadSessionId(payload, envelope) || null;
337
348
  const shouldWarnMissing =
338
349
  threshold !== null &&
339
350
  state.advisory_fired_at !== null &&
340
351
  (state.missing_envelope_warned_at ?? null) === null &&
341
- !envelopeExists(workspaceRoot, state.advisory_fired_at);
352
+ !envelopeExists(workspaceRoot, state.advisory_fired_at, rawSessionId);
342
353
 
343
354
  const nextState: SessionEolState = {
344
355
  schema_version: 1,
@@ -415,7 +426,7 @@ export function main(): number {
415
426
  `${JSON.stringify({
416
427
  decision: 'warn',
417
428
  reason: 'session-eol: recycle advised, no envelope written',
418
- additional_context: buildMissingEnvelopeLine(workspaceRoot),
429
+ additional_context: buildMissingEnvelopeLine(workspaceRoot, rawSessionId),
419
430
  })}\n`,
420
431
  );
421
432
  return EXIT_WARN;
@@ -45,7 +45,9 @@ const MANIFEST_PATH = path.join(
45
45
  "hook_manifest.yaml",
46
46
  );
47
47
 
48
- // (label, project-relative bridge path, install hint).
48
+ // (project-relative bridge path, one-line note shown when the bridge is not
49
+ // live). The note is printed verbatim, so a note that is not a command must
50
+ // not read as one.
49
51
  // Path may be a directory (cline) — existence => any file inside.
50
52
  //
51
53
  // Cowork has no project-scope bridge path: the Claude desktop app's
@@ -58,17 +60,17 @@ const MANIFEST_PATH = path.join(
58
60
  // Once upstream lands the fix and a stable settings location is
59
61
  // documented, swap the empty path here for that location.
60
62
  export const PLATFORM_BRIDGES: Record<string, [string, string]> = {
61
- augment: [".augment/settings.json", "src/scripts/install.py"],
62
- claude: [".claude/settings.json", "src/scripts/install.py"],
63
+ augment: [".augment/settings.json", "run `agent-config install`"],
64
+ claude: [".claude/settings.json", "run `agent-config install`"],
63
65
  cowork: [
64
66
  "",
65
67
  "upstream-blocked: anthropics/claude-code#40495 + #27398 (settings.json ignored in Cowork sandbox)",
66
68
  ],
67
- cursor: [".cursor/hooks.json", "src/scripts/install.py"],
68
- cline: [".clinerules/hooks", "src/scripts/install.py"],
69
- windsurf: [".windsurf/hooks.json", "src/scripts/install.py"],
70
- gemini: [".gemini/settings.json", "src/scripts/install.py"],
71
- copilot: ["", "rule-only fallback (no hook surface)"],
69
+ cursor: [".cursor/hooks.json", "run `agent-config install`"],
70
+ cline: [".clinerules/hooks", "run `agent-config install`"],
71
+ windsurf: [".windsurf/hooks.json", "run `agent-config install`"],
72
+ gemini: [".gemini/settings.json", "run `agent-config install`"],
73
+ copilot: ["", "rule-only fallback — this package binds no hook here"],
72
74
  };
73
75
 
74
76
  export interface PlatformRow {
@@ -180,11 +182,11 @@ export function _render_table(matrix: StatusMatrix): string {
180
182
  lines.push(` ${event.padEnd(22)} → ${concerns}`);
181
183
  }
182
184
  if (row.hint) {
183
- lines.push(` hint: run ${row.hint}`);
185
+ lines.push(` hint: ${row.hint}`);
184
186
  }
185
187
  }
186
188
  lines.push("");
187
- lines.push("Source of truth: scripts/hook_manifest.yaml");
189
+ lines.push("Source of truth: src/scripts/hook_manifest.yaml");
188
190
  lines.push("Contract: docs/contracts/hook-architecture-v1.md");
189
191
  return lines.join("\n");
190
192
  }
@@ -75,6 +75,7 @@ import { build_merge_entries } from './_lib/json_pointers.js';
75
75
  import { is_claude_builtin_name } from './_lib/claude_builtin_names.js';
76
76
  import * as installed_lock from './_lib/installed_lock.js';
77
77
  import * as mcp_bridge from './_lib/mcp_bridge.js';
78
+ import * as mcp_consent from './_lib/mcp_consent_residual.js';
78
79
  import * as scoped_projection from './_lib/scoped_projection.js';
79
80
  import * as surface_tiers from './_lib/surface_tiers.js';
80
81
  import * as global_deploy_inventory from './_lib/global_deploy_inventory.js';
@@ -90,6 +91,7 @@ import * as catalogue from './_lib/skill_catalogue.js';
90
91
  import { isInteractiveSession, type SessionProbe } from '../shared/interactiveContext.js';
91
92
  import * as claude_desktop_bundler from './_lib/claude_desktop_bundler.js';
92
93
  import * as claude_settings_hooks from './_lib/claude_settings_hooks.js';
94
+ import { hostBindings } from './hooks/host_lowering.js';
93
95
  import { find_project_root_with_anchor, load_agent_settings } from './_lib/agent_settings.js';
94
96
  import {
95
97
  LEGACY_ALL,
@@ -417,7 +419,7 @@ function yamlSafeLoad(text: string): unknown {
417
419
  }
418
420
 
419
421
  // ---------------------------------------------------------------------------
420
- // Module-level constants (install.py:51-116)
422
+ // Module-level constants.
421
423
  // ---------------------------------------------------------------------------
422
424
 
423
425
  const DEFAULT_PROFILE = 'balanced';
@@ -1022,14 +1024,6 @@ const AUGMENT_LEGACY_TRAMPOLINES: readonly string[] = [
1022
1024
  'augment-onboarding-gate.sh',
1023
1025
  'augment-context-hygiene.sh',
1024
1026
  ];
1025
- const AUGMENT_DISPATCHER_BINDINGS: ReadonlyArray<readonly [string, string]> = [
1026
- ['session_start', 'SessionStart'],
1027
- ['session_end', 'SessionEnd'],
1028
- ['stop', 'Stop'],
1029
- ['pre_tool_use', 'PreToolUse'],
1030
- ['post_tool_use', 'PostToolUse'],
1031
- ];
1032
-
1033
1027
  function _deploy_augment_trampoline(package_root: string, name: string, force: boolean): string | null {
1034
1028
  const src = path.join(package_root, 'scripts', 'hooks', name);
1035
1029
  if (!pathExists(src)) {
@@ -1073,8 +1067,8 @@ function ensure_augment_user_hooks(package_root: string, force: boolean): Record
1073
1067
  _remove_legacy_augment_trampolines();
1074
1068
 
1075
1069
  const per_event: Record<string, unknown[]> = {};
1076
- for (const [ac_event, native] of AUGMENT_DISPATCHER_BINDINGS) {
1077
- const cmd = `${dst} ${ac_event} ${native}`;
1070
+ for (const { slot, native } of hostBindings('augment')) {
1071
+ const cmd = `${dst} ${slot} ${native}`;
1078
1072
  const entry = { hooks: [{ type: 'command', command: cmd }] };
1079
1073
  (per_event[native] ??= []).push(entry);
1080
1074
  }
@@ -1130,14 +1124,6 @@ const _prune_tier_b_modules = (
1130
1124
  ): [number, Record<string, DeployResult>] =>
1131
1125
  _prune_modules_by(dr, mcp_bridge.tierBPrunePredicate(tier_b));
1132
1126
 
1133
- const CURSOR_DISPATCHER_BINDINGS: ReadonlyArray<readonly [string, string]> = [
1134
- ['session_start', 'sessionStart'],
1135
- ['session_end', 'sessionEnd'],
1136
- ['stop', 'stop'],
1137
- ['user_prompt_submit', 'beforeSubmitPrompt'],
1138
- ['post_tool_use', 'postToolUse'],
1139
- ];
1140
-
1141
1127
  function _cursor_dispatch_command(ac_event: string, native: string): string {
1142
1128
  // Hook-resilience shim (P1.4): missing binary → silent exit 0; a
1143
1129
  // present dispatcher's exit code propagates (fail_closed concerns
@@ -1152,8 +1138,8 @@ function _cursor_dispatch_command(ac_event: string, native: string): string {
1152
1138
 
1153
1139
  function ensure_cursor_bridge(project_root: string, force: boolean): Record<string, unknown>[] {
1154
1140
  const hooks: Record<string, unknown[]> = {};
1155
- for (const [ac_event, native] of CURSOR_DISPATCHER_BINDINGS) {
1156
- (hooks[native] ??= []).push({ command: _cursor_dispatch_command(ac_event, native) });
1141
+ for (const { slot, native } of hostBindings('cursor')) {
1142
+ (hooks[native] ??= []).push({ command: _cursor_dispatch_command(slot, native) });
1157
1143
  }
1158
1144
  const bridge = { version: 1, hooks };
1159
1145
  return merge_json_file(
@@ -1187,8 +1173,8 @@ function ensure_cursor_user_hooks(package_root: string, force: boolean): Record<
1187
1173
  }
1188
1174
 
1189
1175
  const hooks: Record<string, unknown[]> = {};
1190
- for (const [ac_event, native] of CURSOR_DISPATCHER_BINDINGS) {
1191
- (hooks[native] ??= []).push({ command: `${dst} ${ac_event} ${native}` });
1176
+ for (const { slot, native } of hostBindings('cursor')) {
1177
+ (hooks[native] ??= []).push({ command: `${dst} ${slot} ${native}` });
1192
1178
  }
1193
1179
 
1194
1180
  const settings_patch = { version: 1, hooks };
@@ -1200,15 +1186,6 @@ function ensure_cursor_user_hooks(package_root: string, force: boolean): Record<
1200
1186
  );
1201
1187
  }
1202
1188
 
1203
- const CLINE_DISPATCHER_BINDINGS: ReadonlyArray<readonly [string, string]> = [
1204
- ['session_start', 'TaskStart'],
1205
- ['session_start', 'TaskResume'],
1206
- ['session_end', 'TaskComplete'],
1207
- ['stop', 'TaskCancel'],
1208
- ['user_prompt_submit', 'UserPromptSubmit'],
1209
- ['post_tool_use', 'PostToolUse'],
1210
- ];
1211
-
1212
1189
  /** `shlex.quote` — POSIX shell single-quote escaping. */
1213
1190
  function shlexQuote(s: string): string {
1214
1191
  if (s === '') return "''";
@@ -1221,7 +1198,7 @@ function clineProjectHookBody(native_event: string, ac_event: string, workspace_
1221
1198
  // template uses doubled braces ({{ }}) that .format() collapses to single.
1222
1199
  return (
1223
1200
  '#!/usr/bin/env bash\n' +
1224
- '# Generated by event4u/agent-config install.py — DO NOT EDIT.\n' +
1201
+ '# Generated by event4u/agent-config — DO NOT EDIT.\n' +
1225
1202
  `# Project-scope Cline hook for ${native_event} → agent-config ${ac_event}.\n` +
1226
1203
  '# Phase 7.6 (docs/contracts/hook-architecture-v1.md).\n' +
1227
1204
  'set -u\n' +
@@ -1249,14 +1226,14 @@ function ensure_cline_bridge(project_root: string, force: boolean): void {
1249
1226
 
1250
1227
  const workspace_quoted = shlexQuote(resolvePath(project_root));
1251
1228
  let written = 0;
1252
- for (const [ac_event, native_event] of CLINE_DISPATCHER_BINDINGS) {
1253
- const target = path.join(hooks_dir, native_event);
1254
- const body = clineProjectHookBody(native_event, ac_event, workspace_quoted);
1229
+ for (const { slot, native } of hostBindings('cline')) {
1230
+ const target = path.join(hooks_dir, native);
1231
+ const body = clineProjectHookBody(native, slot, workspace_quoted);
1255
1232
  if (pathExists(target) && readText(target) === body && !force) {
1256
1233
  continue;
1257
1234
  }
1258
1235
  if (pathExists(target) && !force) {
1259
- skip(`.clinerules/hooks/${native_event} exists, needs update (use --force)`);
1236
+ skip(`.clinerules/hooks/${native} exists, needs update (use --force)`);
1260
1237
  continue;
1261
1238
  }
1262
1239
  writeText(target, body);
@@ -1292,13 +1269,13 @@ function ensure_cline_user_hooks(package_root: string, force: boolean): void {
1292
1269
  }
1293
1270
 
1294
1271
  const trampoline_quoted = shlexQuote(trampoline);
1295
- for (const [ac_event, native_event] of CLINE_DISPATCHER_BINDINGS) {
1296
- const wrapper = path.join(CLINE_USER_DIR, native_event);
1272
+ for (const { slot, native } of hostBindings('cline')) {
1273
+ const wrapper = path.join(CLINE_USER_DIR, native);
1297
1274
  const body =
1298
1275
  '#!/usr/bin/env bash\n' +
1299
- '# Generated by event4u/agent-config install.py — DO NOT EDIT.\n' +
1300
- `# User-scope Cline hook for ${native_event} → agent-config ${ac_event}.\n` +
1301
- `exec ${trampoline_quoted} ${ac_event} ${native_event}\n`;
1276
+ '# Generated by event4u/agent-config — DO NOT EDIT.\n' +
1277
+ `# User-scope Cline hook for ${native} → agent-config ${slot}.\n` +
1278
+ `exec ${trampoline_quoted} ${slot} ${native}\n`;
1302
1279
  if (pathExists(wrapper) && readText(wrapper) === body && !force) {
1303
1280
  continue;
1304
1281
  }
@@ -1307,12 +1284,6 @@ function ensure_cline_user_hooks(package_root: string, force: boolean): void {
1307
1284
  }
1308
1285
  }
1309
1286
 
1310
- const WINDSURF_DISPATCHER_BINDINGS: ReadonlyArray<readonly [string, string]> = [
1311
- ['session_start', 'post_setup_worktree'],
1312
- ['user_prompt_submit', 'pre_user_prompt'],
1313
- ['stop', 'post_cascade_response'],
1314
- ];
1315
-
1316
1287
  function _windsurf_dispatch_command(ac_event: string, native: string): string {
1317
1288
  // Hook-resilience shim (P1.4): missing binary → silent exit 0; a
1318
1289
  // present dispatcher's exit code propagates (fail_closed concerns
@@ -1327,9 +1298,9 @@ function _windsurf_dispatch_command(ac_event: string, native: string): string {
1327
1298
 
1328
1299
  function ensure_windsurf_bridge(project_root: string, force: boolean): Record<string, unknown>[] {
1329
1300
  const hooks: Record<string, unknown[]> = {};
1330
- for (const [ac_event, native] of WINDSURF_DISPATCHER_BINDINGS) {
1301
+ for (const { slot, native } of hostBindings('windsurf')) {
1331
1302
  (hooks[native] ??= []).push({
1332
- command: _windsurf_dispatch_command(ac_event, native),
1303
+ command: _windsurf_dispatch_command(slot, native),
1333
1304
  show_output: false,
1334
1305
  });
1335
1306
  }
@@ -1365,9 +1336,9 @@ function ensure_windsurf_user_hooks(package_root: string, force: boolean): Recor
1365
1336
  }
1366
1337
 
1367
1338
  const hooks: Record<string, unknown[]> = {};
1368
- for (const [ac_event, native] of WINDSURF_DISPATCHER_BINDINGS) {
1339
+ for (const { slot, native } of hostBindings('windsurf')) {
1369
1340
  (hooks[native] ??= []).push({
1370
- command: `${dst} ${ac_event} ${native}`,
1341
+ command: `${dst} ${slot} ${native}`,
1371
1342
  show_output: false,
1372
1343
  });
1373
1344
  }
@@ -1381,14 +1352,6 @@ function ensure_windsurf_user_hooks(package_root: string, force: boolean): Recor
1381
1352
  );
1382
1353
  }
1383
1354
 
1384
- const GEMINI_DISPATCHER_BINDINGS: ReadonlyArray<readonly [string, string, string]> = [
1385
- ['session_start', 'SessionStart', ''],
1386
- ['session_end', 'SessionEnd', ''],
1387
- ['stop', 'AfterAgent', ''],
1388
- ['user_prompt_submit', 'BeforeAgent', ''],
1389
- ['post_tool_use', 'AfterTool', '.*'],
1390
- ];
1391
-
1392
1355
  function _gemini_dispatch_command(ac_event: string, native: string): string {
1393
1356
  // Hook-resilience shim (P1.4): missing binary → silent exit 0; a
1394
1357
  // present dispatcher's exit code propagates (fail_closed concerns
@@ -1405,10 +1368,10 @@ function _gemini_hooks_dict(
1405
1368
  command_factory: (ac_event: string, native: string) => string,
1406
1369
  ): Record<string, unknown[]> {
1407
1370
  const out: Record<string, unknown[]> = {};
1408
- for (const [ac_event, native, matcher] of GEMINI_DISPATCHER_BINDINGS) {
1371
+ for (const { slot, native, matcher } of hostBindings('gemini')) {
1409
1372
  (out[native] ??= []).push({
1410
1373
  matcher,
1411
- hooks: [{ type: 'command', command: command_factory(ac_event, native) }],
1374
+ hooks: [{ type: 'command', command: command_factory(slot, native) }],
1412
1375
  });
1413
1376
  }
1414
1377
  return out;
@@ -1724,15 +1687,37 @@ function ensure_kiro_bridge(project_root: string, force: boolean): void {
1724
1687
 
1725
1688
  // --- Post-install smoke test ---
1726
1689
 
1727
- const SMOKE_PROBE_EVENTS: ReadonlyArray<readonly [string, string]> = [
1690
+ /**
1691
+ * Platforms whose bridge the smoke probe exercises, with the slot it probes.
1692
+ *
1693
+ * The NATIVE event is derived from `host_lowering.yaml`, never written here.
1694
+ * The hand-written list this replaced probed `['cursor', 'beforeShellExecution']`
1695
+ * — an event no cursor bridge has ever written — so the probe could not fail on
1696
+ * a real binding regression, which is the only thing it exists to catch. A
1697
+ * platform whose table row does not carry the probed slot is a hard error, not
1698
+ * a skip: a silent skip is how the previous list stayed wrong.
1699
+ */
1700
+ const SMOKE_PROBE_SLOTS: ReadonlyArray<readonly [string, string]> = [
1728
1701
  ['augment', 'session_start'],
1729
- ['claude', 'SessionStart'],
1730
- ['cursor', 'beforeShellExecution'],
1702
+ ['claude', 'session_start'],
1703
+ ['cursor', 'session_start'],
1731
1704
  ['cline', 'session_start'],
1732
- ['windsurf', 'post_setup_worktree'],
1733
- ['gemini', 'SessionStart'],
1705
+ ['windsurf', 'session_start'],
1706
+ ['gemini', 'session_start'],
1734
1707
  ];
1735
1708
 
1709
+ export function smokeProbeEvents(): ReadonlyArray<readonly [string, string]> {
1710
+ return SMOKE_PROBE_SLOTS.map(([platform, slot]) => {
1711
+ const binding = hostBindings(platform).find((b) => b.slot === slot);
1712
+ if (binding === undefined) {
1713
+ throw new Error(
1714
+ `install smoke probe: host_lowering.yaml has no \`${slot}\` binding for \`${platform}\``,
1715
+ );
1716
+ }
1717
+ return [platform, binding.native] as const;
1718
+ });
1719
+ }
1720
+
1736
1721
  const SMOKE_BRIDGE_PATHS: Record<string, string> = {
1737
1722
  augment: '.augment/settings.json',
1738
1723
  claude: '.claude/settings.json',
@@ -1787,7 +1772,7 @@ function _smoke_test_hooks(project_root: string, package_root: string): number {
1787
1772
  const skipped: string[] = [];
1788
1773
  const passed: string[] = [];
1789
1774
 
1790
- for (const [platform, native] of SMOKE_PROBE_EVENTS) {
1775
+ for (const [platform, native] of smokeProbeEvents()) {
1791
1776
  const rel_bridge = SMOKE_BRIDGE_PATHS[platform] ?? '';
1792
1777
  const bridge_path = rel_bridge ? path.join(project_root, rel_bridge) : null;
1793
1778
  const bridge_present = Boolean(
@@ -2619,15 +2604,6 @@ function _is_agent_config_source_repo(project_root: string): [boolean, string] {
2619
2604
  }
2620
2605
  }
2621
2606
 
2622
- const installer_self = path.join(project_root, 'scripts', 'install.py');
2623
- try {
2624
- if (isFile(installer_self) && resolvePath(installer_self) === resolvePath(_HERE)) {
2625
- return [true, 'src/scripts/install.py (self)'];
2626
- }
2627
- } catch {
2628
- /* OSError → pass */
2629
- }
2630
-
2631
2607
  return [false, ''];
2632
2608
  }
2633
2609
 
@@ -4028,7 +4004,7 @@ function _merge_tools_aliases(tools: string | null, ai: string | null): string {
4028
4004
  return items.length > 0 ? items.join(',') : 'all';
4029
4005
  }
4030
4006
 
4031
- const PROG = 'install.py';
4007
+ const PROG = 'agent-config install';
4032
4008
  // Verbatim argparse usage block at COLUMNS=80 (captured from the .py). The
4033
4009
  // `--help` BODY (per-flag descriptions) is a documented divergence — argparse
4034
4010
  // re-wraps it to the live terminal width, which is not worth reproducing; the
@@ -5146,11 +5122,9 @@ function _main_project_install(
5146
5122
  ...ensure_augment_bridge(project_root, opts.force),
5147
5123
  ];
5148
5124
  if (_is_tool_enabled(tools, 'claude-code')) {
5149
- merged_keys_by_tool['claude-code'] = [
5150
- ...ensure_claude_bridge(project_root, opts.force),
5151
- ...ensure_mcp_bridge(project_root, opts.force),
5152
- ];
5125
+ merged_keys_by_tool['claude-code'] = ensure_claude_bridge(project_root, opts.force);
5153
5126
  }
5127
+ mcp_bridge.registerMcpHosts(merge_json_file, project_root, opts.force, package_root, tools, merged_keys_by_tool);
5154
5128
  if (_is_tool_enabled(tools, 'cursor')) {
5155
5129
  merged_keys_by_tool['cursor'] = ensure_cursor_bridge(project_root, opts.force);
5156
5130
  }
@@ -5251,6 +5225,7 @@ function _main_project_install(
5251
5225
  );
5252
5226
  process.stdout.write('\n');
5253
5227
  }
5228
+ mcp_consent.printResiduals(tools, state.QUIET);
5254
5229
  if (_is_tool_enabled(tools, 'claude-code')) {
5255
5230
  const team_hint = _team_setup_hint_line(project_root);
5256
5231
  if (team_hint !== null) {
@@ -5364,10 +5339,6 @@ export {
5364
5339
  ROOCODE_MARKER,
5365
5340
  ZED_MARKER,
5366
5341
  // ADR-200 py2ts: re-exported for test_install_snapshot.ts twin
5367
- CURSOR_DISPATCHER_BINDINGS,
5368
- CLINE_DISPATCHER_BINDINGS,
5369
- WINDSURF_DISPATCHER_BINDINGS,
5370
- GEMINI_DISPATCHER_BINDINGS,
5371
5342
  ensure_cursor_bridge,
5372
5343
  ensure_cline_bridge,
5373
5344
  ensure_mcp_bridge,
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # Interactive Anthropic-API-key installer for scripts/skill_trigger_eval.py.
2
+ # Interactive Anthropic-API-key installer for src/scripts/skill_trigger_eval.ts.
3
3
  #
4
4
  # Reads the key with `read -s` so it never echoes to the terminal and
5
5
  # never lands in shell history or scrollback. Writes atomically to
@@ -7,7 +7,7 @@
7
7
  # ~/.config/agent-config/anthropic.key is read as a fallback by the
8
8
  # loaders so pre-2.4 installs keep working until the namespace shim runs.
9
9
  #
10
- # Contract — companion to scripts/skill_trigger_eval.py:
10
+ # Contract — companion to src/scripts/skill_trigger_eval.ts:
11
11
  # - File path: $HOME/.event4u/agent-config/anthropic.key
12
12
  # - File mode: 0600 (owner read/write only)
13
13
  # - Key format: must start with `sk-ant-`