@event4u/agent-config 8.6.0 → 8.8.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 (118) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +81 -0
  3. package/CONTRIBUTING.md +25 -0
  4. package/README.md +7 -4
  5. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +1 -1
  6. package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +28 -16
  7. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +15 -0
  8. package/dist/agent-src/rules/active-remediation.md +92 -0
  9. package/dist/agent-src/rules/broken-access-control.md +106 -0
  10. package/dist/agent-src/rules/communication-through-line.md +42 -0
  11. package/dist/agent-src/rules/decision-revisit-gate.md +8 -0
  12. package/dist/agent-src/rules/minimal-safe-diff.md +18 -1
  13. package/dist/agent-src/rules/prefer-enums-over-literals.md +65 -0
  14. package/dist/agent-src/rules/senior-engineering-discipline.md +82 -0
  15. package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +105 -0
  16. package/dist/agent-src/skills/ai-code-blindspots/evals/triggers.json +52 -0
  17. package/dist/agent-src/skills/authz-review/SKILL.md +21 -4
  18. package/dist/agent-src/skills/dependency-upgrade/SKILL.md +20 -0
  19. package/dist/agent-src/skills/frontend-render-security/SKILL.md +104 -0
  20. package/dist/agent-src/skills/frontend-render-security/evals/triggers.json +52 -0
  21. package/dist/agent-src/skills/roadmap-writing/SKILL.md +7 -0
  22. package/dist/agent-src/skills/skill-writing/SKILL.md +4 -0
  23. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +100 -0
  24. package/dist/agent-src/skills/supply-chain-intake/evals/triggers.json +52 -0
  25. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +43 -2
  26. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  27. package/dist/agent-src/templates/roadmaps.md +24 -0
  28. package/dist/discovery/deprecation-report.md +1 -1
  29. package/dist/discovery/discovery-manifest.json +196 -16
  30. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  31. package/dist/discovery/discovery-manifest.summary.md +3 -3
  32. package/dist/discovery/orphan-report.md +1 -1
  33. package/dist/discovery/packs.json +13 -5
  34. package/dist/discovery/trust-report.md +11 -11
  35. package/dist/discovery/workspaces.json +12 -4
  36. package/dist/install/emit_host_rules_cli.js +185 -0
  37. package/dist/install/emit_host_rules_cli.js.map +1 -0
  38. package/dist/install/install.mjs +11 -2
  39. package/dist/install/plan.js +4 -1
  40. package/dist/install/plan.js.map +1 -1
  41. package/dist/install/rule_scope.js +109 -0
  42. package/dist/install/rule_scope.js.map +1 -0
  43. package/dist/install/rule_scope_cli.js +77 -0
  44. package/dist/install/rule_scope_cli.js.map +1 -0
  45. package/dist/install/wizard-plan.js +14 -1
  46. package/dist/install/wizard-plan.js.map +1 -1
  47. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  48. package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
  49. package/dist/mcp/registry-manifest.json +4 -4
  50. package/dist/router.json +1 -1
  51. package/dist/scripts/_lib/agent_settings.js +963 -0
  52. package/dist/scripts/_lib/agent_settings.js.map +1 -0
  53. package/dist/scripts/_lib/agent_src.js +607 -0
  54. package/dist/scripts/_lib/agent_src.js.map +1 -0
  55. package/dist/scripts/_lib/claude_settings_hooks.js +209 -0
  56. package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -0
  57. package/dist/scripts/_lib/model_tier.js +64 -0
  58. package/dist/scripts/_lib/model_tier.js.map +1 -0
  59. package/dist/scripts/_lib/script_output.js +140 -0
  60. package/dist/scripts/_lib/script_output.js.map +1 -0
  61. package/dist/scripts/_lib/user_global_paths.js +297 -0
  62. package/dist/scripts/_lib/user_global_paths.js.map +1 -0
  63. package/dist/scripts/condense.js +2181 -0
  64. package/dist/scripts/condense.js.map +1 -0
  65. package/dist/server/routes/install.js +18 -2
  66. package/dist/server/routes/install.js.map +1 -1
  67. package/dist/server/schemas/settings.js +4 -1
  68. package/dist/server/schemas/settings.js.map +1 -1
  69. package/docs/CLAIMS.md +30 -2
  70. package/docs/architecture.md +2 -2
  71. package/docs/catalog.md +14 -0
  72. package/docs/contracts/mcp-tool-inventory.md +31 -30
  73. package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +6 -0
  74. package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +2 -2
  75. package/docs/decisions/ADR-117-subagents-auto-default-on.md +88 -0
  76. package/docs/decisions/INDEX.md +1 -0
  77. package/docs/experiences/content_creator.md +2 -0
  78. package/docs/experiences/finance.md +2 -0
  79. package/docs/experiences/founder.md +2 -0
  80. package/docs/experiences/ops.md +2 -0
  81. package/docs/featured-skills.md +2 -2
  82. package/docs/getting-started-by-role.md +1 -1
  83. package/docs/getting-started.md +1 -1
  84. package/docs/governance-advantage.md +1 -1
  85. package/docs/guidelines/agent-infra/memory-access.md +12 -0
  86. package/docs/proof.md +45 -3
  87. package/docs/release-runbook.md +138 -0
  88. package/docs/second-brain-scope.md +73 -0
  89. package/docs/succession.md +80 -0
  90. package/package.json +2 -2
  91. package/src/config/agent-settings.template.yml +29 -8
  92. package/src/install/emit_host_rules_cli.ts +190 -0
  93. package/src/install/plan.ts +11 -1
  94. package/src/install/rule_scope.ts +120 -0
  95. package/src/install/rule_scope_cli.ts +79 -0
  96. package/src/install/wizard-plan.ts +22 -1
  97. package/src/scripts/_lib/orchestration_gate.ts +12 -8
  98. package/src/scripts/_lib/orchestration_savings.ts +127 -0
  99. package/src/scripts/build_proof.ts +38 -0
  100. package/src/scripts/chat_history.ts +79 -7
  101. package/src/scripts/check_trigger_eval_presence.ts +171 -0
  102. package/src/scripts/domain_soundness_status.ts +236 -0
  103. package/src/scripts/generate_index.ts +14 -0
  104. package/src/scripts/hot_context_hook.ts +47 -2
  105. package/src/scripts/install.sh +118 -4
  106. package/src/scripts/lint_behavioural_eval_freshness.ts +140 -0
  107. package/src/scripts/mcp_server/consumer_tool_catalog.json +22 -3
  108. package/src/scripts/mcp_server/tools.ts +159 -1
  109. package/src/scripts/memory_lookup.ts +184 -6
  110. package/src/scripts/memory_replay.ts +285 -0
  111. package/src/scripts/orchestration_savings_report.ts +100 -0
  112. package/src/scripts/schemas/domain-truth.schema.json +94 -0
  113. package/src/scripts/schemas/evals.schema.json +113 -0
  114. package/src/scripts/second_brain_score.ts +186 -0
  115. package/src/scripts/session_memory_index.ts +94 -0
  116. package/src/scripts/skill_eval_coverage.ts +318 -0
  117. package/src/scripts/trigger_eval_grandfather.json +226 -0
  118. package/src/server/schemas/settings.ts +11 -2
