@event4u/agent-config 9.30.0 → 9.32.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 (94) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +60 -292
  3. package/dist/agent-src/contexts/execution/host-capability-manifest.md +32 -1
  4. package/dist/agent-src/rules/delegation-policy.md +11 -1
  5. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  6. package/dist/agent-src/templates/scripts/work_engine/delivery_state.ts +6 -0
  7. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +332 -0
  8. package/dist/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +106 -1
  9. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +50 -15
  10. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +49 -15
  11. package/dist/agent-src/templates/scripts/work_engine/state.ts +74 -0
  12. package/dist/agent-src/templates/scripts/work_engine/state_io.ts +6 -0
  13. package/dist/cli-delegate/chunk-2WELHO3P.js +582 -0
  14. package/dist/cli-delegate/{chunk-IYTGIG2Y.js → chunk-542DZR4P.js} +118 -148
  15. package/dist/cli-delegate/{chunk-66OVWGB5.js → chunk-AG47LAGQ.js} +1 -1
  16. package/dist/cli-delegate/{chunk-MP3JT3IU.js → chunk-DS6BLDMG.js} +1 -1
  17. package/dist/cli-delegate/{chunk-O3CO5BFB.js → chunk-EJITJBJX.js} +12 -1
  18. package/dist/cli-delegate/{chunk-EQY4FLJB.js → chunk-ITIQSGOQ.js} +3 -3
  19. package/dist/cli-delegate/{chunk-LCFJIQ4J.js → chunk-JEJABGKB.js} +110 -6
  20. package/dist/cli-delegate/chunk-PZWRPRWP.js +38 -0
  21. package/dist/cli-delegate/{chunk-BTJNRUX3.js → chunk-QEEB4LHO.js} +1 -1
  22. package/dist/cli-delegate/{chunk-DDUNNFYX.js → chunk-UVKNIJH2.js} +2 -2
  23. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  24. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  25. package/dist/cli-delegate/cmd_conformance.js +154 -41
  26. package/dist/cli-delegate/cmd_doctor.js +4 -3
  27. package/dist/cli-delegate/cmd_explain.js +2 -2
  28. package/dist/cli-delegate/cmd_export.js +4 -4
  29. package/dist/cli-delegate/cmd_handoff.js +168 -59
  30. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  31. package/dist/cli-delegate/cmd_migrate.js +2 -2
  32. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  33. package/dist/cli-delegate/cmd_prune.js +4 -4
  34. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  35. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  36. package/dist/cli-delegate/cmd_session_recycle.js +19 -4
  37. package/dist/cli-delegate/cmd_settings_get.js +2 -55
  38. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  39. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  40. package/dist/cli-delegate/cmd_sync.js +4 -4
  41. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  42. package/dist/cli-delegate/cmd_update.js +1 -1
  43. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  44. package/dist/cli-delegate/cmd_validate.js +4 -4
  45. package/dist/cli-delegate/cmd_versions.js +1 -1
  46. package/dist/discovery/deprecation-report.md +1 -1
  47. package/dist/discovery/discovery-manifest.json +3 -3
  48. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  49. package/dist/discovery/discovery-manifest.summary.md +1 -1
  50. package/dist/discovery/orphan-report.md +1 -1
  51. package/dist/discovery/packs.json +2 -2
  52. package/dist/discovery/trust-report.md +1 -1
  53. package/dist/discovery/workspaces.json +2 -2
  54. package/dist/hooks/dispatch.js +1411 -810
  55. package/dist/install/install.mjs +121 -16
  56. package/dist/mcp/registry-manifest.json +1 -1
  57. package/dist/mcp/server.json +2 -2
  58. package/dist/mcp/server.mjs +365 -143
  59. package/dist/scripts/_lib/agent_settings.js +111 -2
  60. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  61. package/dist/server/schemas/settings.js +2 -2
  62. package/dist/server/schemas/settings.js.map +1 -1
  63. package/package.json +1 -1
  64. package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +6 -0
  65. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +332 -0
  66. package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +106 -1
  67. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +50 -15
  68. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +49 -15
  69. package/src/agent-src/templates/scripts/work_engine/state.ts +74 -0
  70. package/src/agent-src/templates/scripts/work_engine/state_io.ts +6 -0
  71. package/src/config/pack-size-budget.json +3 -2
  72. package/src/scripts/_cli/cmd_session_recycle.ts +25 -0
  73. package/src/scripts/_cli/explain_last/inputs.ts +13 -0
  74. package/src/scripts/_cli/handoff_generate.ts +36 -1
  75. package/src/scripts/_cli/handoff_sessions.ts +140 -9
  76. package/src/scripts/_lib/agent_settings.ts +119 -2
  77. package/src/scripts/_lib/carrier_divergence.ts +120 -2
  78. package/src/scripts/_lib/envelope_grounding.ts +206 -0
  79. package/src/scripts/_lib/host_capability.ts +79 -0
  80. package/src/scripts/_lib/session_eol.ts +85 -0
  81. package/src/scripts/_lib/subagent_capsule.ts +256 -1
  82. package/src/scripts/check_secret_leak.test.ts +43 -0
  83. package/src/scripts/check_secret_leak.ts +12 -0
  84. package/src/scripts/handoff_context_hook.ts +66 -18
  85. package/src/scripts/hooks/session_eol_hook.ts +12 -12
  86. package/src/scripts/hooks/turn_end_gate_hook.ts +296 -88
  87. package/src/scripts/language_mirror_hook.ts +69 -16
  88. package/src/scripts/measure_turn_end_gate.ts +129 -51
  89. package/src/scripts/report_carrier_divergence.ts +143 -30
  90. package/src/scripts/report_conformance_funnel.ts +18 -4
  91. package/src/scripts/routing_doctor.ts +25 -2
  92. package/src/scripts/schemas/dod.schema.json +45 -0
  93. package/src/server/schemas/settings.ts +2 -2
  94. package/dist/cli-delegate/chunk-RVGBH4XK.js +0 -142
