@event4u/agent-config 9.27.0 → 9.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/AGENTS.md +13 -12
- package/CHANGELOG.md +165 -0
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +55 -24
- package/dist/agent-src/commands/analyze/conformance.md +24 -0
- package/dist/agent-src/commands/cost/report.md +2 -2
- package/dist/agent-src/commands/team/adversarial.md +8 -7
- package/dist/agent-src/commands/team/delegate.md +13 -12
- package/dist/agent-src/commands/team/review.md +9 -8
- package/dist/agent-src/commands/team/status.md +9 -8
- package/dist/agent-src/commands/team.md +29 -19
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +88 -2
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +29 -21
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +26 -15
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +28 -3
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +54 -0
- package/dist/agent-src/contexts/execution/subagent-routing.md +13 -11
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +25 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +6 -3
- package/dist/agent-src/rules/artifact-engagement-recording.md +4 -1
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/brand-source-of-truth.md +33 -6
- package/dist/agent-src/rules/context-hygiene.md +3 -1
- package/dist/agent-src/rules/council-availability.md +93 -0
- package/dist/agent-src/rules/cross-source-consistency.md +3 -0
- package/dist/agent-src/rules/delegation-policy.md +38 -10
- package/dist/agent-src/rules/design-fidelity.md +3 -0
- package/dist/agent-src/rules/evaluator-independence.md +2 -1
- package/dist/agent-src/rules/external-code-graph-interop.md +2 -1
- package/dist/agent-src/rules/finance-safety-floor.md +1 -1
- package/dist/agent-src/rules/git-history-discipline.md +1 -1
- package/dist/agent-src/rules/history-discipline.md +3 -1
- package/dist/agent-src/rules/legal-safety-floor.md +1 -1
- package/dist/agent-src/rules/no-pr-progress-comments.md +3 -1
- package/dist/agent-src/rules/onboarding-gate.md +3 -1
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +4 -1
- package/dist/agent-src/rules/role-mode-adherence.md +1 -1
- package/dist/agent-src/rules/scale-discipline.md +3 -1
- package/dist/agent-src/rules/self-repair-loop.md +103 -0
- package/dist/agent-src/rules/session-canary.md +2 -1
- package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
- package/dist/agent-src/rules/token-budget-discipline.md +4 -2
- package/dist/agent-src/rules/tool-safety.md +5 -1
- package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
- package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
- package/dist/agent-src/skills/forensics-report/SKILL.md +114 -0
- package/dist/agent-src/skills/forensics-report/evals/triggers.json +17 -0
- package/dist/agent-src/skills/performance-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
- package/dist/agent-src/skills/premortem/SKILL.md +30 -1
- package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
- package/dist/agent-src/skills/security-audit/SKILL.md +1 -1
- package/dist/agent-src/skills/sequential-thinking/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +7 -5
- package/dist/agent-src/skills/subagent-orchestration/schemas/subagent-status.json +61 -3
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
- package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
- package/dist/agent-src/templates/AGENTS.md +8 -12
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +23 -0
- package/dist/cli/registry.js +7 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-66OVWGB5.js +270 -0
- package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-LCFJIQ4J.js} +89 -7
- package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
- package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-MMXA5GY7.js} +10 -17
- package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-MP3JT3IU.js} +3 -3
- package/dist/cli-delegate/{chunk-3KJIMFTB.js → chunk-NZ5N2XQL.js} +467 -305
- package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-OJM3MBJM.js} +1 -1
- package/dist/cli-delegate/{chunk-BCPIOAGV.js → chunk-RCIA67XN.js} +117 -96
- package/dist/cli-delegate/chunk-REKCMYOU.js +2991 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +134 -0
- package/dist/cli-delegate/cmd_conformance.js +10 -47
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +10 -6
- package/dist/cli-delegate/cmd_explain.js +80 -330
- package/dist/cli-delegate/cmd_export.js +6 -6
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_handoff.js +3 -3
- package/dist/cli-delegate/cmd_mcp_available.js +433 -0
- package/dist/cli-delegate/cmd_migrate.js +3 -3
- package/dist/cli-delegate/cmd_packs_active.js +153 -0
- package/dist/cli-delegate/cmd_preflight.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +6 -6
- package/dist/cli-delegate/cmd_route_audit.js +8 -8
- package/dist/cli-delegate/cmd_route_explain.js +4 -4
- package/dist/cli-delegate/cmd_settings_get.js +266 -0
- package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
- package/dist/cli-delegate/cmd_settings_set.js +8 -2968
- package/dist/cli-delegate/cmd_sync.js +6 -6
- package/dist/cli-delegate/cmd_uninstall.js +6 -6
- package/dist/cli-delegate/cmd_update.js +4 -4
- package/dist/cli-delegate/cmd_upgrade.js +4 -4
- package/dist/cli-delegate/cmd_validate.js +6 -6
- package/dist/cli-delegate/cmd_versions.js +4 -4
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +164 -55
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +44 -5
- package/dist/discovery/trust-report.md +11 -11
- package/dist/discovery/workspaces.json +16 -6
- package/dist/hooks/dispatch.js +9341 -6131
- package/dist/install/install.mjs +739 -524
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +777 -281
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +134 -15
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/scripts/condense.js +147 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +6 -7
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/settingsCarveOut.js +0 -9
- package/dist/shared/settingsCarveOut.js.map +1 -1
- package/dist/shared/settingsClasses.js +11 -9
- package/dist/shared/settingsClasses.js.map +1 -1
- package/dist/ui/assets/{index-DmR0mbk4.js → index-JDAF00bC.js} +2 -2
- package/dist/ui/assets/{index-DmR0mbk4.js.map → index-JDAF00bC.js.map} +1 -1
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +68 -17
- package/docs/guidelines/agent-infra/memory-access.md +14 -3
- package/package.json +1 -1
- package/src/agent-src/templates/AGENTS.md +8 -12
- package/src/config/agent-settings.template.yml +52 -66
- package/src/config/budgets.yml +29 -0
- package/src/config/discovery/packs.yml +11 -0
- package/src/config/discovery/workspaces.yml +1 -1
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +26 -0
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/scripts/_cli/cmd_brand_status.ts +174 -0
- package/src/scripts/_cli/cmd_conformance.ts +14 -42
- package/src/scripts/_cli/cmd_doctor.ts +65 -48
- package/src/scripts/_cli/cmd_mcp_available.ts +266 -0
- package/src/scripts/_cli/cmd_packs_active.ts +225 -0
- package/src/scripts/_cli/cmd_settings_get.ts +324 -0
- package/src/scripts/_dispatch.bash +57 -0
- package/src/scripts/_lib/agent_settings.ts +140 -16
- package/src/scripts/_lib/auto_dispatch.corpus.test.ts +1 -1
- package/src/scripts/_lib/auto_dispatch.ts +23 -18
- package/src/scripts/_lib/capsule_trigger.ts +124 -0
- package/src/scripts/_lib/carrier_divergence.ts +135 -0
- package/src/scripts/_lib/host_capability.ts +144 -4
- package/src/scripts/_lib/judgment_ladder.ts +382 -0
- package/src/scripts/_lib/orchestration_gate.ts +8 -0
- package/src/scripts/_lib/orchestration_record.ts +37 -0
- package/src/scripts/_lib/orchestration_savings.ts +1 -1
- package/src/scripts/_lib/prompt_shape.ts +88 -0
- package/src/scripts/_lib/review_skipped_record.ts +102 -0
- package/src/scripts/_lib/rule_layer_overlap.ts +254 -0
- package/src/scripts/_lib/self_repair.ts +489 -0
- package/src/scripts/_lib/self_repair_store.ts +128 -0
- package/src/scripts/_lib/subagent_capsule.ts +182 -0
- package/src/scripts/_lib/subagent_response.ts +19 -0
- package/src/scripts/_lib/subagent_spawn.ts +13 -0
- package/src/scripts/_lib/subagent_steering.ts +9 -4
- package/src/scripts/_lib/tier_budget_routing.ts +6 -1
- package/src/scripts/_lib/transcript_entry.ts +80 -0
- package/src/scripts/_lib/worker_budget.ts +37 -2
- package/src/scripts/ai_council/clients.ts +76 -54
- package/src/scripts/ai_council/config.ts +94 -6
- package/src/scripts/ai_council/handoff.ts +113 -0
- package/src/scripts/ai_council/modes.ts +19 -4
- package/src/scripts/ai_council/orchestrator.ts +105 -0
- package/src/scripts/ai_council/quorum.ts +84 -0
- package/src/scripts/ai_council/session.ts +44 -0
- package/src/scripts/ai_council/transport_resolver.ts +127 -1
- package/src/scripts/ai_team/availability.ts +110 -0
- package/src/scripts/ai_team/config.ts +34 -14
- package/src/scripts/ai_team/team_dispatch.ts +71 -29
- package/src/scripts/bench_critic_protocol.ts +396 -0
- package/src/scripts/check_branch_freshness.ts +255 -0
- package/src/scripts/check_council_config_location.ts +125 -1
- package/src/scripts/check_detector_corpus.ts +202 -0
- package/src/scripts/check_hook_bundle_freshness.ts +4 -1
- package/src/scripts/check_standing_rule_delivery.ts +314 -0
- package/src/scripts/condense.ts +162 -2
- package/src/scripts/config/profiles.ts +13 -3
- package/src/scripts/conformance_scan.ts +272 -24
- package/src/scripts/council_availability_hook.ts +188 -0
- package/src/scripts/council_cli.ts +527 -32
- package/src/scripts/forensics_report.ts +333 -0
- package/src/scripts/hook_manifest.yaml +107 -26
- package/src/scripts/hooks/block_no_verify.ts +39 -2
- package/src/scripts/hooks/block_unauthorized_git.ts +156 -7
- package/src/scripts/hooks/concern_registry.ts +10 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +484 -0
- package/src/scripts/hooks/end_review_nudge_hook.ts +726 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +342 -0
- package/src/scripts/install.ts +151 -0
- package/src/scripts/language_mirror_hook.ts +30 -37
- package/src/scripts/lint_discovery_vocabulary.ts +2 -0
- package/src/scripts/lint_never_silent.ts +276 -0
- package/src/scripts/lint_no_activation_gates.ts +271 -0
- package/src/scripts/measure_scope_dedup.ts +19 -13
- package/src/scripts/onboarding_gate_hook.ts +40 -1
- package/src/scripts/orchestration_record.ts +12 -1
- package/src/scripts/preamble_byte_census.ts +9 -20
- package/src/scripts/rebuild_hook_bundle.ts +177 -0
- package/src/scripts/release.ts +74 -2
- package/src/scripts/report_carrier_divergence.ts +364 -0
- package/src/scripts/report_skill_activation.ts +257 -0
- package/src/scripts/report_skill_obligation_violations.ts +579 -0
- package/src/scripts/routing_doctor.ts +95 -39
- package/src/scripts/score_skill_selection.ts +19 -5
- package/src/scripts/self_repair_cli.ts +470 -0
- package/src/scripts/self_repair_hook.ts +241 -0
- package/src/scripts/team_events_spike.ts +271 -0
- package/src/scripts/team_review_gate_hook.ts +31 -9
- package/src/server/schemas/settings.ts +10 -17
- package/src/shared/settingsCarveOut.ts +0 -10
- package/src/shared/settingsClasses.ts +11 -9
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule-layer overlap detection — is the corpus about to be delivered twice?
|
|
3
|
+
*
|
|
4
|
+
* Claude Code loads `~/.claude/rules/` and `<project>/.claude/rules/` **both**,
|
|
5
|
+
* user layer first, with no dedup between them: a rule present in both is in
|
|
6
|
+
* standing context twice. Measured on a maintainer machine 2026-08-08 — 112
|
|
7
|
+
* global entries + 92 project entries, 91 shared basenames, 176,354 exact-BPE
|
|
8
|
+
* tokens of which 74,137 (42%) redundant. Evidence:
|
|
9
|
+
* `agents/evidence/analysis/standing-rule-delivery-topologies.md`; the host's
|
|
10
|
+
* loading contract: `agents/evidence/analysis/claude-code-rules-dir-contract.md`.
|
|
11
|
+
*
|
|
12
|
+
* This module answers three questions and nothing else:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Do two layers overlap, and by how much?** ({@link compareLayers})
|
|
15
|
+
* 2. **Is the overlap a pure duplicate, or a content skew?** — the difference
|
|
16
|
+
* decides whether suppression is enough or a refresh is also needed.
|
|
17
|
+
* 3. **How is a layer suppressed without deleting it?** — the host's own
|
|
18
|
+
* `claudeMdExcludes` settings key ({@link claudeMdExcludesGlob},
|
|
19
|
+
* {@link mergeClaudeMdExcludes}).
|
|
20
|
+
*
|
|
21
|
+
* The comparison half is pure so it is testable from fixtures; only
|
|
22
|
+
* {@link readRuleLayer} touches the filesystem, and nothing here ever writes or
|
|
23
|
+
* unlinks. Deleting a user's `~/.claude/rules/` would be a Hard-Floor action
|
|
24
|
+
* (`non-destructive-by-default`), so no function in this module can reach one.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import * as fs from 'node:fs';
|
|
28
|
+
import * as path from 'node:path';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Frontmatter keys the installer stamps onto its OWN copies, and which therefore
|
|
32
|
+
* differ between a global (installed) copy and a project (projected) copy of the
|
|
33
|
+
* same rule without either being stale.
|
|
34
|
+
*
|
|
35
|
+
* Kept in lockstep with `install.ts`'s `_set_key(fm_lines, 'package', …)` /
|
|
36
|
+
* `_set_key(fm_lines, 'source_path', …)` pair. Adding a third stamped key there
|
|
37
|
+
* without adding it here makes every rule read as divergent.
|
|
38
|
+
*/
|
|
39
|
+
export const INSTALLER_PROVENANCE_KEYS: readonly string[] = ['package', 'source_path'];
|
|
40
|
+
|
|
41
|
+
/** One rule layer: its directory and `basename → text` for every `.md` in it. */
|
|
42
|
+
export interface RuleLayer {
|
|
43
|
+
readonly dir: string;
|
|
44
|
+
readonly files: ReadonlyMap<string, string>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** What {@link compareLayers} found. Counts are chars; token conversion is the caller's. */
|
|
48
|
+
export interface OverlapReport {
|
|
49
|
+
readonly schema_version: 1;
|
|
50
|
+
/** Basenames present in both layers. */
|
|
51
|
+
readonly overlap: readonly string[];
|
|
52
|
+
/** Of {@link overlap}: bodies equal once the provenance keys are stripped — pure duplicates. */
|
|
53
|
+
readonly duplicate: readonly string[];
|
|
54
|
+
/** Of {@link overlap}: bodies genuinely differ — a content skew. Suppression alone is NOT enough. */
|
|
55
|
+
readonly divergent: readonly string[];
|
|
56
|
+
/** Basenames only in the first (global) layer. */
|
|
57
|
+
readonly global_only: readonly string[];
|
|
58
|
+
/** Basenames only in the second (project) layer. */
|
|
59
|
+
readonly project_only: readonly string[];
|
|
60
|
+
/** Chars the SECOND layer contributes redundantly — what suppressing it recovers. */
|
|
61
|
+
readonly redundant_chars: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The three answers a user may give when an overlap is detected. */
|
|
65
|
+
export type LayerChoice = 'global' | 'project' | 'both-acknowledged';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Strip the installer's provenance keys from a rule's frontmatter block.
|
|
69
|
+
*
|
|
70
|
+
* Only lines inside the leading `---` … `---` fence are considered, and only
|
|
71
|
+
* those whose key is in {@link INSTALLER_PROVENANCE_KEYS}. A body line that
|
|
72
|
+
* happens to start with `package:` survives — stripping it would make two
|
|
73
|
+
* genuinely different rules compare equal, which is the failure direction that
|
|
74
|
+
* matters here.
|
|
75
|
+
*
|
|
76
|
+
* A file with no frontmatter fence is returned unchanged.
|
|
77
|
+
*/
|
|
78
|
+
export function stripProvenance(text: string): string {
|
|
79
|
+
const lines = text.split('\n');
|
|
80
|
+
if (lines[0]?.trim() !== '---') return text;
|
|
81
|
+
let close = -1;
|
|
82
|
+
for (let i = 1; i < lines.length; i++) {
|
|
83
|
+
if (lines[i]?.trim() === '---') {
|
|
84
|
+
close = i;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (close === -1) return text;
|
|
89
|
+
const kept: string[] = [];
|
|
90
|
+
for (let i = 0; i < lines.length; i++) {
|
|
91
|
+
if (i > 0 && i < close) {
|
|
92
|
+
const key = /^([A-Za-z_][A-Za-z0-9_-]*):/.exec(lines[i] ?? '')?.[1];
|
|
93
|
+
if (key !== undefined && INSTALLER_PROVENANCE_KEYS.includes(key)) continue;
|
|
94
|
+
}
|
|
95
|
+
kept.push(lines[i] ?? '');
|
|
96
|
+
}
|
|
97
|
+
return kept.join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Sorted basenames of `a` that are absent from `b`. */
|
|
101
|
+
function only_in(a: ReadonlyMap<string, string>, b: ReadonlyMap<string, string>): string[] {
|
|
102
|
+
return [...a.keys()].filter((k) => !b.has(k)).sort();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Compare two rule layers. Pure — no filesystem, no clock, no environment.
|
|
107
|
+
*
|
|
108
|
+
* `global_layer` is the user-level layer and `project_layer` the project one,
|
|
109
|
+
* because {@link OverlapReport.redundant_chars} is measured on the SECOND
|
|
110
|
+
* argument: it answers "what does suppressing the project layer recover", which
|
|
111
|
+
* is the direction the installer offers first (the project layer is the one it
|
|
112
|
+
* is usually about to write).
|
|
113
|
+
*/
|
|
114
|
+
export function compareLayers(
|
|
115
|
+
global_layer: ReadonlyMap<string, string>,
|
|
116
|
+
project_layer: ReadonlyMap<string, string>,
|
|
117
|
+
): OverlapReport {
|
|
118
|
+
const overlap = [...global_layer.keys()].filter((k) => project_layer.has(k)).sort();
|
|
119
|
+
const duplicate: string[] = [];
|
|
120
|
+
const divergent: string[] = [];
|
|
121
|
+
let redundant_chars = 0;
|
|
122
|
+
for (const name of overlap) {
|
|
123
|
+
const g = stripProvenance(global_layer.get(name) ?? '');
|
|
124
|
+
const p = stripProvenance(project_layer.get(name) ?? '');
|
|
125
|
+
if (g === p) {
|
|
126
|
+
duplicate.push(name);
|
|
127
|
+
redundant_chars += (project_layer.get(name) ?? '').length;
|
|
128
|
+
} else {
|
|
129
|
+
divergent.push(name);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
schema_version: 1,
|
|
134
|
+
overlap,
|
|
135
|
+
duplicate,
|
|
136
|
+
divergent,
|
|
137
|
+
global_only: only_in(global_layer, project_layer),
|
|
138
|
+
project_only: only_in(project_layer, global_layer),
|
|
139
|
+
redundant_chars,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Read every `.md` in `dir` into a {@link RuleLayer}, or `null` when the
|
|
145
|
+
* directory does not exist.
|
|
146
|
+
*
|
|
147
|
+
* Symlinks are followed, because the project layer is a symlink projection into
|
|
148
|
+
* `dist/agent-src/rules/` and its CONTENT is what lands in context. An entry
|
|
149
|
+
* whose target is unreadable (a dangling symlink) is skipped rather than
|
|
150
|
+
* throwing — a broken link contributes no bytes to the session either.
|
|
151
|
+
*/
|
|
152
|
+
export function readRuleLayer(dir: string): RuleLayer | null {
|
|
153
|
+
let names: string[];
|
|
154
|
+
try {
|
|
155
|
+
names = fs.readdirSync(dir);
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const files = new Map<string, string>();
|
|
160
|
+
for (const name of names.filter((n) => n.endsWith('.md')).sort()) {
|
|
161
|
+
try {
|
|
162
|
+
files.set(name, fs.readFileSync(path.join(dir, name), 'utf-8'));
|
|
163
|
+
} catch {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return { dir, files };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The `claudeMdExcludes` glob that suppresses one rules directory.
|
|
172
|
+
*
|
|
173
|
+
* The host matches this key against ABSOLUTE paths, so the directory is
|
|
174
|
+
* resolved before the `/**` suffix is appended. Documented example from the
|
|
175
|
+
* host's own docs: `"/home/user/monorepo/other-team/.claude/rules/**"`.
|
|
176
|
+
*/
|
|
177
|
+
export function claudeMdExcludesGlob(dir: string): string {
|
|
178
|
+
return `${path.resolve(dir).replace(/\/+$/, '')}/**`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Append `entry` to an existing `claudeMdExcludes` value, preserving everything
|
|
183
|
+
* already there.
|
|
184
|
+
*
|
|
185
|
+
* `claudeMdExcludes` arrays MERGE across settings layers, so an install that
|
|
186
|
+
* replaced the array would silently drop another layer's exclusions — a
|
|
187
|
+
* different defect from the one being fixed. Non-string members of a
|
|
188
|
+
* hand-edited array are preserved in order; an absent or non-array value starts
|
|
189
|
+
* a fresh single-entry array. Already present → returned unchanged, so the
|
|
190
|
+
* operation is idempotent across re-installs.
|
|
191
|
+
*/
|
|
192
|
+
export function mergeClaudeMdExcludes(existing: unknown, entry: string): unknown[] {
|
|
193
|
+
if (!Array.isArray(existing)) return [entry];
|
|
194
|
+
if (existing.includes(entry)) return [...existing];
|
|
195
|
+
return [...existing, entry];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** What the installer should do, given an overlap report and the user's choice. */
|
|
199
|
+
export interface LayerAction {
|
|
200
|
+
/** Which layer this install writes. */
|
|
201
|
+
readonly write: 'global' | 'project' | 'both';
|
|
202
|
+
/** Directory to suppress via `claudeMdExcludes`, or `null` when nothing is suppressed. */
|
|
203
|
+
readonly suppress_dir: string | null;
|
|
204
|
+
/** True when the overlap contains divergent bodies — suppression alone loses obligations. */
|
|
205
|
+
readonly refresh_required: boolean;
|
|
206
|
+
/** One-line human summary; the installer prints it verbatim. */
|
|
207
|
+
readonly note: string;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Turn a report plus a {@link LayerChoice} into the action to take. Pure.
|
|
212
|
+
*
|
|
213
|
+
* `both-acknowledged` writes both and suppresses nothing — a deliberate
|
|
214
|
+
* two-layer setup is legitimate and the flag exists so the installer does not
|
|
215
|
+
* have to guess. The other two choices name the layer to KEEP and suppress the
|
|
216
|
+
* other; suppression never deletes.
|
|
217
|
+
*
|
|
218
|
+
* `refresh_required` fires on any divergent body: suppressing a layer whose
|
|
219
|
+
* copies differ would drop whichever obligations only the suppressed copy
|
|
220
|
+
* carried, so the caller must refresh before suppressing. This is the guard
|
|
221
|
+
* against the version-skew case, which the 2026-08-08 measurement found absent
|
|
222
|
+
* but nothing prevents later.
|
|
223
|
+
*/
|
|
224
|
+
export function decideLayerAction(
|
|
225
|
+
report: OverlapReport,
|
|
226
|
+
choice: LayerChoice,
|
|
227
|
+
global_dir: string,
|
|
228
|
+
project_dir: string,
|
|
229
|
+
): LayerAction {
|
|
230
|
+
const refresh_required = report.divergent.length > 0;
|
|
231
|
+
const skew = refresh_required
|
|
232
|
+
? ` ${report.divergent.length} shared rule(s) differ in body — refresh before suppressing, or obligations only the suppressed copy carries are lost.`
|
|
233
|
+
: '';
|
|
234
|
+
if (choice === 'both-acknowledged') {
|
|
235
|
+
return {
|
|
236
|
+
write: 'both',
|
|
237
|
+
suppress_dir: null,
|
|
238
|
+
refresh_required,
|
|
239
|
+
note:
|
|
240
|
+
`Keeping both rule layers by request: ${report.overlap.length} shared rule(s), `
|
|
241
|
+
+ `${report.redundant_chars} chars delivered twice per session. Nothing suppressed, nothing deleted.${skew}`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const keep = choice === 'global' ? global_dir : project_dir;
|
|
245
|
+
const drop = choice === 'global' ? project_dir : global_dir;
|
|
246
|
+
return {
|
|
247
|
+
write: choice,
|
|
248
|
+
suppress_dir: drop,
|
|
249
|
+
refresh_required,
|
|
250
|
+
note:
|
|
251
|
+
`Keeping ${choice} rule layer (${keep}); suppressing ${drop} via claudeMdExcludes — `
|
|
252
|
+
+ `recovers ${report.redundant_chars} chars per session. No file is deleted or rewritten.${skew}`,
|
|
253
|
+
};
|
|
254
|
+
}
|