@@ -64,12 +64,14 @@ projection:
64
64
  # rule_workspaces: [engineering]
65
65
  # Default flip to a scoped value is a human release gate — see
66
66
  # agents/settings/contexts/consumer-scoping-audit-2026-07-07.md.
67
- # rule_workspaces: []
67
+ # An EMPTY list is inactive (= legacy-all); only a non-empty list scopes.
68
+ rule_workspaces: []
68
69
  #
69
70
  # Optional second axis, same semantics per pack ids (packs.yml). When set,
70
71
  # a non-kernel rule also needs a `packs:` intersection to project — e.g.
71
72
  # deselecting `frontend-design` removes ui-audit-gate + design-fidelity.
72
- # rule_packs: []
73
+ # Empty = inactive, same as rule_workspaces.
74
+ rule_packs: []
73
75
 
74
76
  # --- Discipline profile (successor knob, opt-in until the evidence gates pass) ---
75
77
  #
@@ -522,13 +524,21 @@ subagents:
522
524
  # on = auto-dispatch delegable tasks without asking (surface the choice
523
525
  # in one line).
524
526
  #
525
- # Initial SHIPPED default is `ask` on hosts whose capability manifest reports
527
+ # SHIPPED default is `on` on hosts whose capability manifest reports
526
528
  # `subagent_spawn: true`, and `off` on hosts without a subagent primitive.