@@ -2,7 +2,7 @@ import { createRequire as __acCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  DEFAULT_PROJECT_FILE,
4
4
  logger
5
- } from "./chunk-LCFJIQ4J.js";
5
+ } from "./chunk-JEJABGKB.js";
6
6
 
7
7
  // src/scripts/config/profiles.ts
8
8
  import * as fs2 from "node:fs";
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
2
  import {
3
3
  resolve_project_root
4
- } from "./chunk-LCFJIQ4J.js";
4
+ } from "./chunk-JEJABGKB.js";
5
5
  import {
6
6
  resolvePackageRoot
7
7
  } from "./chunk-MCJYCQPF.js";
@@ -108,6 +108,15 @@ function atomic_write_json(target, payload, options = {}) {
108
108
  const body = _py_json_dumps(payload, indent) + "\n";
109
109
  _atomic_write_text(targetPath, body);
110
110
  }
111
+ function atomic_write_text(target, text) {
112
+ if (is_replay_mode()) {
113
+ return;
114
+ }
115
+ const targetPath = path.resolve(target);
116
+ const state_dir = path.dirname(targetPath);
117
+ fs.mkdirSync(state_dir, { recursive: true });
118
+ _atomic_write_text(targetPath, text);
119
+ }
111
120
  function _acquire_lock(lock_path) {
112
121
  const fd = fs.openSync(lock_path, "a", 420);
113
122
  const held = lock_path + ".held";
@@ -157,5 +166,7 @@ function _atomic_write_text(target, text) {
157
166
  }
158
167
 
159
168
  export {
160
- atomic_write_json
169
+ is_replay_mode,
170
+ atomic_write_json,
171
+ atomic_write_text
161
172
  };
@@ -2,10 +2,10 @@ import { createRequire as __acCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  ZodFirstPartyTypeKind,
4
4
  settingsSchema
5
- } from "./chunk-DDUNNFYX.js";
5
+ } from "./chunk-UVKNIJH2.js";
6
6
  import {
7
7
  main
8
- } from "./chunk-MP3JT3IU.js";
8
+ } from "./chunk-DS6BLDMG.js";
9
9
  import {
10
10
  RULE_SOURCE_REL
11
11
  } from "./chunk-BKO2AZ7Z.js";
