@event4u/agent-config 14.6.0 → 14.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +255 -145
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +264 -10
- package/dist/agent-src/commands/analyze.md +1 -1
- package/dist/agent-src/commands/evals/git-pr-merge.json +13 -0
- package/dist/agent-src/commands/evals/roadmap.json +17 -1
- package/dist/agent-src/commands/pr/create.md +14 -2
- package/dist/agent-src/commands/pr/merge.md +379 -0
- package/dist/agent-src/commands/roadmap/process-full.md +272 -32
- package/dist/agent-src/commands/roadmap.md +1 -1
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +95 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +289 -106
- package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
- package/dist/agent-src/rules/context-hygiene.md +2 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +67 -32
- package/dist/agent-src/rules/fix-what-you-see.md +108 -0
- package/dist/agent-src/rules/no-roadmap-references.md +0 -5
- package/dist/agent-src/rules/recurring-criticism.md +110 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +20 -1
- package/dist/agent-src/rules/rule-type-governance.md +0 -2
- package/dist/agent-src/rules/self-repair-loop.md +17 -0
- package/dist/agent-src/rules/skill-quality.md +0 -2
- package/dist/agent-src/rules/source-confidentiality.md +37 -7
- package/dist/agent-src/scripts/archival_sweep.ts +117 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +26 -7
- package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
- package/dist/agent-src/scripts/gate_budget.ts +282 -0
- package/dist/agent-src/scripts/gate_execute.ts +72 -31
- package/dist/agent-src/scripts/roadmap_gates.ts +74 -3
- package/dist/agent-src/scripts/update_roadmap_progress.ts +296 -53
- package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
- package/dist/agent-src/skills/ai-council/SKILL.md +48 -807
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +320 -0
- package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +99 -0
- package/dist/agent-src/skills/ai-council/references/execution-modes.md +162 -0
- package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +125 -0
- package/dist/agent-src/skills/ai-council/references/procedure.md +143 -0
- package/dist/agent-src/skills/decision-record/SKILL.md +61 -3
- package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
- package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -0
- package/dist/agent-src/skills/quality-tools/SKILL.md +34 -326
- package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +113 -0
- package/dist/agent-src/skills/quality-tools/references/php-tools.md +222 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +15 -409
- package/dist/agent-src/skills/roadmap-management/references/archival.md +226 -0
- package/dist/agent-src/skills/roadmap-management/references/authoring.md +107 -0
- package/dist/agent-src/skills/roadmap-management/references/dashboard.md +47 -0
- package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +58 -0
- package/dist/agent-src/skills/security/SKILL.md +8 -0
- package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
- package/dist/agent-src/skills/skill-writing/SKILL.md +34 -570
- package/dist/agent-src/skills/skill-writing/references/procedure.md +275 -0
- package/dist/agent-src/skills/skill-writing/references/read-only-scripts.md +18 -0
- package/dist/agent-src/skills/skill-writing/references/section-patterns.md +298 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +65 -4
- package/dist/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/dist/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/dist/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/dist/cli/registry.js +2 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
- package/dist/cli-delegate/{chunk-P6LQDIWK.js → chunk-7PRE6ZV2.js} +338 -0
- package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
- package/dist/cli-delegate/chunk-E4XIZDIB.js +402 -0
- package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-EEJUMCDZ.js} +16 -16
- package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
- package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-H4VIVD4C.js} +385 -284
- package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
- package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-LPPWO5CE.js} +12 -8
- package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
- package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-N7CDDGI3.js} +4 -23
- package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-PMEKGDED.js} +74 -167
- package/dist/cli-delegate/{chunk-GRRPEPRN.js → chunk-T7L2QK3K.js} +24 -5
- package/dist/cli-delegate/{chunk-EVTY7ISR.js → chunk-YM4GZDJG.js} +9 -1
- package/dist/cli-delegate/cmd_adr_effective.js +1162 -0
- package/dist/cli-delegate/cmd_conformance.js +466 -716
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +10 -10
- package/dist/cli-delegate/cmd_explain.js +1 -1
- package/dist/cli-delegate/cmd_export.js +10 -9
- package/dist/cli-delegate/cmd_handoff.js +10 -9
- package/dist/cli-delegate/cmd_memory_get.js +2 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +5 -5
- package/dist/cli-delegate/cmd_prune.js +11 -10
- package/dist/cli-delegate/cmd_route_audit.js +6 -5
- package/dist/cli-delegate/cmd_route_explain.js +3 -3
- package/dist/cli-delegate/cmd_session_recycle.js +840 -30
- package/dist/cli-delegate/cmd_settings_get.js +3 -3
- package/dist/cli-delegate/cmd_settings_migrate.js +10 -9
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +11 -10
- package/dist/cli-delegate/cmd_uninstall.js +13 -11
- 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 +11 -10
- package/dist/cli-delegate/cmd_versions.js +3 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +111 -37
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +5 -5
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +15 -12
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +11 -8
- package/dist/hooks/dispatch.js +3165 -2192
- package/dist/install/claudePathsPlan.js +187 -0
- package/dist/install/claudePathsPlan.js.map +1 -0
- package/dist/install/globalRuleLayers.js +169 -0
- package/dist/install/globalRuleLayers.js.map +1 -0
- package/dist/install/hostLayerFingerprint.js +136 -0
- package/dist/install/hostLayerFingerprint.js.map +1 -0
- package/dist/install/install.mjs +553 -425
- package/dist/install/partitionEligibility.js +432 -0
- package/dist/install/partitionEligibility.js.map +1 -0
- package/dist/install/ruleLayerPartition.js +157 -0
- package/dist/install/ruleLayerPartition.js.map +1 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1863 -1234
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_src.js +41 -0
- package/dist/scripts/_lib/agent_src.js.map +1 -1
- package/dist/scripts/_lib/install_layout.js +83 -0
- package/dist/scripts/_lib/install_layout.js.map +1 -0
- package/dist/scripts/_lib/installed_lock.js +444 -0
- package/dist/scripts/_lib/installed_lock.js.map +1 -0
- package/dist/scripts/_lib/prune_empty_dirs.js +85 -0
- package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
- package/dist/scripts/condense.js +63 -251
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +4 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/contracts/settings-classes.md +12 -6
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +8 -3
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +45 -1
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
- package/docs/guidelines/agent-infra/false-green.md +47 -1
- package/docs/guidelines/agent-infra/memory-access.md +17 -0
- package/docs/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archival_sweep.ts +117 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +26 -7
- package/src/agent-src/scripts/dashboard_mode.ts +212 -0
- package/src/agent-src/scripts/gate_budget.ts +282 -0
- package/src/agent-src/scripts/gate_execute.ts +72 -31
- package/src/agent-src/scripts/roadmap_gates.ts +74 -3
- package/src/agent-src/scripts/update_roadmap_progress.ts +296 -53
- package/src/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/src/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/src/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/src/config/agent-settings.template.yml +27 -2
- package/src/config/ci-local-parity.yml +39 -0
- package/src/config/estate-count-budget.json +612 -3
- package/src/config/evaluator-budgets.json +3 -3
- package/src/config/gate-coverage.yml +220 -12
- package/src/config/gate-violation-baselines.json +73 -63
- package/src/config/gitignore-block.txt +15 -2
- package/src/config/hook-latency-budget.json +5 -0
- package/src/config/rule-activation-census.json +14 -6
- package/src/config/rule-stub-ceilings.json +34 -7
- package/src/install/claudePathsPlan.ts +196 -0
- package/src/install/globalRuleLayers.ts +197 -0
- package/src/install/hostLayerFingerprint.ts +145 -0
- package/src/install/partitionEligibility.ts +502 -0
- package/src/install/ruleLayerPartition.ts +212 -0
- package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
- package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
- package/src/scripts/_dispatch.bash +17 -1
- package/src/scripts/_lib/ac_heading.ts +30 -0
- package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
- package/src/scripts/_lib/adr_frontmatter.ts +459 -0
- package/src/scripts/_lib/agent_src.ts +42 -0
- package/src/scripts/_lib/bench_ab_pinned_repo.ts +162 -0
- package/src/scripts/_lib/bench_ab_workspace.ts +10 -1
- package/src/scripts/_lib/envelope_grounding.ts +49 -9
- package/src/scripts/_lib/installed_lock.ts +50 -5
- package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
- package/src/scripts/_lib/md_section.ts +129 -0
- package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
- package/src/scripts/_lib/scope_guard.sh +26 -3
- package/src/scripts/_lib/self_repair.ts +19 -0
- package/src/scripts/_lib/self_repair_class_b.ts +171 -0
- package/src/scripts/adr/evidence_census.ts +899 -0
- package/src/scripts/adr/regenerate_index.ts +12 -32
- package/src/scripts/adr_cite_check.ts +540 -39
- package/src/scripts/ai_council/cli_help.ts +55 -0
- package/src/scripts/audit_adr_coverage.ts +103 -17
- package/src/scripts/before_complete_hook.ts +153 -28
- package/src/scripts/bench_ab_v2_complexity.ts +27 -7
- package/src/scripts/bench_hook_latency.ts +204 -0
- package/src/scripts/build_archive_index.ts +34 -0
- package/src/scripts/check_adr_frontmatter.ts +574 -39
- package/src/scripts/check_hook_bundle_content.ts +219 -0
- package/src/scripts/check_new_adr_evidence.ts +529 -0
- package/src/scripts/check_references.ts +78 -0
- package/src/scripts/check_release_published.ts +32 -11
- package/src/scripts/check_rule_layer_partition.ts +531 -0
- package/src/scripts/check_single_delivery.ts +194 -17
- package/src/scripts/check_standing_rule_delivery.ts +8 -2
- package/src/scripts/ci_settle.ts +170 -0
- package/src/scripts/condense.ts +79 -259
- package/src/scripts/council_cli.ts +3 -3
- package/src/scripts/dispatch_r2_reviewer.ts +6 -14
- package/src/scripts/external_sources_denylist.json +4 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +77 -14
- package/src/scripts/hooks/block_no_verify.ts +61 -10
- package/src/scripts/hooks/block_unauthorized_git.ts +21 -1
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/dispatch_hook.ts +154 -187
- package/src/scripts/hooks/dispatch_issues.ts +38 -19
- package/src/scripts/hooks/fallback_yaml.ts +66 -0
- package/src/scripts/hooks/host_semantics.ts +21 -0
- package/src/scripts/hooks/injection_budget.ts +13 -3
- package/src/scripts/hooks/py_json_dumps.ts +103 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
- package/src/scripts/hooks/run_continuation_hook.ts +8 -5
- package/src/scripts/hooks/ship_diff_volume_hook.ts +81 -15
- package/src/scripts/hooks/source_first_gate_hook.ts +464 -0
- package/src/scripts/hooks/state_io.ts +870 -21
- package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
- package/src/scripts/hooks/subagent_ledger_hook.ts +44 -15
- package/src/scripts/hooks/telemetry_flush_hook.ts +139 -0
- package/src/scripts/hooks/telemetry_self_repair.ts +128 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +22 -4
- package/src/scripts/hooks/turn_end_gate_hook.ts +81 -15
- package/src/scripts/injection_scan_hook.ts +137 -5
- package/src/scripts/install.ts +16 -11
- package/src/scripts/language_mirror_hook.ts +561 -25
- package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
- package/src/scripts/lint_hook_manifest.ts +49 -0
- package/src/scripts/lint_plan_risk_register.ts +4 -3
- package/src/scripts/lint_provenance_vocabulary.ts +472 -4
- package/src/scripts/lint_skill_router_head.ts +4 -4
- package/src/scripts/mcp_server/tools.ts +2 -2
- package/src/scripts/new_roadmap.ts +136 -0
- package/src/scripts/pack_mcp_content.ts +20 -5
- package/src/scripts/release.ts +37 -38
- package/src/scripts/report_layer_overlap.ts +6 -0
- package/src/scripts/routing_doctor.ts +5 -1
- package/src/scripts/self_repair_hook.ts +37 -5
- package/src/scripts/session_register_hook.ts +11 -0
- package/src/scripts/skill_usage_collect.ts +11 -1
- package/src/scripts/skill_usage_report.ts +278 -8
- package/src/scripts/sync_pr_branch.ts +170 -20
- package/src/server/schemas/settings.ts +8 -0
- package/dist/cli-delegate/chunk-EJITJBJX.js +0 -172
- package/dist/cli-delegate/chunk-PZWRPRWP.js +0 -38
- package/dist/cli-delegate/{chunk-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
- package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
- package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-MMSGVZKV.js} +3 -3
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single-delivery partition predicate — one artefact, one layer.
|
|
3
|
+
*
|
|
4
|
+
* ## What it decides
|
|
5
|
+
*
|
|
6
|
+
* Two layers deliver agent artefacts: a machine-local project layer at
|
|
7
|
+
* `<repo>/.claude/` (gitignored, 0 tracked files, rewritten by every
|
|
8
|
+
* `task generate-tools`) and a host-global layer at `~/.claude/` (written by
|
|
9
|
+
* `agent-config install`, never by the build). Measured 2026-08-19 on a freshly
|
|
10
|
+
* regenerated tree: **110 rules arrive twice**, and standing rule prose is
|
|
11
|
+
* 203,873 tokens against a 110,000 cap (185.3 %).
|
|
12
|
+
*
|
|
13
|
+
* ADR-236 partitions them: an artefact that exists ONLY for this package stays
|
|
14
|
+
* in the project layer; everything else is delivered only globally. That takes
|
|
15
|
+
* `<repo>/.claude/` from 111 rules and 338 skills to **16 rules and zero
|
|
16
|
+
* skills**.
|
|
17
|
+
*
|
|
18
|
+
* ## Why the predicate is fail-safe and never fails the build
|
|
19
|
+
*
|
|
20
|
+
* The partition is a removal, so the build loses its repair path: it can no
|
|
21
|
+
* longer heal a stale global layer by regenerating, because it stops writing
|
|
22
|
+
* those files. Every uncertainty therefore resolves to `standalone/full` — the
|
|
23
|
+
* pre-partition behaviour — and **never** to a refusal:
|
|
24
|
+
*
|
|
25
|
+
* `.github/workflows/consistency.yml:169` runs `task generate-tools` on a fresh
|
|
26
|
+
* checkout where, by that workflow's own comment at `:172-174`, the host rule
|
|
27
|
+
* trees are gitignored and absent. An option that made a missing global layer a
|
|
28
|
+
* hard failure was eliminated by that fact in the 2026-08-19 council round, not
|
|
29
|
+
* by preference. Under-governing a checkout is the one regression this estate
|
|
30
|
+
* exists to prevent; breaking the Consistency pipeline is the other. Full
|
|
31
|
+
* projection is the only branch that does neither.
|
|
32
|
+
*
|
|
33
|
+
* ## Why content and not a version number
|
|
34
|
+
*
|
|
35
|
+
* Rationale and the 153-skill measurement that decided it: see
|
|
36
|
+
* `hostLayerFingerprint.ts`. Version equality is checked too, but as a cheap
|
|
37
|
+
* pre-filter — it is necessary, never sufficient.
|
|
38
|
+
*
|
|
39
|
+
* ## Contract
|
|
40
|
+
*
|
|
41
|
+
* Side-effect-free, no I/O of its own (callers supply the facts), no CLI entry,
|
|
42
|
+
* no `process.exit`. Ships inside the consumer installer bundle, same
|
|
43
|
+
* constraint as `ruleInScope.ts`.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import * as fs from 'node:fs';
|
|
47
|
+
import * as os from 'node:os';
|
|
48
|
+
|
|
49
|
+
import { parseFrontmatter } from './ruleInScope.js';
|
|
50
|
+
import { fingerprintLayers, hostLayerInputs } from './hostLayerFingerprint.js';
|
|
51
|
+
import {
|
|
52
|
+
current_package_version,
|
|
53
|
+
read_lockfile,
|
|
54
|
+
write_lockfile,
|
|
55
|
+
} from '../scripts/_lib/installed_lock.js';
|
|
56
|
+
|
|
57
|
+
/** The workspace id that marks an artefact as existing only for this package. */
|
|
58
|
+
export const MAINTAINER_WORKSPACE = 'agent-config-maintainer';
|
|
59
|
+
|
|
60
|
+
/** Delivery mode the build selected, and prints. */
|
|
61
|
+
export type PartitionMode = 'standalone/full' | 'dual-layer/partitioned';
|
|
62
|
+
|
|
63
|
+
export interface PartitionVerdict {
|
|
64
|
+
readonly mode: PartitionMode;
|
|
65
|
+
/** One clause, printable, saying WHY this mode was selected. */
|
|
66
|
+
readonly reason: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The lockfile fields the predicate reads — a structural subset. */
|
|
70
|
+
export interface LockfileFacts {
|
|
71
|
+
readonly agent_config_version?: string | undefined;
|
|
72
|
+
readonly host_layer_fingerprint?: string | undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface PartitionInputs {
|
|
76
|
+
/** `package.json` version of the checkout being built. */
|
|
77
|
+
readonly projectVersion: string;
|
|
78
|
+
/** Parsed `installed.lock`, or `null` when no global install is recorded. */
|
|
79
|
+
readonly lockfile: LockfileFacts | null;
|
|
80
|
+
/** Does the host layer exist on disk at all? */
|
|
81
|
+
readonly hostLayerPresent: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Fingerprint this checkout expects the host layer to carry. Lazy on
|
|
84
|
+
* purpose: every disqualifying branch above it returns without paying the
|
|
85
|
+
* ~100 ms hash.
|
|
86
|
+
*/
|
|
87
|
+
readonly expectedFingerprint: () => string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Select the delivery mode. Total function: always returns a verdict, never
|
|
92
|
+
* throws, never refuses.
|
|
93
|
+
*
|
|
94
|
+
* The order of the guards is the fail-safe order — cheapest and most decisive
|
|
95
|
+
* first, so the fingerprint is computed only when everything else already
|
|
96
|
+
* agrees.
|
|
97
|
+
*/
|
|
98
|
+
export function partitionVerdict(inputs: PartitionInputs): PartitionVerdict {
|
|
99
|
+
if (!inputs.hostLayerPresent) {
|
|
100
|
+
return {
|
|
101
|
+
mode: 'standalone/full',
|
|
102
|
+
reason: 'no host-global layer on this machine',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const lock = inputs.lockfile;
|
|
106
|
+
if (lock === null) {
|
|
107
|
+
return {
|
|
108
|
+
mode: 'standalone/full',
|
|
109
|
+
reason: 'host layer present but no install record (installed.lock absent)',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const recorded = lock.agent_config_version;
|
|
113
|
+
if (!recorded) {
|
|
114
|
+
return {
|
|
115
|
+
mode: 'standalone/full',
|
|
116
|
+
reason: 'install record carries no version',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Exact equality, deliberately not `>=`. A NEWER global layer is not a
|
|
120
|
+
// superset: a later release may have renamed or removed an artefact this
|
|
121
|
+
// checkout still expects, and ordering does not establish substitutability.
|
|
122
|
+
if (recorded !== inputs.projectVersion) {
|
|
123
|
+
return {
|
|
124
|
+
mode: 'standalone/full',
|
|
125
|
+
reason: `version mismatch (installed ${recorded}, building ${inputs.projectVersion})`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const installedFp = lock.host_layer_fingerprint;
|
|
129
|
+
if (!installedFp) {
|
|
130
|
+
return {
|
|
131
|
+
mode: 'standalone/full',
|
|
132
|
+
reason: 'install predates host-layer fingerprinting — re-run `agent-config install` to enable the partition',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
let expected: string;
|
|
136
|
+
try {
|
|
137
|
+
expected = inputs.expectedFingerprint();
|
|
138
|
+
} catch {
|
|
139
|
+
return {
|
|
140
|
+
mode: 'standalone/full',
|
|
141
|
+
reason: 'could not compute the expected host-layer fingerprint',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (installedFp !== expected) {
|
|
145
|
+
return {
|
|
146
|
+
mode: 'standalone/full',
|
|
147
|
+
reason: 'host-layer content differs from this checkout — re-run `agent-config install`',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
mode: 'dual-layer/partitioned',
|
|
152
|
+
reason: `host layer verified at ${recorded} (fingerprint ${installedFp.slice(0, 12)})`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Does this artefact exist ONLY for this package?
|
|
158
|
+
*
|
|
159
|
+
* True iff `workspaces:` is present, non-empty, and its every entry is
|
|
160
|
+
* {@link MAINTAINER_WORKSPACE}. Measured 2026-08-20: exactly **16** rules in
|
|
161
|
+
* `src/rules/` satisfy this — the figure ADR-236 partitions on.
|
|
162
|
+
*
|
|
163
|
+
* The direction of the default is the opposite of `rule_in_scope`'s, and that
|
|
164
|
+
* is deliberate rather than an inconsistency. There, an untagged artefact ships
|
|
165
|
+
* — over-shipping is the safe error for a scope filter. Here, an untagged
|
|
166
|
+
* artefact is NOT package-only, so it is delivered globally and withheld from
|
|
167
|
+
* the project layer. Both defaults resolve toward "the artefact is generally
|
|
168
|
+
* useful"; only one of them is about withholding.
|
|
169
|
+
*
|
|
170
|
+
* ## The state space, MEASURED rather than enumerated defensively (2026-08-21)
|
|
171
|
+
*
|
|
172
|
+
* Three of this function's branches — unreadable file, absent `workspaces:`,
|
|
173
|
+
* empty list — all resolve to `false`, and the closure review asked whether one
|
|
174
|
+
* of them was resolving by accident. Counted over all 119 files in `src/rules/`:
|
|
175
|
+
*
|
|
176
|
+
* absent=0 · empty=0 · maintainer-only=16 · mixed=103 · scalar-or-other=0
|
|
177
|
+
*
|
|
178
|
+
* So exactly TWO states occur today, both deliberate, and the 16 is the figure
|
|
179
|
+
* ADR-236 partitions on.
|
|
180
|
+
*
|
|
181
|
+
* **The other three branches are CURRENTLY ABSENT from the generated tree, not
|
|
182
|
+
* unreachable** — an earlier revision of this note said "the state space is
|
|
183
|
+
* closed by that count", and a neutral review was right to refuse it. A file can
|
|
184
|
+
* become unreadable at runtime (permissions, a partial write, a truncated
|
|
185
|
+
* checkout) and a rule can acquire a malformed `workspaces:` on any commit. The
|
|
186
|
+
* count is a current inventory, never a proof of impossibility.
|
|
187
|
+
*
|
|
188
|
+
* What the count DOES buy is a bound on the defensive work worth doing now:
|
|
189
|
+
* fixtures for the absent and malformed cases would pin behaviour on inputs no
|
|
190
|
+
* committed rule produces, so they were not written. All three branches already
|
|
191
|
+
* resolve to `false` — deliver globally, withhold from the project layer — which
|
|
192
|
+
* is the over-delivery direction rather than the losing one, so the untested
|
|
193
|
+
* branches fail safe. Should that count move, this note is the thing that dates.
|
|
194
|
+
*/
|
|
195
|
+
export function isExclusivelyPackageOnly(source_path: string): boolean {
|
|
196
|
+
let meta: Record<string, unknown>;
|
|
197
|
+
try {
|
|
198
|
+
[meta] = parseFrontmatter(fs.readFileSync(source_path, 'utf-8'));
|
|
199
|
+
} catch {
|
|
200
|
+
return false; // unreadable → not package-only → delivered globally
|
|
201
|
+
}
|
|
202
|
+
const raw = meta['workspaces'];
|
|
203
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return raw.every((w) => String(w) === MAINTAINER_WORKSPACE);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Emitter for the ONE mode line; see the visibility note on the resolver. */
|
|
210
|
+
export type Announce = (message: string) => void;
|
|
211
|
+
|
|
212
|
+
let _memo: PartitionVerdict | null = null;
|
|
213
|
+
let _announce: Announce = (m) => process.stdout.write(`${m}\n`);
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Install the emitter used for the mode line. Callers pass a function visible at
|
|
217
|
+
* their DEFAULT output level — the first implementation used one that prints only
|
|
218
|
+
* at `verbose`, which withheld ~100 rules while saying nothing in a normal run.
|
|
219
|
+
*/
|
|
220
|
+
export function setPartitionAnnounce(fn: Announce): void {
|
|
221
|
+
_announce = fn;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Test seam — drop the memo so one process can exercise both delivery modes. */
|
|
225
|
+
export function _resetPartitionVerdictForTest(): void {
|
|
226
|
+
_memo = null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Is the partition active for this generation? Memoised per process, because the
|
|
231
|
+
* fingerprint costs ~100 ms and the answer cannot change mid-run.
|
|
232
|
+
*
|
|
233
|
+
* This is the single entry point the generators call, deliberately: wiring a
|
|
234
|
+
* feature into two files that are already past the 1,500-line source ceiling
|
|
235
|
+
* costs a ratchet violation per line, so the decision, its fail-safe ordering,
|
|
236
|
+
* its printed reason and its memo all live here and the call sites are one line
|
|
237
|
+
* each.
|
|
238
|
+
*/
|
|
239
|
+
export function partitionActive(projectRoot: string): boolean {
|
|
240
|
+
return resolvePartitionVerdict(projectRoot).mode === 'dual-layer/partitioned';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Resolve the delivery mode for one generation, and say so once.
|
|
245
|
+
*
|
|
246
|
+
* Lives here rather than in `condense.ts` for two reasons. The decision belongs
|
|
247
|
+
* beside {@link partitionVerdict}, whose fail-safe ordering it depends on; and
|
|
248
|
+
* `condense.ts` is 1,500+ lines, where the source-size ratchet counts every
|
|
249
|
+
* added line as a violation — so a block that has no reason to live there is a
|
|
250
|
+
* cost as well as a misplacement.
|
|
251
|
+
*
|
|
252
|
+
* **Both council seats (2026-08-20, 2/2) required that generation PRINT the mode
|
|
253
|
+
* it selected** rather than partition silently: a withheld artefact nobody
|
|
254
|
+
* announced is exactly the under-governance the partition exists to remove. The
|
|
255
|
+
* caller supplies `announce`, and the level matters — the first implementation
|
|
256
|
+
* used an `info()` that prints only at `verbose`, so it withheld ~100 rules while
|
|
257
|
+
* saying nothing in a default run. Pass a function that is visible at the default
|
|
258
|
+
* level. Residual, stated: at an explicitly silent output level the line is
|
|
259
|
+
* dropped; that is an operator choice and the partition stays fail-safe anyway.
|
|
260
|
+
*
|
|
261
|
+
* The fingerprint compares the host layer against **what the installer recorded
|
|
262
|
+
* when it wrote that layer**, not against a re-derivation from source. That
|
|
263
|
+
* avoids guessing the installer's byte representation — a mismatch there would
|
|
264
|
+
* make the partition permanently unreachable rather than merely inactive — and it
|
|
265
|
+
* is the property the partition needs: the omitted artefacts are still present,
|
|
266
|
+
* in the form this version's installer left them.
|
|
267
|
+
*
|
|
268
|
+
* **Known residual:** an installer that crashes mid-write and still reaches the
|
|
269
|
+
* lockfile would fingerprint its own partial layer, and that fingerprint then
|
|
270
|
+
* verifies. Ordering narrows the window (the lockfile is written last) without
|
|
271
|
+
* closing it; a per-artefact manifest would close it and is not built here.
|
|
272
|
+
*/
|
|
273
|
+
export function resolvePartitionVerdict(projectRoot: string): PartitionVerdict {
|
|
274
|
+
if (_memo !== null) {
|
|
275
|
+
return _memo;
|
|
276
|
+
}
|
|
277
|
+
const layers = hostLayerInputs(os.homedir());
|
|
278
|
+
const present = layers.some((l) => {
|
|
279
|
+
try {
|
|
280
|
+
return fs.statSync(l.root).isDirectory();
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
let lock: LockfileFacts | null = null;
|
|
286
|
+
try {
|
|
287
|
+
lock = read_lockfile();
|
|
288
|
+
} catch {
|
|
289
|
+
lock = null; // unreadable record → fail safe
|
|
290
|
+
}
|
|
291
|
+
_memo = partitionVerdict({
|
|
292
|
+
projectVersion: current_package_version(projectRoot),
|
|
293
|
+
lockfile: lock,
|
|
294
|
+
hostLayerPresent: present,
|
|
295
|
+
expectedFingerprint: () => fingerprintLayers(layers),
|
|
296
|
+
});
|
|
297
|
+
_announce(`projection mode: ${_memo.mode} — ${_memo.reason}`);
|
|
298
|
+
return _memo;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Stamp the host-layer content fingerprint into the install record.
|
|
303
|
+
*
|
|
304
|
+
* The write side of the partition, and what makes it reachable at all: the build
|
|
305
|
+
* withholds artefacts only when this fingerprint matches what it finds on disk.
|
|
306
|
+
* No fingerprint recorded → full projection, which is the safe direction.
|
|
307
|
+
*
|
|
308
|
+
* **Call it AFTER the deploy and after the failed-tool postcheck.** The install
|
|
309
|
+
* writes its lockfile once before the redeploy; a fingerprint taken there would
|
|
310
|
+
* describe the PREVIOUS install and then verify against a layer this run
|
|
311
|
+
* replaced. Both council seats required the record be written last, and this is
|
|
312
|
+
* the function that respects it.
|
|
313
|
+
*
|
|
314
|
+
* On failure it reports and writes NOTHING. A missing fingerprint is fail-safe; a
|
|
315
|
+
* wrong one would authorise a partition against an unverified layer, so the two
|
|
316
|
+
* errors are not symmetric and this must never prefer the second.
|
|
317
|
+
*
|
|
318
|
+
* Lives here rather than inline in `install.ts` for the same reason
|
|
319
|
+
* {@link resolvePartitionVerdict} does: it belongs beside the predicate it
|
|
320
|
+
* serves, and `install.ts` is 5,000+ lines where the source-size ratchet counts
|
|
321
|
+
* every added line.
|
|
322
|
+
*
|
|
323
|
+
* @returns the fingerprint written, or `null` when nothing was recorded.
|
|
324
|
+
*/
|
|
325
|
+
export function stampHostLayerFingerprint(
|
|
326
|
+
installedVersion: string,
|
|
327
|
+
tools: readonly string[],
|
|
328
|
+
lockfilePath: string,
|
|
329
|
+
/** Skip entirely — e.g. when the claude-code deploy itself failed. */
|
|
330
|
+
skip: boolean,
|
|
331
|
+
report: Announce,
|
|
332
|
+
): string | null {
|
|
333
|
+
if (skip) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const fingerprint = fingerprintLayers(hostLayerInputs(os.homedir()));
|
|
338
|
+
write_lockfile(installedVersion, [...tools], {
|
|
339
|
+
path: lockfilePath,
|
|
340
|
+
host_layer_fingerprint: fingerprint,
|
|
341
|
+
});
|
|
342
|
+
report(`Host-layer fingerprint recorded: ${fingerprint.slice(0, 12)} (enables single delivery)`);
|
|
343
|
+
return fingerprint;
|
|
344
|
+
} catch (e) {
|
|
345
|
+
report(
|
|
346
|
+
`Host-layer fingerprint NOT recorded (${String(e)}) — the project layer keeps the full projection.`,
|
|
347
|
+
);
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Per-tool-directory persona list under the partition — ADR-236's closure for the
|
|
354
|
+
* one family it never reached.
|
|
355
|
+
*
|
|
356
|
+
* The partition shipped for rules and skills and stopped there, so
|
|
357
|
+
* `<repo>/.claude/personas` kept being written while `~/.claude/personas` was
|
|
358
|
+
* installed from `_CLAUDE_SKILL_BUNDLE` (`install.ts:1916-1921`). Measured
|
|
359
|
+
* 2026-08-21 on a freshly regenerated tree with the partition ACTIVE: **29 shared
|
|
360
|
+
* names**, and neither `check_single_delivery` nor `_lib/layer_overlap_notice`
|
|
361
|
+
* looked, because `personas` was in neither's `TYPES`.
|
|
362
|
+
*
|
|
363
|
+
* ## Two properties the caller depends on
|
|
364
|
+
*
|
|
365
|
+
* **Scoped to `.claude/` only.** {@link partitionActive} verifies the CLAUDE host
|
|
366
|
+
* layer against `installed.lock`. It says nothing about `~/.cursor`, so
|
|
367
|
+
* withholding a cursor persona on the strength of a claude fingerprint would
|
|
368
|
+
* deliver it nowhere — the one failure the fail-safe design exists to prevent.
|
|
369
|
+
* Every other tool directory keeps the full projection.
|
|
370
|
+
*
|
|
371
|
+
* **Reconciliation is the empty list, not a second code path.** The caller's
|
|
372
|
+
* stale-symlink sweep removes any link whose name is absent from the list it was
|
|
373
|
+
* given for that directory, so returning `[]` empties a directory an earlier
|
|
374
|
+
* version populated. A gate that only declined to WRITE would leave the existing
|
|
375
|
+
* duplicate standing — a partition that stops new duplication and keeps the old
|
|
376
|
+
* is not a partition.
|
|
377
|
+
*
|
|
378
|
+
* Verified before shipping: every one of the 29 project personas is present in
|
|
379
|
+
* the global layer (32 there, a strict superset). The partition is a removal and
|
|
380
|
+
* has no repair path, so withholding is only safe once the surviving layer is
|
|
381
|
+
* known to carry what is withheld.
|
|
382
|
+
*/
|
|
383
|
+
export function personaPartition(
|
|
384
|
+
projectRoot: string,
|
|
385
|
+
all: readonly string[],
|
|
386
|
+
): {
|
|
387
|
+
readonly all: readonly string[];
|
|
388
|
+
listFor: (toolDir: string) => readonly string[];
|
|
389
|
+
readonly note: string;
|
|
390
|
+
/**
|
|
391
|
+
* Per-directory count, for the caller's success line.
|
|
392
|
+
*
|
|
393
|
+
* Added after a neutral review (2026-08-21) caught the message claiming
|
|
394
|
+
* `N personas each` across every tool directory while `.claude/` had
|
|
395
|
+
* received zero. The suffix said "withheld" and the count contradicted it in
|
|
396
|
+
* the same sentence — a reader trusting the number is misled by a line that
|
|
397
|
+
* also carries its own correction.
|
|
398
|
+
*/
|
|
399
|
+
countFor: (toolDir: string) => number;
|
|
400
|
+
} {
|
|
401
|
+
const active = partitionActive(projectRoot);
|
|
402
|
+
return {
|
|
403
|
+
all,
|
|
404
|
+
listFor: (toolDir) => (personaWithheldFor(toolDir, active) ? [] : all),
|
|
405
|
+
note: active ? ' — .claude/ withheld: ADR-236 partition, personas arrive from ~/.claude' : '',
|
|
406
|
+
countFor: (toolDir) => (personaWithheldFor(toolDir, active) ? 0 : all.length),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* The pure half of {@link personaPartition}: withhold iff the partition is active
|
|
412
|
+
* AND this is a Claude tool directory.
|
|
413
|
+
*
|
|
414
|
+
* Split out so the decision is testable in BOTH directions. `personaPartition`
|
|
415
|
+
* reads `installed.lock` through a memoized `partitionActive`, so a test over it
|
|
416
|
+
* can only assert whatever this machine happens to be — which is a test that
|
|
417
|
+
* passes either way and therefore proves nothing. The two properties worth
|
|
418
|
+
* pinning are exactly the two this signature exposes: `.claude/` is withheld when
|
|
419
|
+
* active, and NOTHING else ever is.
|
|
420
|
+
*/
|
|
421
|
+
export function personaWithheldFor(toolDir: string, active: boolean): boolean {
|
|
422
|
+
return active && toolDir.startsWith('.claude/');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Does the project layer withhold the colon-form `/cluster:sub` commands?
|
|
427
|
+
*
|
|
428
|
+
* ## The claim this replaces, and the measurement that revised it
|
|
429
|
+
*
|
|
430
|
+
* `generate_claude_project_commands` was written on the reasoning that "Claude
|
|
431
|
+
* Code dedupes project and user scope by name, so the two copies of
|
|
432
|
+
* `/cluster:sub` collapse" — a host-behaviour claim with no first-party
|
|
433
|
+
* observation behind it. Its sibling claim about skills WAS probed under
|
|
434
|
+
* ADR-236's roadmap (Phase 5.2, `claudeMdExcludes`) and came back negative, so
|
|
435
|
+
* an unprobed host assumption in the same area was not a safe default.
|
|
436
|
+
*
|
|
437
|
+
* **MEASURED 2026-08-21, Claude Code 2.1.238.** Fixture: `/analyze:inbox`
|
|
438
|
+
* present in BOTH `~/.claude/commands/analyze/` and a temp project's
|
|
439
|
+
* `.claude/commands/analyze/`. The session reports `COUNT=1`. A control second
|
|
440
|
+
* entry (`/analyze:inboxctl`, project-only) makes the same probe report
|
|
441
|
+
* `COUNT=2` — so the 1 is an observation, not a probe that can only say one.
|
|
442
|
+
*
|
|
443
|
+
* **The half nobody had checked: the surviving copy is the GLOBAL one.** Asked
|
|
444
|
+
* for the command's description, the session returned the global body, not the
|
|
445
|
+
* project fixture's. So the dedup claim holds and its unstated corollary
|
|
446
|
+
* inverts the value of the project-layer copy: where a verified global layer
|
|
447
|
+
* exists, those 40 symlinks are written, deduped away, and LOSE. They are dead
|
|
448
|
+
* weight there — not a second listing, and not a reachability guarantee either.
|
|
449
|
+
*
|
|
450
|
+
* ## Why `partitionActive` is the right predicate rather than a new one
|
|
451
|
+
*
|
|
452
|
+
* It is true exactly when a global layer is present and verified against
|
|
453
|
+
* `installed.lock`, which is exactly the case where the project copy loses; and
|
|
454
|
+
* false on a fresh checkout, an unverified install, or a version mismatch —
|
|
455
|
+
* every case where the project copy is the only reachable one. So the
|
|
456
|
+
* fail-safe direction the whole module is built around already matches the
|
|
457
|
+
* measurement, and withholding needs no separate switch.
|
|
458
|
+
*
|
|
459
|
+
* ## Honest limits
|
|
460
|
+
*
|
|
461
|
+
* Self-report, n=1 per condition, one host version, one machine. What is NOT
|
|
462
|
+
* claimed: that older or newer hosts dedupe the same way, or that precedence is
|
|
463
|
+
* stable across them.
|
|
464
|
+
*
|
|
465
|
+
* **And a claim that WAS made here has been withdrawn as false** (neutral review,
|
|
466
|
+
* 2026-08-21). It read: *"A host that stopped deduping would show up as a
|
|
467
|
+
* double-listing, and `check_single_delivery` reports the overlap either way."*
|
|
468
|
+
* It cannot. When this predicate returns true the project copy is never written,
|
|
469
|
+
* so there is no second copy for the host to double-list or for the gate to
|
|
470
|
+
* count — it would report zero overlap while the assumption underneath had
|
|
471
|
+
* failed. Worse, `partitionActive` verifies the INSTALL (version + content
|
|
472
|
+
* fingerprint), never the HOST version whose behaviour was measured, so a host
|
|
473
|
+
* upgrade changes the premise and moves nothing this code reads.
|
|
474
|
+
*
|
|
475
|
+
* What actually holds: reachability survives a dedup change either way, because
|
|
476
|
+
* the global copy is delivered regardless. What is lost is only the project
|
|
477
|
+
* copy's redundancy, and nothing detects that. A real detector would have to
|
|
478
|
+
* pin the host version this measurement was taken against and re-probe when it
|
|
479
|
+
* moves — not built here, and named as absent rather than implied away.
|
|
480
|
+
*/
|
|
481
|
+
export function commandsWithheld(projectRoot: string): boolean {
|
|
482
|
+
return partitionActive(projectRoot);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* ## The caller's early return, flagged by review and kept
|
|
487
|
+
*
|
|
488
|
+
* `generate_claude_project_commands` applies this predicate and then returns
|
|
489
|
+
* early when `src/domains/` is absent — BEFORE its stale-link sweep. So a tree
|
|
490
|
+
* with no `src/domains` keeps whatever `.claude/commands` it already had, even
|
|
491
|
+
* when the commands are now withheld.
|
|
492
|
+
*
|
|
493
|
+
* Invariant rather than oversight: this package always ships `src/domains/`, the
|
|
494
|
+
* generator only runs from a checkout that has it, and a consumer never reaches
|
|
495
|
+
* this path. Documented rather than reordered, because hoisting the sweep above
|
|
496
|
+
* the guard would make a non-package tree lose a directory this code did not
|
|
497
|
+
* write — a worse failure than the one being avoided, and on a tree we do not own.
|
|
498
|
+
*
|
|
499
|
+
* The transition that DOES occur is covered: `partition_delivery_topology.test.ts`
|
|
500
|
+
* runs inactive→active over a fixture that has `src/domains`, and asserts both
|
|
501
|
+
* the links and the empty cluster directories are gone after one run.
|
|
502
|
+
*/
|