527
- # The shipped default flips toward `on` (host-gated) only behind the Phase-6
528
- # benchmark gate of road-to-auto-subagent-orchestration.md — never on an
529
- # unmeasured claim. Auto-dispatch is also a no-op wherever the host manifest
530
- # reports `subagent_spawn: false`, regardless of this value.
531
- auto: ask
529
+ # Auto-dispatch stays a no-op wherever the manifest reports
530
+ # `subagent_spawn: false`, regardless of this value.
531
+ #
532
+ # The default was `ask` until 2026-07-09, when it was flipped to `on` after a
533
+ # re-evaluation (agents/settings/contexts/orchestration-default-flip-verdict.md
534
+ # § 2026-07-09; ADR-117). The flip rests on a bounded-downside argument, NOT a
535
+ # passed benchmark: delegable slices are already cost-routed to the cheapest
536
+ # capable tier, classification fires only on real structural signals (never
537
+ # unstructured or tiny work), and every return is verified — so `on` cannot
538
+ # delegate frontier-priced or speculative work. `resolveShippedDefault()`
539
+ # remains the telemetry-driven DEMOTION gate: a measured regression demotes
540
+ # back to `ask`.
541
+ auto: on
532
542
 
533
543
  # Route delegable sub-tasks to the lowest-capable model tier (cost + speed
534
544
  # via model downshift). true (default) = downshift per declared model_tier;
@@ -693,6 +703,17 @@ memory:
693
703
  # Example: ["api[_-]?key", "/Users/[a-z]+/Library"]
694
704
  redact_patterns: []
695
705
 
706
+ # Opt-in compact memory index at session start
707
+ # (road-to-memory-retrieval-economy Phase 5). When `on`, session_start
708
+ # injects a compact index of curated entries — id + title + ~tokens,
709
+ # hard cap 30 rows — through the hot-context hook surface. Bodies are
710
+ # never injected; the model fetches full entries via memory_get on
711
+ # demand. Default OFF: the ship-criterion (measured hit-rate gain at
712
+ # bounded fixed cost) is unproven — off unless proven.
713
+ # off = (default) no session-start index.
714
+ # on = inject the compact index (spotlighted as DATA).
715
+ session_index: "off"
716
+
696
717
  # --- Knowledge cards: global cross-project sharing (ADR-100) ---
697
718
  #