@@ -48,7 +48,7 @@ import {
48
48
  import {
49
49
  find_project_root_with_anchor,
50
50
  load_agent_settings
51
- } from "./chunk-LCFJIQ4J.js";
51
+ } from "./chunk-JEJABGKB.js";
52
52
  import {
53
53
  event4u_root,
54
54
  legacy_xdg_root,
@@ -4,11 +4,67 @@ import {
4
4
  write_target
5
5
  } from "./chunk-NDJLNC6Q.js";
6
6
 
7
+ // src/shared/settingsCarveOut.ts
8
+ var SETTINGS_CARVE_OUT = [
9
+ {
10
+ key: "projection.mode",
11
+ reader: "src/scripts/install.ts:3409 _resolve_scoped_projection",
12
+ absentResolvesTo: "legacy-all",
13
+ reason: "The template fallback applies only when NO global settings file exists. Once a file exists, an absent key means legacy-all by documented contract, so an existing install is never silently narrowed on upgrade."
14
+ },
15
+ {
16
+ key: "projection.rule_workspaces",
17
+ reader: "src/install/rule_scope.ts:96 ruleScopeFromSettings",
18
+ absentResolvesTo: "null \u2014 LEGACY_ALL, i.e. every rule ships including maintainer-only ones",
19
+ reason: "Same upgrade contract as projection.mode, and the failure is louder: an absent list widens the projection instead of narrowing it."
20
+ },
21
+ {
22
+ key: "discipline_profile",
23
+ reader: "work_engine/_lib/agent_settings.ts:1263",
24
+ absentResolvesTo: "essential, unconditionally",
25
+ reason: "The template ships `auto`, which resolves to `off` on any measured-null model, any non-Claude host, and any host exposing no model id. Absent skips that resolution entirely and loads the essential tier everywhere."
26
+ },
27
+ {
28
+ key: "chat_history.frequency",
29
+ reader: "src/scripts/chat_history.ts:1140",
30
+ absentResolvesTo: "per_phase",
31
+ reason: "Canonical substitution is `per_turn`. Absent gives coarser capture than the shipped default \u2014 an audit-thinning change, which the class contract's own test 8 (what can the attacker HIDE) names as the severe direction."
32
+ },
33
+ {
34
+ key: "profile.id",
35
+ reader: "src/scripts/config/profiles.ts:221 resolve_profile",
36
+ absentResolvesTo: "the id `developer`, but a DEGRADED profile \u2014 packs: [], personas: [], hints: []",
37
+ reason: "Exactly the projection.mode shape: the template default is honoured only when no settings file exists. With a file present the id resolves but its profile body does not load, so the install silently loses every pack and persona."
38
+ },
39
+ {
40
+ key: "quality.local_auto_run",
41
+ reader: "src/scripts/lint_roadmap_ci_steps.ts:106",
42
+ absentResolvesTo: "true \u2014 which DISABLES the CI-step gate",
43
+ reason: "Inverted polarity, and the most dangerous row in the set: the template ships `false`, which ARMS the gate. Omitting the key would disarm a quality gate for every fresh install while the reference page says it is on."
44
+ },
45
+ {
46
+ key: "onboarding.onboarded",
47
+ reader: "src/scripts/onboarding_gate_hook.ts:99",
48
+ absentResolvesTo: "gate skipped, as if onboarding had already completed",
49
+ reason: "Absent behaves like `onboarded: true`, not like the template default `false`. Omitting it would skip the onboarding gate on precisely the fresh installs the gate exists for."
50
+ },
51
+ {
52
+ key: "chat_history.enabled",
53
+ reader: "src/scripts/chat_history.ts:1025 _read_chat_history_enabled",
54
+ absentResolvesTo: "false",
55
+ reason: "The reader falls back to false while the template ships true. This one is privacy-shaped: fixing the reader would start recording history for every install whose file lacks the key. Writing it explicitly keeps the decision visible in the file the user can read."
56
+ }
57
+ ];
58
+ function carveOutKeys() {
59
+ return SETTINGS_CARVE_OUT.map((c) => c.key);
60
+ }
61
+
7
62
  // src/scripts/_lib/agent_settings.ts
8
63
  import { createRequire } from "node:module";
9
64
  import * as fs from "node:fs";
10
65
  import * as os from "node:os";
11
66
  import * as path from "node:path";
67
+ import { fileURLToPath } from "node:url";
12
68
  var _require = createRequire(import.meta.url);
13
69
  var Logger = class {
14
70
  name = "scripts._lib.agent_settings";
@@ -117,7 +173,45 @@ var MERGEABLE_KEYS = [
117
173
  "knowledge.global_sharing.freshness.hypothesis_after_days",
118
174
  "knowledge.global_sharing.freshness.stale_after_days"
119
175
  ];
120
- var _DEFAULTS = {};
176
+ var TEMPLATE_RELATIVE = path.join("src", "config", "agent-settings.template.yml");
177
+ var _PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
178
+ function default_template_path() {
179
+ return path.join(_PACKAGE_ROOT, TEMPLATE_RELATIVE);
180
+ }
181
+ function _is_placeholder(value) {
182
+ return typeof value === "string" && /^__[A-Z0-9_]+__$/.test(value);
183
+ }
184
+ function _prune_template(tree, excluded, prefix = "") {
185
+ const out = {};
186
+ for (const [key, value] of Object.entries(tree)) {
187
+ const dotted = prefix === "" ? key : `${prefix}.${key}`;
188
+ if (excluded.has(dotted) || _is_placeholder(value)) {
189
+ continue;
190
+ }
191
+ if (_is_plain_dict(value)) {
192
+ const child = _prune_template(value, excluded, dotted);
193
+ if (Object.keys(child).length > 0) {
194
+ out[key] = child;
195
+ }
196
+ continue;
197
+ }
198
+ out[key] = value;
199
+ }
200
+ return out;
201
+ }
202
+ var _template_defaults_cache = null;
203
+ function template_defaults(template_path) {
204
+ const use_cache = template_path === void 0;
205
+ if (use_cache && _template_defaults_cache !== null) {
206
+ return _deepcopy(_template_defaults_cache);
207
+ }
208
+ const parsed = _read_yaml(template_path ?? default_template_path()) ?? {};
209
+ const pruned = _prune_template(parsed, new Set(carveOutKeys()));
210
+ if (use_cache) {
211
+ _template_defaults_cache = pruned;
212
+ }
213
+ return _deepcopy(pruned);
214
+ }
121
215
  var ANCHOR_AGENT_SETTINGS = "agent-settings";
122
216
  var ANCHOR_AGENTS_DIR = "agents-dir";
123
217
  var ANCHOR_GIT = "git";
@@ -386,6 +480,7 @@ function load_agent_settings(options = {}) {
386
480
  const user_global_path = options.user_global_path ?? null;
387
481
  const verbose = options.verbose ?? false;
388
482
  const cwd = options.cwd ?? null;
483
+ const template_path = options.template_path ?? null;
389
484
  const user_global_raw = {};
390
485
  for (const p of user_global_path ? [user_global_path] : user_global_settings_paths()) {
391
486
  _deep_merge(user_global_raw, _read_yaml(p) ?? {});
@@ -398,7 +493,7 @@ function load_agent_settings(options = {}) {
398
493
  );
399
494
  }
400
495
  const cascade = _resolve_cascade_paths(cwd, project_path);
401
- const merged = _deep_copy_defaults(_DEFAULTS);
496
+ const merged = template_defaults(template_path ?? void 0);
402
497
  _deep_merge(merged, user_global_filtered);
403
498
  for (const p of cascade) {
404
499
  const layer = _read_yaml(p) ?? {};
@@ -555,16 +650,25 @@ function _deep_merge(dst, src) {
555
650
  }
556
651
  }
557
652
  }
558
- function _deep_copy_defaults(src) {
559
- const out = {};
560
- _deep_merge(out, src);
561
- return out;
653
+ function _deepcopy(value) {
654
+ if (Array.isArray(value)) {
655
+ return value.map((v) => _deepcopy(v));
656
+ }
657
+ if (_is_plain_dict(value)) {
658
+ const out = {};
659
+ for (const [k, v] of Object.entries(value)) {
660
+ out[k] = _deepcopy(v);
661
+ }
662
+ return out;
663
+ }
664
+ return value;
562
665
  }
563
666
  function _is_plain_dict(value) {
564
667
  return typeof value === "object" && value !== null && !Array.isArray(value);
565
668
  }
566
669
 
567
670
  export {
671
+ SETTINGS_CARVE_OUT,
568
672
  logger,
569
673
  DEFAULT_PROJECT_FILE,
570
674
  LOCAL_PROJECT_FILE,
@@ -0,0 +1,38 @@
1
+ import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
+
3
+ // src/scripts/_lib/git_common_dir.ts
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ function realpath_or_self(p) {
7
+ try {
8
+ return fs.realpathSync(p);
9
+ } catch {
10
+ return p;
11
+ }
12
+ }
13
+ function git_common_dir(project_root) {
14
+ const dot_git = path.join(project_root, ".git");
15
+ try {
16
+ const st = fs.statSync(dot_git);
17
+ if (st.isDirectory()) {
18
+ return realpath_or_self(dot_git);
19
+ }
20
+ const m = fs.readFileSync(dot_git, "utf-8").match(/^gitdir:\s*(.+)\s*$/m);
21
+ if (m === null) {
22
+ return null;
23
+ }
24
+ const gitdir = path.resolve(project_root, m[1].trim());
25
+ const commondir_file = path.join(gitdir, "commondir");
26
+ if (fs.existsSync(commondir_file)) {
27
+ const common = path.resolve(gitdir, fs.readFileSync(commondir_file, "utf-8").trim());
28
+ return realpath_or_self(common);
29
+ }
30
+ return realpath_or_self(gitdir);
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ export {
37
+ git_common_dir
38
+ };
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-MMJL4TIX.js";
5
5
  import {
6
6
  load_agent_settings
7
- } from "./chunk-LCFJIQ4J.js";
7
+ } from "./chunk-JEJABGKB.js";
8
8
 
9
9
  // src/scripts/chat_history.ts
10
10
  import { createHash } from "node:crypto";
@@ -4549,10 +4549,10 @@ var settingsSchema = external_exports.object({
4549
4549
  "Stop-slot turn-end gate (road-to-conformance-round5 Phase 3). Default off, and it is the only concern that can REFUSE a turn-end rather than a tool call \u2014 so it soaks behind an explicit opt-in before it binds. Round 5 measured the reason: both blocking carriers reached zero violations, neither advisory carrier did, and 19 wrong-language replies survived a pin that had fired 26-35 seconds earlier. A refused turn continues in the SAME turn and is never refused twice."
4550
4550
  ),
4551
4551
  promissory: external_exports.boolean().default(true).describe(
4552
- "Detector A of the turn-end gate: refuse a turn whose closing paragraph promises work not yet performed while asking the user nothing (20 measured occurrences). Inert while hooks.turn_end_gate.enabled is false."
4552
+ "Detector A of the turn-end gate: refuse a turn whose closing paragraph promises work not yet performed while asking the user nothing (20 measured occurrences). No fire rate is quoted here on purpose: the measured rate moved twice while the instrument itself was being corrected, so a number in a shipped description would be a moving target. Re-derive it with `measure-turn-end-gate --store <transcript dir>` against your own corpus before arming the gate. Inert while hooks.turn_end_gate.enabled is false."
4553
4553
  ),
4554
4554
  language: external_exports.boolean().default(true).describe(
4555
- "Detector B of the turn-end gate: refuse a turn whose user-visible prose is not in the pinned language, with fenced code, inline code, quoted tool output, tables, URLs, paths and identifiers excluded first (19 measured occurrences). Fires 18.0% of turns on the measured corpus, which is why the master switch is off by default. Inert while hooks.turn_end_gate.enabled is false."
4555
+ "Detector B of the turn-end gate: refuse a turn whose reply is not in the pinned language (19 measured occurrences). Fenced code, inline code, quoted tool output, tables, URLs and paths are stripped first, and the remainder is then scored LEAD-FIRST by the same classifier that sets the pin \u2014 so the verdict usually comes from the reply opening, not from all of it. Sharing that classifier is deliberate: it stops the pin and the detector disagreeing about a turn. Accepted opt-in spellings for the switches below include YAML true/false and yes/no/on/off. Inert while hooks.turn_end_gate.enabled is false."
4556
4556
  )
4557
4557
  }).default({})
4558
4558
  }),
@@ -7,7 +7,7 @@ import {
7
7
  import {
8
8
  ProjectRootError,
9
9
  resolve_project_root
10
- } from "./chunk-LCFJIQ4J.js";
10
+ } from "./chunk-JEJABGKB.js";
11
11
  import "./chunk-NDJLNC6Q.js";
12
12
  import "./chunk-J55WI3RR.js";
13
13
 
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
2
  import {
3
3
  resolve_project_root
4
- } from "./chunk-LCFJIQ4J.js";
4
+ } from "./chunk-JEJABGKB.js";
5
5
  import "./chunk-NDJLNC6Q.js";
6
6
  import "./chunk-J55WI3RR.js";
7
7
 
@@ -14,12 +14,15 @@ import {
14
14
  _run_checks,
15
15
  _run_checks_no_manifest,
16
16
  _scan_foreign
17
- } from "./chunk-IYTGIG2Y.js";
17
+ } from "./chunk-542DZR4P.js";
18
18
  import "./chunk-FTAISGDQ.js";