698
719
  # Structure-grounding v2. Promote *expensive* (remote) knowledge cards from
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Consumer host-native rule emission
4
+ * (road-to-request-scoped-rule-load Phase 1b Step 3).
5
+ *
6
+ * Before this CLI, consumer Cursor got a symlink farm to raw `.md` (no
7
+ * `.mdc`, no auto-attach globs) and consumer `.windsurfrules` was built by
8
+ * a bash concatenator that STRIPPED all frontmatter — destroying the
9
+ * host-native trigger signal Phase 2 added to the projection path.
10
+ *
11
+ * ## Recorded decision — install-time emission, not dist artifacts
12
+ *
13
+ * Phase 1b Step 3 asked for one recorded choice: pre-built host artifacts
14
+ * in `dist/` vs emission at install time. Decision: INSTALL-TIME EMISSION,
15
+ * re-using the exported condense emitters (`_emit_cursor_mdc`,
16
+ * `_emit_windsurf_rule`, `strip_frontmatter`) verbatim. Rationale:
17
+ * - a new `dist/agent-src/hosts/` tree would cascade through the
18
+ * discovery manifest, artefact checksums, condensation hashes and the
19
+ * always-budget gates for what is derived data;
20
+ * - dist artifacts are scope-agnostic — install-time emission runs AFTER
21
+ * the Phase-1b rule filter, so scoped installs get scoped host files
22
+ * for free;
23
+ * - re-using the exported emitters keeps consumer output byte-identical
24
+ * to the maintainer projection (Pipeline B never drifts from A).
25
+ * The trade-off (node required at install time) matches the rule-scope
26
+ * resolver: no node → install.sh keeps its legacy fallback surfaces.
27
+ *
28
+ * Usage:
29
+ * emit_host_rules_cli.ts --rules-dir <installed rules dir>
30
+ * --project-root <consumer root>
31
+ * --tools cursor,windsurf
32
+ *
33
+ * Exit codes: 0 emitted · 2 usage error · 1 emission failure (caller falls
34
+ * back to the legacy surface).
35
+ */
36
+ import * as fs from 'node:fs';
37
+ import * as path from 'node:path';
38
+ import process from 'node:process';
39
+ import { fileURLToPath, pathToFileURL } from 'node:url';
40
+
41
+ import {
42
+ _emit_cursor_mdc,
43
+ _emit_windsurf_rule,
44
+ strip_frontmatter,
45
+ } from '../scripts/condense.js';
46
+
47
+ function _ruleFiles(rulesDir: string): string[] {
48
+ return fs
49
+ .readdirSync(rulesDir)
50
+ .filter((n) => n.endsWith('.md'))
51
+ .sort()
52
+ .map((n) => path.join(rulesDir, n))
53
+ .filter((p) => {
54
+ try {
55
+ return fs.statSync(p).isFile();
56
+ } catch {
57
+ return false;
58
+ }
59
+ });
60
+ }
61
+
62
+ /** Remove entries not in `valid` (mirror of condense's `_clean_modern_dir`). */
63
+ function _cleanDir(dir: string, valid: ReadonlySet<string>): void {
64
+ let entries: string[];
65
+ try {
66
+ entries = fs.readdirSync(dir);
67
+ } catch {
68
+ return;
69
+ }
70
+ for (const name of entries) {
71
+ if (name === 'README.md' || valid.has(name)) {
72
+ continue;
73
+ }
74
+ const full = path.join(dir, name);
75
+ try {
76
+ if (fs.lstatSync(full).isDirectory()) {
77
+ fs.rmSync(full, { recursive: true, force: true });
78
+ } else {
79
+ fs.unlinkSync(full);
80
+ }
81
+ } catch {
82
+ /* best-effort clean */
83
+ }
84
+ }
85
+ }
86
+
87
+ export function emitCursor(rulesDir: string, projectRoot: string): number {
88
+ const targetDir = path.join(projectRoot, '.cursor', 'rules');
89
+ const files = _ruleFiles(rulesDir);
90
+ const valid = new Set<string>();
91
+ for (const src of files) {
92
+ const name = `${path.basename(src, '.md')}.mdc`;
93
+ _emit_cursor_mdc(src, path.join(targetDir, name));
94
+ valid.add(name);
95
+ }
96
+ _cleanDir(targetDir, valid);
97
+ return files.length;
98
+ }
99
+
100
+ export function emitWindsurf(rulesDir: string, projectRoot: string): number {
101
+ const perRuleDir = path.join(projectRoot, '.windsurf', 'rules');
102
+ const files = _ruleFiles(rulesDir);
103
+ const valid = new Set<string>();
104
+ for (const src of files) {
105
+ const name = path.basename(src);
106
+ _emit_windsurf_rule(src, path.join(perRuleDir, name));
107
+ valid.add(name);
108
+ }
109
+ _cleanDir(perRuleDir, valid);
110
+
111
+ // Concatenated legacy surface — same format as the projection-path
112
+ // generator (condense.generate_windsurfrules), consumer-sourced header.
113
+ const parts = ['# Auto-generated from .augment/rules/ — do not edit directly\n'];
114
+ for (const src of files) {
115
+ const content = strip_frontmatter(fs.readFileSync(src, 'utf-8'));
116
+ parts.push(`---\n\n${content.trim()}\n`);
117
+ }
118
+ fs.writeFileSync(path.join(projectRoot, '.windsurfrules'), parts.join('\n') + '\n', 'utf-8');
119
+ return files.length;
120
+ }
121
+
122
+ export function main(argv: readonly string[] = process.argv.slice(2)): number {
123
+ let rulesDir = '';
124
+ let projectRoot = '';
125
+ let tools: string[] = [];
126
+ for (let i = 0; i < argv.length; i += 1) {
127
+ const arg = argv[i];
128
+ if (arg === '--rules-dir') {
129
+ rulesDir = argv[i + 1] ?? '';
130
+ i += 1;
131
+ } else if (arg === '--project-root') {
132
+ projectRoot = argv[i + 1] ?? '';
133
+ i += 1;
134
+ } else if (arg === '--tools') {
135
+ tools = (argv[i + 1] ?? '').split(',').filter((t) => t.length > 0);
136
+ i += 1;
137
+ } else {
138
+ process.stderr.write(`emit_host_rules_cli: unknown argument: ${arg}\n`);
139
+ return 2;
140
+ }
141
+ }
142
+ if (rulesDir === '' || projectRoot === '' || tools.length === 0) {
143
+ process.stderr.write(
144
+ 'usage: emit_host_rules_cli.ts --rules-dir <dir> --project-root <dir> --tools cursor,windsurf\n',
145
+ );
146
+ return 2;
147
+ }
148
+ if (!fs.existsSync(rulesDir)) {
149
+ process.stderr.write(`emit_host_rules_cli: rules dir not found: ${rulesDir}\n`);
150
+ return 1;
151
+ }
152
+ try {
153
+ for (const tool of tools) {
154
+ if (tool === 'cursor') {
155
+ const n = emitCursor(rulesDir, path.resolve(projectRoot));
156
+ process.stdout.write(`cursor: ${n} .mdc rule(s)\n`);
157
+ } else if (tool === 'windsurf') {
158
+ const n = emitWindsurf(rulesDir, path.resolve(projectRoot));
159
+ process.stdout.write(`windsurf: ${n} rule(s) + .windsurfrules\n`);
160
+ } else {
161
+ process.stderr.write(`emit_host_rules_cli: unknown tool: ${tool}\n`);
162
+ return 2;
163
+ }
164
+ }
165
+ return 0;
166
+ } catch (e) {
167
+ process.stderr.write(
168
+ `emit_host_rules_cli: emission failed: ${e instanceof Error ? e.message : String(e)}\n`,
169
+ );
170
+ return 1;
171
+ }
172
+ }
173
+
174
+ function _isCliEntry(): boolean {
175
+ if (process.argv[1] === undefined) return false;
176
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
177
+ if (import.meta.url === argvUrl) return true;
178
+ try {
179
+ return (
180
+ fs.realpathSync(fileURLToPath(import.meta.url)) ===
181
+ fs.realpathSync(path.resolve(process.argv[1]))
182
+ );
183
+ } catch {
184
+ return false;
185
+ }
186
+ }
187
+
188
+ if (_isCliEntry()) {
189
+ process.exit(main());
190
+ }
@@ -46,6 +46,13 @@ export interface PlanSource {
46
46
  readonly destDir: string;
47
47
  /** Kind recorded on every {@link FileEntry} produced from this source. */
48
48
  readonly kind: FileKind;
49
+ /**
50
+ * Optional per-file predicate (absolute source path → include?).
51
+ * Files rejected here never enter the plan — the rule-scoping filter
52
+ * for `rules` sources (road-to-request-scoped-rule-load Phase 1b).
53
+ * Absent = include everything (legacy behaviour).
54
+ */
55
+ readonly fileFilter?: (srcFile: string) => boolean;
49
56
  }
50
57
 
51
58
  /** Inputs the planner needs to walk source trees and produce an {@link InstallPlan}. */
@@ -147,7 +154,10 @@ export function buildInstallPlan(inputs: PlanInputs): InstallPlan {
147
154
 
148
155
  for (const source of inputs.sources) {
149
156
  const bucket = filesByTool[source.toolId] ?? [];
150
- const files = walkSourceTree(source.srcDir);
157
+ let files = walkSourceTree(source.srcDir);
158
+ if (source.fileFilter !== undefined) {
159
+ files = files.filter(source.fileFilter);
160
+ }
151
161
  for (const srcFile of files) {
152
162
  const rel = relative(source.srcDir, srcFile);
153
163
  const destPath = resolve(source.destDir, rel);
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Install-time rule scoping — Pipeline B of consumer-scoped rule projection
3
+ * (road-to-request-scoped-rule-load Phase 1b).
4
+ *
5
+ * The projection filter (`rule_in_scope`, condense.ts) covered only the
6
+ * maintainer projection; the consumer install pipeline shipped rules
7
+ * unfiltered (dead `EXCLUDE_RULES` in install.sh; no exclude at all on the
8
+ * global payload). This module is the ONE scoping surface both install
9
+ * paths consume — it deliberately re-uses `rule_in_scope` so the install
10
+ * semantics can never drift from the projection semantics.
11
+ *
12
+ * ## The `source-of-truth.md` decision (Phase 1b Step 2 — recorded here)
13
+ *
14
+ * Before this module, `install.sh` excluded `source-of-truth.md` from
15
+ * PROJECT installs (hardcoded name) while the GLOBAL payload shipped it —
16
+ * the documented contradiction. Decision: the rule is excluded from BOTH
17
+ * consumer paths, always (even under legacy-all). Rationale: its Iron Law
18
+ * ("NEVER EDIT ANY GENERATED PROJECTION … ALWAYS WORK IN src/") describes
19
+ * THIS repository's build layout; in a consumer project there is no `src/`
20
+ * authoring tree and the installed files ARE the working surface — the rule
21
+ * is not merely useless there, it actively forbids legitimate consumer
22
+ * edits. Shipping it globally was the bug; project installs had it right.
23
+ * The compat list below is the pre-flip carrier of that decision; once the
24
+ * Phase-1 human gate flips consumer defaults to scoped, the rule drops out
25
+ * via its maintainer-only workspace tag and this list can retire.
26
+ */
27
+
28
+ import * as fs from 'node:fs';
29
+ import * as path from 'node:path';
30
+
31
+ import { rule_in_scope } from '../scripts/condense.js';
32
+
33
+ /**
34
+ * Rules excluded from EVERY consumer install path regardless of scope —
35
+ * same treatment on project and global (see module doc for the decision).
36
+ */
37
+ export const COMPAT_ALWAYS_EXCLUDED: readonly string[] = ['source-of-truth.md'];
38
+
39
+ /** Workspace/pack scope read from consumer settings. `null` axis = unset. */
40
+ export interface RuleScope {
41
+ readonly workspaces: readonly string[] | null;
42
+ readonly packs: readonly string[] | null;
43
+ }
44
+
45
+ export const LEGACY_ALL: RuleScope = { workspaces: null, packs: null };
46
+
47
+ /** Non-empty string-array extraction; anything else → null (legacy-all). */
48
+ function _list(value: unknown): readonly string[] | null {
49
+ if (Array.isArray(value) && value.length > 0) {
50
+ return value.map((v) => String(v));
51
+ }
52
+ return null;
53
+ }
54
+
55
+ /**
56
+ * Extract `projection.rule_workspaces` / `projection.rule_packs` from an
57
+ * already-parsed settings object (same key contract as condense's
58
+ * `_read_projection_list` — absent / empty = legacy-all).
59
+ */
60
+ export function ruleScopeFromSettings(settings: Record<string, unknown>): RuleScope {
61
+ const proj = settings['projection'];
62
+ if (typeof proj !== 'object' || proj === null || Array.isArray(proj)) {
63
+ return LEGACY_ALL;
64
+ }
65
+ const p = proj as Record<string, unknown>;
66
+ return {
67
+ workspaces: _list(p['rule_workspaces']),
68
+ packs: _list(p['rule_packs']),
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Whether a rule FILE (path into the shipped `dist/agent-src/rules` tree)
74
+ * arrives in a consumer install under `scope`.
75
+ *
76
+ * Delegates the scoping decision to the projection-path predicate
77
+ * (`rule_in_scope` — kernel always ships, untagged axes fail safe) and adds
78
+ * the compat exclusion on top. Non-rule files (non-`.md`) always arrive.
79
+ */
80
+ export function ruleFileArrives(sourcePath: string, scope: RuleScope): boolean {
81
+ if (!sourcePath.endsWith('.md')) {
82
+ return true;
83
+ }
84
+ const basename = path.basename(sourcePath);
85
+ if (COMPAT_ALWAYS_EXCLUDED.includes(basename)) {
86
+ return false;
87
+ }
88
+ return rule_in_scope(sourcePath, scope.workspaces, scope.packs);
89
+ }
90
+
91
+ /**
92
+ * Basenames from `rulesDir` that are EXCLUDED under `scope` — the shape the
93
+ * bash installer consumes (one basename per line via the CLI below).
94
+ */
95
+ export function excludedRuleBasenames(rulesDir: string, scope: RuleScope): string[] {
96
+ let entries: string[];
97
+ try {
98
+ entries = fs.readdirSync(rulesDir).sort();
99
+ } catch {
100
+ return [];
101
+ }
102
+ const out: string[] = [];
103
+ for (const name of entries) {
104
+ if (!name.endsWith('.md')) {
105
+ continue;
106
+ }
107
+ const full = path.join(rulesDir, name);
108
+ try {
109
+ if (!fs.statSync(full).isFile()) {
110
+ continue;
111
+ }
112
+ } catch {
113
+ continue;
114
+ }
115
+ if (!ruleFileArrives(full, scope)) {
116
+ out.push(name);
117
+ }
118
+ }
119
+ return out;
120
+ }
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CLI twin surface of {@link module:install/rule_scope} for the bash
4
+ * installer (road-to-request-scoped-rule-load Phase 1b).
5
+ *
6
+ * Prints one EXCLUDED rule basename per line for the given rules source
7
+ * dir + consumer settings file. `install.sh` consumes this to replace its
8
+ * dead hardcoded `EXCLUDE_RULES` list; on any failure the caller falls back
9
+ * to the static compat exclusion (fail-safe: over-ship, never under-ship).
10
+ *
11
+ * Usage:
12
+ * rule_scope_cli.ts --rules-dir <dist/agent-src/rules> [--settings <file>]
13
+ *
14
+ * Exit codes: 0 ok (list on stdout, possibly empty) · 2 usage error.
15
+ */
16
+ import * as fs from 'node:fs';
17
+ import * as path from 'node:path';
18
+ import process from 'node:process';
19
+ import { fileURLToPath, pathToFileURL } from 'node:url';
20
+
21
+ import { load_agent_settings } from '../scripts/_lib/agent_settings.js';
22
+
23
+ import { excludedRuleBasenames, ruleScopeFromSettings, LEGACY_ALL } from './rule_scope.js';
24
+
25
+ export function main(argv: readonly string[] = process.argv.slice(2)): number {
26
+ let rulesDir = '';
27
+ let settingsFile = '';
28
+ for (let i = 0; i < argv.length; i += 1) {
29
+ const arg = argv[i];
30
+ if (arg === '--rules-dir') {
31
+ rulesDir = argv[i + 1] ?? '';
32
+ i += 1;
33
+ } else if (arg === '--settings') {
34
+ settingsFile = argv[i + 1] ?? '';
35
+ i += 1;
36
+ } else {
37
+ process.stderr.write(`rule_scope_cli: unknown argument: ${arg}\n`);
38
+ return 2;
39
+ }
40
+ }
41
+ if (rulesDir === '') {
42
+ process.stderr.write('usage: rule_scope_cli.ts --rules-dir <dir> [--settings <file>]\n');
43
+ return 2;
44
+ }
45
+
46
+ let scope = LEGACY_ALL;
47
+ if (settingsFile !== '' && fs.existsSync(settingsFile)) {
48
+ try {
49
+ const settings = load_agent_settings({ project_path: settingsFile });
50
+ scope = ruleScopeFromSettings(settings as Record<string, unknown>);
51
+ } catch {
52
+ // Unreadable settings → legacy-all (fail-safe: over-ship).
53
+ scope = LEGACY_ALL;
54
+ }
55
+ }
56
+
57
+ for (const name of excludedRuleBasenames(path.resolve(rulesDir), scope)) {
58
+ process.stdout.write(`${name}\n`);
59
+ }
60
+ return 0;
61
+ }
62
+
63
+ function _isCliEntry(): boolean {
64
+ if (process.argv[1] === undefined) return false;
65
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
66
+ if (import.meta.url === argvUrl) return true;
67
+ try {
68
+ return (
69
+ fs.realpathSync(fileURLToPath(import.meta.url)) ===
70
+ fs.realpathSync(path.resolve(process.argv[1]))
71
+ );
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ if (_isCliEntry()) {
78
+ process.exit(main());
79
+ }
@@ -23,6 +23,7 @@ import { isAbsolute, join, resolve } from 'node:path';
23
23
 
24
24
  import type { ConflictPolicy, InstallPlan, InstallTarget } from './types.js';
25
25
  import { buildInstallPlan, type PlanSource } from './plan.js';
26
+ import { ruleFileArrives, LEGACY_ALL, type RuleScope } from './rule_scope.js';
26
27
 
27
28
  /**
28
29
  * Per-tool user-scope anchor directory, mirrored 1:1 from
@@ -152,6 +153,14 @@ export interface WizardExpandInputs {
152
153
  readonly packageRoot: string;
153
154
  /** Override for the user's home directory (tests only). */
154
155
  readonly home?: string;
156
+ /**
157
+ * Workspace/pack scope applied to `rules` sources
158
+ * (road-to-request-scoped-rule-load Phase 1b). Absent = LEGACY_ALL,
159
+ * which still applies the compat exclusion (`source-of-truth.md`) so
160
+ * the global path finally matches the project path — the recorded
161
+ * decision in `rule_scope.ts`.
162
+ */
163
+ readonly ruleScope?: RuleScope;
155
164
  }
156
165
 
157
166
  /**
@@ -172,6 +181,8 @@ export interface WizardExpandInputs {
172
181
  */
173
182
  export function expandWizardSources(inputs: WizardExpandInputs): PlanSource[] {
174
183
  const { toolIds, packageRoot, home } = inputs;
184
+ const scope = inputs.ruleScope ?? LEGACY_ALL;
185
+ const ruleFilter = (srcFile: string): boolean => ruleFileArrives(srcFile, scope);
175
186
  const out: PlanSource[] = [];
176
187
  for (const toolId of toolIds) {
177
188
  const deploy = GLOBAL_DEPLOY_SOURCES[toolId];
@@ -181,7 +192,17 @@ export function expandWizardSources(inputs: WizardExpandInputs): PlanSource[] {
181
192
  for (const [srcRel, destSub] of deploy) {
182
193
  const srcDir = join(packageRoot, srcRel);
183
194
  const destDir = destSub.length === 0 ? destRoot : join(destRoot, destSub);
184
- out.push({ toolId, srcDir, destDir, kind: 'deployed' });
195
+ // Rule sources get the install-time scoping filter — the same
196
+ // predicate the projection path uses (Phase 1b: Pipeline B may
197
+ // never drift from Pipeline A).
198
+ const isRuleSource = srcRel === 'dist/agent-src/rules';
199
+ out.push({
200
+ toolId,
201
+ srcDir,
202
+ destDir,
203
+ kind: 'deployed',
204
+ ...(isRuleSource ? { fileFilter: ruleFilter } : {}),
205
+ });
185
206
  }
186
207
  }
187
208
  return out;
@@ -1,15 +1,19 @@
1
1
  /**
2
- * Default-flip decision gate (Phase 6).
2
+ * Default-flip / demotion decision gate (Phase 6 → telemetry-demotion).
3
3
  *
4
- * Pure, no-I/O. Encodes the Phase-6 falsification gate: the SHIPPED default for
5
- * `subagents.auto` flips toward `on` ONLY when a real benchmark shows a net
6
- * token-or-time win at held quality, and only on hosts with a subagent
7
- * primitive. Honest-null exit (no win, or quality regressed) keeps the
8
- * conservative default. Contract:
4
+ * Pure, no-I/O. Originally the Phase-6 falsification gate: the SHIPPED default
5
+ * for `subagents.auto` was to flip toward `on` ONLY on a passed benchmark.
6
+ *
7
+ * As of 2026-07-09 the shipped default IS `on` on subagent-capable hosts
8
+ * (ADR-117; agents/settings/contexts/orchestration-default-flip-verdict.md
9
+ * § 2026-07-09) — flipped on a bounded-downside re-evaluation, not a passed
10
+ * benchmark. This helper is therefore now the telemetry-driven DEMOTION gate:
11
+ * a `fail` verdict from accumulated real-world orchestration telemetry demotes
12
+ * the default back to `ask` (conservative). Contract:
9
13
  * `src/agent-src/contexts/execution/orchestration-benchmark-gate.md`.
10
14
  *
11
- * This helper decides; it never runs the benchmark. The measurement is the
12
- * empirical bench:ab step, authorised + run out of band.
15
+ * This helper decides; it never runs the measurement. The signal is realized
16
+ * orchestration telemetry, aggregated + evaluated out of band.
13
17
  */
14
18
 
15
19
  export interface BenchmarkResult {