19
19
  import {
20
- atomic_write_json
21
- } from "./chunk-O3CO5BFB.js";
20
+ atomic_write_json,
21
+ atomic_write_text,
22
+ is_replay_mode
23
+ } from "./chunk-EJITJBJX.js";
22
24
  import "./chunk-LVRYBKTI.js";
25
+ import "./chunk-PZWRPRWP.js";
23
26
  import "./chunk-736JCWLM.js";
24
27
  import {
25
28
  atomicAppendLine,
@@ -28,7 +31,7 @@ import {
28
31
  } from "./chunk-63WSP6YZ.js";
29
32
  import "./chunk-GRRPEPRN.js";
30
33
  import "./chunk-NXFWFAYV.js";
31
- import "./chunk-LCFJIQ4J.js";
34
+ import "./chunk-JEJABGKB.js";
32
35
  import "./chunk-NDJLNC6Q.js";
33
36
  import {
34
37
  detectToolPresence
@@ -140,6 +143,37 @@ function comparePair(a, b) {
140
143
  }
141
144
  return "body-diff";
142
145
  }
146
+ function splitFrontmatter(text) {
147
+ if (!text.startsWith("---")) return { hadFrontmatter: false, frontmatter: "", body: text };
148
+ const end = text.indexOf("\n---", 3);
149
+ if (end === -1) return { hadFrontmatter: false, frontmatter: "", body: text };
150
+ const afterFence = text.indexOf("\n", end + 1);
151
+ return {
152
+ hadFrontmatter: true,
153
+ frontmatter: text.slice(3, end),
154
+ body: afterFence === -1 ? "" : text.slice(afterFence + 1)
155
+ };
156
+ }
157
+ function proseEqual(a, b) {
158
+ const sa = splitFrontmatter(a);
159
+ const sb = splitFrontmatter(b);
160
+ if (!sa.hadFrontmatter && !sb.hadFrontmatter) return false;
161
+ const leading = /^\n+/;
162
+ return sa.body.replace(leading, "") === sb.body.replace(leading, "");
163
+ }
164
+ function frontmatterPaths(text) {
165
+ const { hadFrontmatter, frontmatter } = splitFrontmatter(text);
166
+ if (!hadFrontmatter) return null;
167
+ const lines = frontmatter.split("\n");
168
+ const start = lines.findIndex((l) => /^paths:/.test(l));
169
+ if (start === -1) return null;
170
+ const block = [lines[start]];
171
+ for (const line of lines.slice(start + 1)) {
172
+ if (/^\s/.test(line) && line.trim() !== "") block.push(line);
173
+ else break;
174
+ }
175
+ return block.map((l) => l.trim()).join(" ");
176
+ }
143
177
  function censusRuleDir(dir) {
144
178
  let files = 0;
145
179
  let chars = 0;
@@ -223,7 +257,11 @@ function humanAuthoredLead(text) {
223
257
  var EXIT_ALLOW = 0;
224
258
  var EXIT_WARN = 2;
225
259
  var STATE_FILE = path2.join("agents", "state", "language-mirror.json");
226
- var PIN_LOST_MARKER = path2.join("agents", "state", "language-mirror.pin-lost");
260
+ var PIN_LOST_DIR = path2.join("agents", "state", "language-mirror.pin-lost");
261
+ function pinLostMarker(session_id) {
262
+ const safe = (session_id || "unknown-session").replace(/[^A-Za-z0-9._-]/g, "_");
263
+ return path2.join(PIN_LOST_DIR, `${safe}.marker`);
264
+ }
227
265
  function _isObject2(v) {
228
266
  return typeof v === "object" && v !== null && !Array.isArray(v);
229
267
  }
@@ -362,35 +400,49 @@ function _extractPrompt(payload) {
362
400
  }
363
401
  return "";
364
402
  }
365
- function _setPinLost(consumer_root) {
403
+ function _sessionId(envelope) {
404
+ const v = envelope["session_id"];
405
+ return typeof v === "string" && v.trim() !== "" ? v : "unknown-session";
406
+ }
407
+ function _migrateLegacyMarker(consumer_root) {
408
+ const legacy = path2.join(consumer_root, PIN_LOST_DIR);
409
+ try {
410
+ if (fs2.statSync(legacy).isFile()) {
411
+ fs2.rmSync(legacy, { force: true });
412
+ }
413
+ } catch {
414
+ }
415
+ }
416
+ function _setPinLost(consumer_root, session_id) {
417
+ if (is_replay_mode()) return;
418
+ _migrateLegacyMarker(consumer_root);
366
419
  try {
367
- const target = path2.join(consumer_root, PIN_LOST_MARKER);
368
- fs2.mkdirSync(path2.dirname(target), { recursive: true });
369
- fs2.writeFileSync(target, (/* @__PURE__ */ new Date()).toISOString());
420
+ atomic_write_text(path2.join(consumer_root, pinLostMarker(session_id)), (/* @__PURE__ */ new Date()).toISOString());
370
421
  } catch {
371
422
  }
372
423
  }
373
- function _pinLost(consumer_root) {
424
+ function _pinLost(consumer_root, session_id) {
374
425
  try {
375
- return fs2.existsSync(path2.join(consumer_root, PIN_LOST_MARKER));
426
+ return fs2.existsSync(path2.join(consumer_root, pinLostMarker(session_id)));
376
427
  } catch {
377
428
  return false;
378
429
  }
379
430
  }
380
- function _clearPinLost(consumer_root) {
431
+ function _clearPinLost(consumer_root, session_id) {
432
+ if (is_replay_mode()) return;
381
433
  try {
382
- fs2.rmSync(path2.join(consumer_root, PIN_LOST_MARKER), { force: true });
434
+ fs2.rmSync(path2.join(consumer_root, pinLostMarker(session_id)), { force: true });
383
435
  } catch {
384
436
  }
385
437
  }
386
- function _reEmitAfterCompaction(consumer_root) {
387
- if (!_pinLost(consumer_root)) {
438
+ function _reEmitAfterCompaction(consumer_root, session_id) {
439
+ if (!_pinLost(consumer_root, session_id)) {
388
440
  return EXIT_ALLOW;
389
441
  }
390
442
  const previous = _loadState(path2.join(consumer_root, STATE_FILE));
391
443
  const language = previous.language;
392
444
  const source = previous.source === "system-locale" ? "system-locale" : "prompt";
393
- _clearPinLost(consumer_root);
445
+ _clearPinLost(consumer_root, session_id);
394
446
  if (language !== "de" && language !== "en") {
395
447
  return EXIT_ALLOW;
396
448
  }
@@ -419,11 +471,11 @@ function run(stdin_text, options) {
419
471
  const payload = _isObject2(envelope["payload"]) ? envelope["payload"] : envelope;
420
472
  const event = typeof envelope["event"] === "string" ? envelope["event"] : "";
421
473
  if (event === "pre_compact") {
422
- _setPinLost(options.consumer_root);
474
+ _setPinLost(options.consumer_root, _sessionId(envelope));
423
475
  return EXIT_ALLOW;
424
476
  }
425
477
  if (event === "post_tool_use") {
426
- return _reEmitAfterCompaction(options.consumer_root);
478
+ return _reEmitAfterCompaction(options.consumer_root, _sessionId(envelope));
427
479
  }
428
480
  const prompt = _extractPrompt(payload);
429
481
  if (!prompt) {
@@ -1944,10 +1996,7 @@ function _ruleType(file) {
1944
1996
  } catch {
1945
1997
  return "";
1946
1998
  }
1947
- if (!text.startsWith("---")) return "";
1948
- const end = text.indexOf("\n---", 3);
1949
- const fm = end === -1 ? text : text.slice(0, end);
1950
- const m = /^type:\s*"?([a-z]+)"?/m.exec(fm);
1999
+ const m = /^type:\s*"?([a-z]+)"?/m.exec(splitFrontmatter(text).frontmatter);
1951
2000
  return m === null ? "" : m[1];
1952
2001
  }
1953
2002
  function compareCarriers(projectDir, globalDir, sourceDir) {
@@ -1959,6 +2008,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
1959
2008
  shared: 0,
1960
2009
  identical: [],
1961
2010
  provenanceOnly: [],
2011
+ frontmatterOnly: [],
2012
+ pathsScopeDiff: [],
1962
2013
  bodyDiff: [],
1963
2014
  projectOnly: [],
1964
2015
  globalOnly: [],
@@ -1983,11 +2034,12 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
1983
2034
  }
1984
2035
  out.shared += 1;
1985
2036
  let verdict;
2037
+ let projectCopy;
2038
+ let globalCopy;
1986
2039
  try {
1987
- verdict = comparePair(
1988
- fs8.readFileSync(path8.join(projectDir, rule)),
1989
- fs8.readFileSync(path8.join(globalDir, rule))
1990
- );
2040
+ projectCopy = fs8.readFileSync(path8.join(projectDir, rule));
2041
+ globalCopy = fs8.readFileSync(path8.join(globalDir, rule));
2042
+ verdict = comparePair(projectCopy, globalCopy);
1991
2043
  } catch {
1992
2044
  out.shared -= 1;
1993
2045
  out.unreadable.push(rule);
@@ -1995,7 +2047,16 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
1995
2047
  }
1996
2048
  if (verdict === "identical") out.identical.push(rule);
1997
2049
  else if (verdict === "provenance-only") out.provenanceOnly.push(rule);
1998
- else out.bodyDiff.push(rule);
2050
+ else {
2051
+ const projectText = projectCopy.toString("utf-8");
2052
+ const globalText = globalCopy.toString("utf-8");
2053
+ if (proseEqual(projectText, globalText)) {
2054
+ out.frontmatterOnly.push(rule);
2055
+ if (frontmatterPaths(projectText) !== frontmatterPaths(globalText)) {
2056
+ out.pathsScopeDiff.push(rule);
2057
+ }
2058
+ } else out.bodyDiff.push(rule);
2059
+ }
1999
2060
  }
2000
2061
  return out;
2001
2062
  }
@@ -2023,7 +2084,9 @@ function render2(d) {
2023
2084
  lines.push(` shared rule names ${d.shared}`);
2024
2085
  lines.push(` byte-identical ${d.identical.length}`);
2025
2086
  lines.push(` differ ONLY in the install stamp ${d.provenanceOnly.length}`);
2026
- lines.push(` differ in BODY ${d.bodyDiff.length}`);
2087
+ lines.push(` differ ONLY in frontmatter ${d.frontmatterOnly.length}`);
2088
+ lines.push(` of which disagree on \`paths:\` ${d.pathsScopeDiff.length} \u2190 ACTIONABLE`);
2089
+ lines.push(` differ in PROSE ${d.bodyDiff.length}`);
2027
2090
  if (d.unreadable.length > 0) {
2028
2091
  lines.push(` unreadable on one side ${d.unreadable.length} (${d.unreadable.join(", ")})`);
2029
2092
  lines.push(" Not counted as shared and NOT a body difference \u2014 a dangling entry is");
@@ -2033,15 +2096,43 @@ function render2(d) {
2033
2096
  lines.push(` present at global scope only ${d.globalOnly.length}`);
2034
2097
  lines.push("");
2035
2098
  if (d.bodyDiff.length > 0) {
2036
- lines.push(" BODY DIVERGENCE \u2014 the two carriers deliver different text for these rules.");
2037
- lines.push(" Both copies reach the model, with no precedence marker between them, so a");
2038
- lines.push(" claim one copy retracts can be re-asserted by the other. PRECEDENCE: the");
2039
- lines.push(" project projection is generated from src/ at this commit and wins; the");
2040
- lines.push(" global install is a release snapshot. Close it by reinstalling the global");
2041
- lines.push(" copy, or by reading the project copy as authoritative.");
2099
+ lines.push(" PROSE DIVERGENCE \u2014 the two carriers deliver different governed text for");
2100
+ lines.push(" these rules. Both copies reach the model, and the host resolves NOTHING:");
2101
+ lines.push(" rules without a `paths` key load at launch with the same priority as");
2102
+ lines.push(" CLAUDE.md, with no precedence marker between the layers, so a claim one");
2103
+ lines.push(" copy retracts can be re-asserted by the other and which text binds is");
2104
+ lines.push(" UNDEFINED. Cited, not inferred \u2014 agents/evidence/analysis/");
2105
+ lines.push(" claude-code-rules-dir-contract.md (host 2.1.226, the host's own docs plus");
2106
+ lines.push(" a first-party observation). The project copy is the NEWER text, which is a");
2107
+ lines.push(" fact about recency and NOT a host precedence rule: close the divergence by");
2108
+ lines.push(" reinstalling the global copy, or decide which text binds \u2014 do not assume");
2109
+ lines.push(" the newer one already won.");
2042
2110
  for (const r of d.bodyDiff) lines.push(` - ${r}`);
2043
2111
  lines.push("");
2044
2112
  }
2113
+ if (d.pathsScopeDiff.length > 0) {
2114
+ lines.push(` \`paths:\` SCOPE DISAGREEMENT (${String(d.pathsScopeDiff.length)}) \u2014 same prose, different load schedule.`);
2115
+ lines.push(" ACT ON THESE. `paths` is the one frontmatter key this host reads: with it, a");
2116
+ lines.push(" rule fires when a matching file is read and is NOT re-injected after");
2117
+ lines.push(" /compact; without it, the rule loads unconditionally at launch. So the copy");
2118
+ lines.push(" that lacks `paths` DEFEATS the other copy's scoping \u2014 the rule the project");
2119
+ lines.push(" layer meant to deliver conditionally is delivered always, and an obligation");
2120
+ lines.push(" a maintainer deliberately scoped is silently unscoped on any machine that");
2121
+ lines.push(" carries both. Identical prose does not make this inert.");
2122
+ for (const r of d.pathsScopeDiff) lines.push(` - ${r}`);
2123
+ lines.push("");
2124
+ }
2125
+ const inert = d.frontmatterOnly.length - d.pathsScopeDiff.length;
2126
+ if (inert > 0) {
2127
+ lines.push(` ${String(inert)} further pair(s) differ ONLY in frontmatter the host does not read \u2014`);
2128
+ lines.push(" prose byte-identical, `paths` in agreement, nothing to act on. Two writers,");
2129
+ lines.push(" two policies: `generate-tools` emits `paths` where a rule is path-scoped and");
2130
+ lines.push(" nothing otherwise; `install.ts` writes agent-config's own vocabulary plus its");
2131
+ lines.push(" ownership stamp, and none of those keys is one this host reads");
2132
+ lines.push(" (claude-code-rules-dir-contract.md). Reported as a count on purpose: naming");
2133
+ lines.push(" every pair would bury the two classes above, which are the actionable ones.");
2134
+ lines.push("");
2135
+ }
2045
2136
  if (d.provenanceOnly.length > 0) {
2046
2137
  lines.push(` ${String(d.provenanceOnly.length)} pair(s) differ only by the installer's package:/source_path: stamp.`);
2047
2138
  lines.push(" Not a content difference and NOT a defect: install.ts stamps every installed");
@@ -2077,9 +2168,18 @@ function render2(d) {
2077
2168
  }
2078
2169
  if (d.bodyDiff.length === 0) {
2079
2170
  lines.push("");
2080
- lines.push(" No body divergence. The condition is transient \u2014 it returns whenever the");
2081
- lines.push(" checkout moves ahead of the installed release \u2014 so this is a reading of");
2082
- lines.push(" right now, not a property of the repo. Re-run it, do not cite it.");
2171
+ lines.push(" No prose divergence \u2014 no rule's governed text differs between the carriers.");
2172
+ if (d.pathsScopeDiff.length > 0) {
2173
+ lines.push(" NOT an all-clear: the `paths:` disagreements above are still open, and they");
2174
+ lines.push(" change when a rule loads rather than what it says.");
2175
+ }
2176
+ lines.push(" The condition is transient \u2014 it returns whenever the checkout moves ahead of");
2177
+ lines.push(" the installed release \u2014 so this is a reading of right now, not a property of");
2178
+ lines.push(" the repo. Re-run it, do not cite it. That holds doubly here: the reading also");
2179
+ lines.push(" depends on WHICH GENERATION of the emitter produced the project tree (a");
2180
+ lines.push(" frontmatter-carrying symlink tree and a frontmatter-less real-file tree");
2181
+ lines.push(" classify the same commit differently), so a number from another checkout is");
2182
+ lines.push(" not comparable to this one.");
2083
2183
  }
2084
2184
  return lines.join("\n");
2085
2185
  }
@@ -2680,12 +2780,25 @@ function render5(r) {
2680
2780
  lines.push(" substituted with dist/, which would answer a different question.");
2681
2781
  } else {
2682
2782
  lines.push(
2683
- ` Divergence: ${d.shared} shared \xB7 ${d.bodyDiff.length} body-diff \xB7 ${d.provenanceOnly.length} provenance-only \xB7 ${d.projectOnly.length} project-only \xB7 ${d.globalOnly.length} global-only` + (d.manualOnlyGlobal.length > 0 ? ` (${d.manualOnlyGlobal.length} by design, ADR-004 manual)` : "")
2783
+ ` Divergence: ${d.shared} shared \xB7 ${d.bodyDiff.length} prose-diff \xB7 ${d.frontmatterOnly.length} frontmatter-only (${d.pathsScopeDiff.length} on \`paths:\`) \xB7 ${d.provenanceOnly.length} provenance-only \xB7 ${d.projectOnly.length} project-only \xB7 ${d.globalOnly.length} global-only` + (d.manualOnlyGlobal.length > 0 ? ` (${d.manualOnlyGlobal.length} by design, ADR-004 manual)` : "")
2684
2784
  );
2685
2785
  if (d.bodyDiff.length > 0) {
2686
- lines.push(` BODY DIVERGENCE \u2014 both copies reach the model: ${d.bodyDiff.join(", ")}`);
2687
- lines.push(" Precedence: the project projection is generated from src/ at this commit");
2688
- lines.push(" and wins; the global install is a release snapshot.");
2786
+ lines.push(` PROSE DIVERGENCE \u2014 both copies reach the model: ${d.bodyDiff.join(", ")}`);
2787
+ lines.push(" The host resolves nothing: both layers load at launch at the same");
2788
+ lines.push(" priority with no precedence marker, so which text binds is UNDEFINED");
2789
+ lines.push(" (claude-code-rules-dir-contract.md). The project copy is merely the");
2790
+ lines.push(" newer one \u2014 recency, not precedence.");
2791
+ }
2792
+ if (d.pathsScopeDiff.length > 0) {
2793
+ lines.push(` \`paths:\` SCOPE DISAGREEMENT (${String(d.pathsScopeDiff.length)}) \u2014 same prose, different load`);
2794
+ lines.push(" schedule. `paths` is the one frontmatter key the host reads, so the copy");
2795
+ lines.push(" without it defeats the other copy's scoping: a deliberately scoped");
2796
+ lines.push(" obligation is delivered unconditionally. Act on these.");
2797
+ }
2798
+ const inertFm = d.frontmatterOnly.length - d.pathsScopeDiff.length;
2799
+ if (inertFm > 0) {
2800
+ lines.push(` ${String(inertFm)} further pair(s) differ only in frontmatter the host does not read \u2014`);
2801
+ lines.push(" prose byte-identical, `paths` in agreement, nothing to act on.");
2689
2802
  }
2690
2803
  if (d.unreadable.length > 0) {
2691
2804
  lines.push(` Unreadable on one side (${d.unreadable.length}) \u2014 a broken install, not a disagreement.`);
@@ -66,15 +66,16 @@ import {
66
66
  _wizard_state_path,
67
67
  main,
68
68
  shutilWhich
69
- } from "./chunk-IYTGIG2Y.js";
69
+ } from "./chunk-542DZR4P.js";
70
70
  import "./chunk-FTAISGDQ.js";
71
- import "./chunk-O3CO5BFB.js";
71
+ import "./chunk-EJITJBJX.js";
72
72
  import "./chunk-LVRYBKTI.js";
73
+ import "./chunk-PZWRPRWP.js";
73
74
  import "./chunk-736JCWLM.js";
74
75
  import "./chunk-63WSP6YZ.js";
75
76
  import "./chunk-GRRPEPRN.js";
76
77
  import "./chunk-NXFWFAYV.js";
77
- import "./chunk-LCFJIQ4J.js";
78
+ import "./chunk-JEJABGKB.js";
78
79
  import "./chunk-NDJLNC6Q.js";
79
80
  import "./chunk-OY53J6II.js";
80
81
  import "./chunk-MCJYCQPF.js";