@event4u/agent-config 9.26.0 → 9.28.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 +12 -11
- package/CHANGELOG.md +134 -207
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/conformance.md +43 -0
- package/dist/agent-src/commands/roadmap/next.md +65 -3
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +10 -3
- package/dist/agent-src/rules/active-remediation.md +2 -0
- package/dist/agent-src/rules/analysis-skill-routing.md +2 -0
- package/dist/agent-src/rules/architecture.md +2 -0
- package/dist/agent-src/rules/artifact-drafting-protocol.md +2 -0
- package/dist/agent-src/rules/artifact-engagement-recording.md +6 -1
- package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
- package/dist/agent-src/rules/autonomous-execution.md +3 -1
- package/dist/agent-src/rules/brand-consistency.md +2 -0
- package/dist/agent-src/rules/brand-source-of-truth.md +35 -6
- package/dist/agent-src/rules/broken-access-control.md +2 -0
- package/dist/agent-src/rules/cli-output-handling.md +2 -0
- package/dist/agent-src/rules/code-comment-discipline.md +2 -0
- package/dist/agent-src/rules/code-provenance.md +2 -0
- package/dist/agent-src/rules/command-suggestion-policy.md +2 -0
- package/dist/agent-src/rules/commit-conventions.md +2 -0
- package/dist/agent-src/rules/communication-through-line.md +2 -0
- package/dist/agent-src/rules/content-quoting-floor.md +2 -0
- package/dist/agent-src/rules/context-hygiene.md +13 -1
- package/dist/agent-src/rules/copilot-routing.md +2 -0
- package/dist/agent-src/rules/council-availability.md +93 -0
- package/dist/agent-src/rules/cross-source-consistency.md +5 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +2 -0
- package/dist/agent-src/rules/delegation-policy.md +31 -5
- package/dist/agent-src/rules/design-fidelity.md +5 -0
- package/dist/agent-src/rules/design-review-after-ui-write.md +4 -0
- package/dist/agent-src/rules/devcontainer-routing.md +2 -0
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +2 -0
- package/dist/agent-src/rules/docker-commands.md +2 -0
- package/dist/agent-src/rules/domain-adoption-policy.md +2 -0
- package/dist/agent-src/rules/domain-safety-disclaimer.md +2 -0
- package/dist/agent-src/rules/domain-safety-pii.md +2 -0
- package/dist/agent-src/rules/domain-safety-retention.md +2 -0
- package/dist/agent-src/rules/downstream-changes.md +2 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
- package/dist/agent-src/rules/evaluator-independence.md +14 -3
- package/dist/agent-src/rules/external-code-graph-interop.md +4 -1
- package/dist/agent-src/rules/external-reference-deep-dive.md +2 -0
- package/dist/agent-src/rules/fast-path-marker-visibility.md +2 -0
- package/dist/agent-src/rules/finance-safety-floor.md +3 -1
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +2 -0
- package/dist/agent-src/rules/git-history-discipline.md +3 -1
- package/dist/agent-src/rules/guidelines.md +2 -0
- package/dist/agent-src/rules/history-discipline.md +5 -1
- package/dist/agent-src/rules/icon-consistency.md +2 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +2 -0
- package/dist/agent-src/rules/improve-before-implement.md +2 -0
- package/dist/agent-src/rules/invite-challenge.md +2 -0
- package/dist/agent-src/rules/laravel-routing.md +2 -0
- package/dist/agent-src/rules/laravel-translations.md +2 -0
- package/dist/agent-src/rules/legal-safety-floor.md +3 -1
- package/dist/agent-src/rules/lethal-trifecta-guard.md +2 -0
- package/dist/agent-src/rules/linked-projects-onboarding-gate.md +2 -0
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +2 -0
- package/dist/agent-src/rules/markdown-safe-codeblocks.md +2 -0
- package/dist/agent-src/rules/media-governance-routing.md +2 -0
- package/dist/agent-src/rules/media-sync-ground-truth.md +2 -0
- package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
- package/dist/agent-src/rules/missing-tool-handling.md +2 -0
- package/dist/agent-src/rules/model-recommendation.md +2 -0
- package/dist/agent-src/rules/no-attribution-footers.md +2 -0
- package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +2 -0
- package/dist/agent-src/rules/no-pr-progress-comments.md +5 -1
- package/dist/agent-src/rules/no-roadmap-references.md +2 -0
- package/dist/agent-src/rules/notes-first-reasoning.md +2 -0
- package/dist/agent-src/rules/onboarding-gate.md +5 -1
- package/dist/agent-src/rules/output-discipline.md +2 -0
- package/dist/agent-src/rules/package-ci-checks.md +2 -0
- package/dist/agent-src/rules/persona-governance.md +2 -0
- package/dist/agent-src/rules/php-coding.md +2 -0
- package/dist/agent-src/rules/prefer-enums-over-literals.md +2 -0
- package/dist/agent-src/rules/preservation-guard.md +2 -0
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +2 -0
- package/dist/agent-src/rules/question-not-instruction.md +2 -0
- package/dist/agent-src/rules/reviewer-awareness.md +2 -0
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +6 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
- package/dist/agent-src/rules/role-mode-adherence.md +3 -1
- package/dist/agent-src/rules/rule-type-governance.md +2 -0
- package/dist/agent-src/rules/runtime-safety.md +2 -0
- package/dist/agent-src/rules/scale-discipline.md +5 -1
- package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
- package/dist/agent-src/rules/self-repair-loop.md +103 -0
- package/dist/agent-src/rules/senior-engineering-discipline.md +2 -0
- package/dist/agent-src/rules/session-canary.md +62 -19
- package/dist/agent-src/rules/settings-ask-protocol.md +4 -0
- package/dist/agent-src/rules/size-enforcement.md +2 -0
- package/dist/agent-src/rules/skill-improvement-trigger.md +2 -0
- package/dist/agent-src/rules/skill-quality.md +2 -0
- package/dist/agent-src/rules/slash-command-routing-policy.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +2 -0
- package/dist/agent-src/rules/source-discovery-gate.md +2 -0
- package/dist/agent-src/rules/source-of-truth.md +2 -0
- package/dist/agent-src/rules/spreadsheet-source-quality.md +2 -0
- package/dist/agent-src/rules/strategy-safety-floor.md +3 -1
- package/dist/agent-src/rules/symfony-routing.md +2 -0
- package/dist/agent-src/rules/think-before-action.md +2 -0
- package/dist/agent-src/rules/token-budget-discipline.md +5 -1
- package/dist/agent-src/rules/token-efficiency.md +2 -0
- package/dist/agent-src/rules/token-optimizer-maintenance.md +2 -0
- package/dist/agent-src/rules/tool-safety.md +7 -1
- package/dist/agent-src/rules/ui-audit-gate.md +4 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
- package/dist/agent-src/rules/upstream-proposal.md +2 -0
- package/dist/agent-src/rules/user-interaction.md +2 -0
- package/dist/agent-src/rules/user-interrupt-priority.md +2 -0
- 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/performance-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
- package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -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/universal-project-analysis/SKILL.md +1 -1
- package/dist/agent-src/templates/AGENTS.md +6 -10
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +9 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-22GCIJ2O.js +270 -0
- package/dist/cli-delegate/{chunk-BSLUHV2F.js → chunk-2BE5I2MN.js} +156 -141
- package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-7OMQCH4V.js} +1 -1
- package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-GB2FSUEE.js} +57 -7
- package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-JXARCGVN.js} +3 -3
- package/dist/cli-delegate/{chunk-SJQMJI7M.js → chunk-PQSAQKHW.js} +467 -305
- 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 +7 -7
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +4 -4
- package/dist/cli-delegate/cmd_explain.js +80 -330
- package/dist/cli-delegate/cmd_export.js +5 -5
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_handoff.js +2 -2
- 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 +5 -5
- 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 +272 -0
- package/dist/cli-delegate/cmd_settings_migrate.js +5 -5
- package/dist/cli-delegate/cmd_settings_set.js +7 -2967
- package/dist/cli-delegate/cmd_sync.js +5 -5
- package/dist/cli-delegate/cmd_uninstall.js +5 -5
- 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 +5 -5
- package/dist/cli-delegate/cmd_versions.js +4 -4
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +184 -124
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -5
- package/dist/discovery/trust-report.md +11 -11
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +9080 -5701
- package/dist/install/install.mjs +698 -508
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +698 -437
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +80 -15
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/scripts/condense.js +33 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/io/yamlIO.js +85 -0
- package/dist/server/io/yamlIO.js.map +1 -1
- package/dist/server/routes/settings.js +1 -12
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/wizard.js +52 -2
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/shared/settingsCarveOut.js +118 -0
- package/dist/shared/settingsCarveOut.js.map +1 -0
- package/dist/shared/settingsSurface.js +7 -1
- package/dist/shared/settingsSurface.js.map +1 -1
- package/dist/ui/assets/index-DmR0mbk4.js.map +1 -1
- package/docs/contracts/settings-classes.md +29 -8
- package/docs/guidelines/agent-infra/memory-access.md +14 -3
- package/package.json +1 -1
- package/src/agent-src/templates/AGENTS.md +6 -10
- package/src/config/budgets.yml +29 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +26 -0
- package/src/scripts/_cli/cmd_brand_status.ts +174 -0
- package/src/scripts/_cli/cmd_doctor.ts +6 -21
- 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 +75 -0
- package/src/scripts/_lib/agent_settings.ts +82 -16
- package/src/scripts/_lib/carrier_divergence.ts +135 -0
- package/src/scripts/_lib/git_common_dir.ts +128 -0
- package/src/scripts/_lib/host_capability.ts +84 -1
- package/src/scripts/_lib/obligation_frequency.ts +391 -0
- 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 +342 -0
- package/src/scripts/_lib/self_repair_store.ts +100 -0
- package/src/scripts/_lib/session_register.ts +361 -0
- package/src/scripts/_lib/transcript_entry.ts +80 -0
- package/src/scripts/check_branch_freshness.ts +255 -0
- package/src/scripts/check_council_config_location.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +192 -7
- package/src/scripts/check_rule_projection_integrity.ts +364 -0
- package/src/scripts/check_standing_rule_delivery.ts +314 -0
- package/src/scripts/condense.ts +36 -2
- package/src/scripts/config/profiles.ts +13 -3
- package/src/scripts/conformance_scan.ts +307 -24
- package/src/scripts/council_availability_hook.ts +188 -0
- package/src/scripts/council_cli.ts +126 -7
- package/src/scripts/generate_settings_reference.ts +232 -0
- package/src/scripts/hook_manifest.yaml +155 -33
- package/src/scripts/hooks/block_no_verify.ts +95 -3
- package/src/scripts/hooks/block_unauthorized_git.ts +217 -2
- package/src/scripts/hooks/concern_registry.ts +12 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +450 -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 +49 -6
- package/src/scripts/measure_scope_dedup.ts +19 -13
- package/src/scripts/onboarding_gate_hook.ts +40 -1
- package/src/scripts/orchestration_backfill.ts +354 -0
- package/src/scripts/orchestration_record.ts +4 -1
- package/src/scripts/preamble_byte_census.ts +9 -20
- package/src/scripts/release.ts +142 -36
- 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 +77 -6
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/self_repair_cli.ts +240 -0
- package/src/scripts/self_repair_hook.ts +241 -0
- package/src/scripts/session_canary_hook.ts +35 -3
- package/src/scripts/session_register_hook.ts +288 -0
- package/src/scripts/sessions_cli.ts +140 -0
- package/src/scripts/validate_frontmatter.ts +68 -0
- package/src/server/io/yamlIO.ts +87 -0
- package/src/shared/settingsCarveOut.ts +143 -0
- package/src/shared/settingsSurface.ts +7 -1
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Gate — how many rule tokens does a session actually RECEIVE?
|
|
4
|
+
*
|
|
5
|
+
* P1.2 of `road-to-rule-delivery-integrity`. Claude Code loads
|
|
6
|
+
* `~/.claude/rules/` and `<project>/.claude/rules/` **both**, user layer first,
|
|
7
|
+
* with no dedup between them (host contract, quoted with citations in
|
|
8
|
+
* `agents/evidence/analysis/claude-code-rules-dir-contract.md`). So the number
|
|
9
|
+
* that matters is not what one tree contains — it is the sum of what arrives.
|
|
10
|
+
*
|
|
11
|
+
* Measured 2026-08-08 on a maintainer machine, exact BPE: **176,354 tokens
|
|
12
|
+
* standing, 74,137 of them (42%) the same 91 rules twice.** Full six-topology
|
|
13
|
+
* accounting: `agents/evidence/analysis/standing-rule-delivery-topologies.md`.
|
|
14
|
+
*
|
|
15
|
+
* Three deliberate properties:
|
|
16
|
+
*
|
|
17
|
+
* 1. **Measure what arrived, not what was projected.** Where an
|
|
18
|
+
* `InstructionsLoaded` record exists (the host logs which instruction files
|
|
19
|
+
* loaded, when, and why) the gate reads it and the filesystem sum becomes a
|
|
20
|
+
* cross-check. Where it does not, the filesystem sum stands alone. The gate
|
|
21
|
+
* always NAMES which input it used — a filesystem-only sum cannot see a rule
|
|
22
|
+
* that is projected but never loaded, nor one that loads from a layer nobody
|
|
23
|
+
* projected, and the second of those is the defect itself.
|
|
24
|
+
* 2. **`assertScanned` posture.** Zero rule files is exit 3, not green. This
|
|
25
|
+
* repo has shipped gates that scanned an emptied tree and certified coverage
|
|
26
|
+
* that did not exist; the posture is the recorded fix, not boilerplate.
|
|
27
|
+
* 3. **The ceiling cannot be raised silently.** `cap_raise_reason` in
|
|
28
|
+
* `src/config/budgets.yml` must be a real sentence naming which rule added
|
|
29
|
+
* the tokens. A placeholder fails the gate.
|
|
30
|
+
*
|
|
31
|
+
* Exit codes: `0` under cap · `1` over cap or an unstated cap raise · `2` usage
|
|
32
|
+
* error · `3` dead scan scope.
|
|
33
|
+
*
|
|
34
|
+
* ## This is deliberately NOT a CI gate
|
|
35
|
+
*
|
|
36
|
+
* Both inputs are machine-local by construction. `.claude/rules/` is gitignored
|
|
37
|
+
* (`.gitignore:123` — it is generated by `task sync` / `task generate-tools`),
|
|
38
|
+
* and no CI workflow performs a user-scope install, so on a fresh runner
|
|
39
|
+
* **neither** layer exists. Wired into `ci` it would either red on a dead scope
|
|
40
|
+
* or, with the empty case allowed, sit permanently green while certifying a
|
|
41
|
+
* property it cannot observe — the exact failure `src/config/gate-coverage.yml`
|
|
42
|
+
* was written to end. It is therefore registered as a local / doctor-surface
|
|
43
|
+
* check and is absent from `gate-coverage.yml`, whose contract is CI-identical
|
|
44
|
+
* invocation.
|
|
45
|
+
*
|
|
46
|
+
* What that costs, stated plainly: the duplicate-delivery defect is not
|
|
47
|
+
* mechanically caught for anyone who never runs this locally. The enforced layer
|
|
48
|
+
* is the installer gate (`install.ts::_gate_rule_layer_overlap`), which refuses
|
|
49
|
+
* to CREATE the doubled state; this script measures a state that already exists.
|
|
50
|
+
* Making the measurement CI-observable needs the `InstructionsLoaded` record
|
|
51
|
+
* committed from a real session, which is a different piece of work.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
import * as fs from 'node:fs';
|
|
55
|
+
import * as os from 'node:os';
|
|
56
|
+
import * as path from 'node:path';
|
|
57
|
+
import process from 'node:process';
|
|
58
|
+
import { fileURLToPath } from 'node:url';
|
|
59
|
+
|
|
60
|
+
import { assertScanned, DeadScopeError, reportScanned } from './_lib/scan_scope.js';
|
|
61
|
+
import { measure, method_note } from './_lib/token_count.js';
|
|
62
|
+
import * as rule_layer_overlap from './_lib/rule_layer_overlap.js';
|
|
63
|
+
|
|
64
|
+
const PROG = 'check_standing_rule_delivery';
|
|
65
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
66
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
67
|
+
const BUDGETS = path.join(REPO_ROOT, 'src', 'config', 'budgets.yml');
|
|
68
|
+
|
|
69
|
+
/** Placeholder reasons that do not count as a stated cause for a cap raise. */
|
|
70
|
+
const PLACEHOLDER_REASONS: readonly string[] = ['', '-', 'tbd', 'todo', 'n/a', 'na', 'none', 'bump'];
|
|
71
|
+
|
|
72
|
+
interface Budget {
|
|
73
|
+
readonly total_cap_tokens: number;
|
|
74
|
+
readonly warn_threshold: number;
|
|
75
|
+
readonly cap_raise_reason: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read the `standing_rule_delivery` block from `budgets.yml`.
|
|
80
|
+
*
|
|
81
|
+
* Deliberately a narrow line scan rather than a YAML dependency: this gate must
|
|
82
|
+
* run in a consumer tree where `yaml` may not be installed, and the three keys
|
|
83
|
+
* it needs are flat scalars. An absent block is a configuration error (exit 2),
|
|
84
|
+
* never a permissive default — a budget gate that invents its own ceiling is
|
|
85
|
+
* the same class of failure as one that scans nothing.
|
|
86
|
+
*/
|
|
87
|
+
export function readBudget(text: string): Budget | null {
|
|
88
|
+
const lines = text.split('\n');
|
|
89
|
+
const start = lines.findIndex((l) => l.trimEnd() === 'standing_rule_delivery:');
|
|
90
|
+
if (start === -1) return null;
|
|
91
|
+
let cap: number | null = null;
|
|
92
|
+
let warn: number | null = null;
|
|
93
|
+
let reason: string | null = null;
|
|
94
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
95
|
+
const line = lines[i] ?? '';
|
|
96
|
+
if (/^\S/.test(line)) break; // next top-level key ends the block
|
|
97
|
+
const m = /^\s+([a-z_]+):\s*(.*)$/.exec(line);
|
|
98
|
+
if (m === null) continue;
|
|
99
|
+
const [, key, rawValue] = m;
|
|
100
|
+
const value = (rawValue ?? '').trim().replace(/^['"]|['"]$/g, '');
|
|
101
|
+
if (key === 'total_cap_tokens') cap = Number.parseInt(value, 10);
|
|
102
|
+
if (key === 'warn_threshold') warn = Number.parseFloat(value);
|
|
103
|
+
if (key === 'cap_raise_reason') reason = value;
|
|
104
|
+
}
|
|
105
|
+
if (cap === null || Number.isNaN(cap) || warn === null || Number.isNaN(warn) || reason === null) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return { total_cap_tokens: cap, warn_threshold: warn, cap_raise_reason: reason };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Is `reason` a real stated cause, or a placeholder? */
|
|
112
|
+
export function reasonIsStated(reason: string): boolean {
|
|
113
|
+
return !PLACEHOLDER_REASONS.includes(reason.trim().toLowerCase());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** One measured layer. */
|
|
117
|
+
interface LayerMeasure {
|
|
118
|
+
readonly label: string;
|
|
119
|
+
readonly dir: string;
|
|
120
|
+
readonly files: number;
|
|
121
|
+
readonly tokens: number;
|
|
122
|
+
readonly exact: boolean;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function measureLayer(label: string, dir: string): LayerMeasure | null {
|
|
126
|
+
const layer = rule_layer_overlap.readRuleLayer(dir);
|
|
127
|
+
if (layer === null) return null;
|
|
128
|
+
const joined = [...layer.files.values()].join('\n');
|
|
129
|
+
const m = measure(joined);
|
|
130
|
+
return {
|
|
131
|
+
label,
|
|
132
|
+
dir,
|
|
133
|
+
files: layer.files.size,
|
|
134
|
+
tokens: m.tokens_gpt,
|
|
135
|
+
exact: m.tokens_gpt_exact,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Where the loaded-instruction record would live if the host wrote one.
|
|
141
|
+
*
|
|
142
|
+
* Returning a path that does not exist is the expected case today: the
|
|
143
|
+
* `InstructionsLoaded` hook is a host feature this suite does not yet bind, so
|
|
144
|
+
* the gate reports `filesystem` as its input and says so. The probe exists now
|
|
145
|
+
* so that the day the hook is bound, the gate reads the better input without a
|
|
146
|
+
* second change — and so that "which input did this number come from" is
|
|
147
|
+
* answerable rather than assumed.
|
|
148
|
+
*/
|
|
149
|
+
export function instructionsLoadedRecord(repo_root: string): string {
|
|
150
|
+
return path.join(repo_root, 'agents', 'runtime', 'metrics', 'instructions-loaded.jsonl');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
154
|
+
const unknown = argv.filter((a) => a !== '--quiet');
|
|
155
|
+
if (unknown.length > 0) {
|
|
156
|
+
process.stderr.write(`usage: ${PROG} [--quiet]\n`);
|
|
157
|
+
process.stderr.write(`${PROG}: error: unrecognized arguments: ${unknown.join(' ')}\n`);
|
|
158
|
+
return 2;
|
|
159
|
+
}
|
|
160
|
+
const quiet = argv.includes('--quiet');
|
|
161
|
+
const out = (s: string): void => {
|
|
162
|
+
if (!quiet) process.stdout.write(s);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
let budget: Budget | null;
|
|
166
|
+
try {
|
|
167
|
+
budget = readBudget(fs.readFileSync(BUDGETS, 'utf-8'));
|
|
168
|
+
} catch {
|
|
169
|
+
process.stderr.write(`❌ ${PROG}: cannot read ${path.relative(REPO_ROOT, BUDGETS)}\n`);
|
|
170
|
+
return 2;
|
|
171
|
+
}
|
|
172
|
+
if (budget === null) {
|
|
173
|
+
process.stderr.write(
|
|
174
|
+
`❌ ${PROG}: no complete \`standing_rule_delivery\` block in `
|
|
175
|
+
+ `${path.relative(REPO_ROOT, BUDGETS)} — needs total_cap_tokens, `
|
|
176
|
+
+ 'warn_threshold and cap_raise_reason. A budget gate does not invent its own ceiling.\n',
|
|
177
|
+
);
|
|
178
|
+
return 2;
|
|
179
|
+
}
|
|
180
|
+
if (!reasonIsStated(budget.cap_raise_reason)) {
|
|
181
|
+
process.stderr.write(
|
|
182
|
+
`❌ ${PROG}: cap_raise_reason is a placeholder (${JSON.stringify(budget.cap_raise_reason)}). `
|
|
183
|
+
+ 'Every ceiling value needs a stated cause naming which rule added the tokens and why.\n',
|
|
184
|
+
);
|
|
185
|
+
return 1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const layers = [
|
|
189
|
+
measureLayer('global', path.join(os.homedir(), '.claude', 'rules')),
|
|
190
|
+
measureLayer('project', path.join(REPO_ROOT, '.claude', 'rules')),
|
|
191
|
+
].filter((l): l is LayerMeasure => l !== null);
|
|
192
|
+
|
|
193
|
+
const scanned = layers.reduce((n, l) => n + l.files, 0);
|
|
194
|
+
// Two zeroes that mean opposite things, and conflating them is how this gate
|
|
195
|
+
// would become the thing it guards against:
|
|
196
|
+
//
|
|
197
|
+
// - **No layer directory at all** — a machine that has not installed the
|
|
198
|
+
// suite. Zero rule tokens are delivered, which IS the success state, so
|
|
199
|
+
// `allowEmpty` is honest here and passes scan_scope's own operational test
|
|
200
|
+
// ("if the scan root were deleted, would the reason still make sense?" —
|
|
201
|
+
// yes: nothing installed, nothing loaded).
|
|
202
|
+
// - **A layer directory that exists and holds no `.md`** — a projection that
|
|
203
|
+
// ran and produced nothing. That is blindness, not cleanliness, and it
|
|
204
|
+
// stays exit 3.
|
|
205
|
+
const no_layer_present = layers.length === 0;
|
|
206
|
+
try {
|
|
207
|
+
assertScanned({
|
|
208
|
+
gate: PROG,
|
|
209
|
+
scanned,
|
|
210
|
+
units: 'rule file(s) across the layers a session loads',
|
|
211
|
+
roots: ['~/.claude/rules', '.claude/rules'],
|
|
212
|
+
...(no_layer_present
|
|
213
|
+
? {
|
|
214
|
+
allowEmpty:
|
|
215
|
+
'EMPTY_VALID: neither ~/.claude/rules nor .claude/rules exists on this '
|
|
216
|
+
+ 'machine, so zero rule tokens are delivered — the success state, not a dead scope.',
|
|
217
|
+
}
|
|
218
|
+
: {}),
|
|
219
|
+
});
|
|
220
|
+
} catch (exc) {
|
|
221
|
+
if (exc instanceof DeadScopeError) {
|
|
222
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
223
|
+
return 3;
|
|
224
|
+
}
|
|
225
|
+
throw exc;
|
|
226
|
+
}
|
|
227
|
+
if (no_layer_present) {
|
|
228
|
+
out(
|
|
229
|
+
`${PROG}: no rule layer on this machine (neither ~/.claude/rules nor .claude/rules) — `
|
|
230
|
+
+ 'nothing is delivered, nothing to cap.\n',
|
|
231
|
+
);
|
|
232
|
+
reportScanned({
|
|
233
|
+
gate: PROG,
|
|
234
|
+
scanned: 0,
|
|
235
|
+
units: 'rule file(s)',
|
|
236
|
+
roots: ['~/.claude/rules', '.claude/rules'],
|
|
237
|
+
allowEmpty: 'EMPTY_VALID: no layer installed on this machine.',
|
|
238
|
+
});
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const record = instructionsLoadedRecord(REPO_ROOT);
|
|
243
|
+
const input = fs.existsSync(record) ? 'instructions-loaded' : 'filesystem';
|
|
244
|
+
const total = layers.reduce((n, l) => n + l.tokens, 0);
|
|
245
|
+
const exact = layers.every((l) => l.exact);
|
|
246
|
+
|
|
247
|
+
out(`${PROG} · input: ${input} · ${method_note()}\n`);
|
|
248
|
+
if (input === 'filesystem') {
|
|
249
|
+
out(
|
|
250
|
+
' note: no InstructionsLoaded record present, so this is what the filesystem\n'
|
|
251
|
+
+ ' projects, not what the host confirmed loading. A rule projected but never\n'
|
|
252
|
+
+ ' loaded, or loaded from an unprojected layer, is invisible to this input.\n',
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
for (const l of layers) {
|
|
256
|
+
out(` ${l.label.padEnd(8)} ${String(l.files).padStart(4)} file(s) ${String(l.tokens).padStart(8)} tok\n`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (layers.length === 2) {
|
|
260
|
+
const g = rule_layer_overlap.readRuleLayer(layers[0]!.dir);
|
|
261
|
+
const p = rule_layer_overlap.readRuleLayer(layers[1]!.dir);
|
|
262
|
+
if (g !== null && p !== null) {
|
|
263
|
+
const r = rule_layer_overlap.compareLayers(g.files, p.files);
|
|
264
|
+
if (r.overlap.length > 0) {
|
|
265
|
+
out(
|
|
266
|
+
` overlap ${String(r.overlap.length).padStart(4)} rule(s) in both layers `
|
|
267
|
+
+ `(${r.duplicate.length} duplicate, ${r.divergent.length} divergent)\n`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const pct = total / budget.total_cap_tokens;
|
|
274
|
+
out(
|
|
275
|
+
` TOTAL ${String(total).padStart(13)} tok / ${budget.total_cap_tokens} cap `
|
|
276
|
+
+ `(${(pct * 100).toFixed(1)}%)${exact ? '' : ' — proxy, not exact'}\n`,
|
|
277
|
+
);
|
|
278
|
+
reportScanned({
|
|
279
|
+
gate: PROG,
|
|
280
|
+
scanned,
|
|
281
|
+
units: 'rule file(s)',
|
|
282
|
+
roots: ['~/.claude/rules', '.claude/rules'],
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
if (pct > 1) {
|
|
286
|
+
process.stderr.write(
|
|
287
|
+
`❌ ${PROG}: ${total} tok of standing rule prose exceeds the ${budget.total_cap_tokens} cap. `
|
|
288
|
+
+ 'If two layers overlap, `agent-config install --layer=<global|project>` suppresses one '
|
|
289
|
+
+ 'without deleting a file; otherwise scope rules with `paths:` or move bodies into the digest.\n',
|
|
290
|
+
);
|
|
291
|
+
return 1;
|
|
292
|
+
}
|
|
293
|
+
if (pct >= budget.warn_threshold) {
|
|
294
|
+
process.stderr.write(
|
|
295
|
+
`⚠️ ${PROG}: ${(pct * 100).toFixed(1)}% of the standing rule cap used `
|
|
296
|
+
+ `(warn at ${(budget.warn_threshold * 100).toFixed(0)}%).\n`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
out(`✅ ${PROG}: standing rule delivery within cap\n`);
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function _isCliEntry(): boolean {
|
|
304
|
+
try {
|
|
305
|
+
if (process.argv[1] === undefined) return false;
|
|
306
|
+
return fs.realpathSync(_HERE) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
307
|
+
} catch {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (_isCliEntry()) {
|
|
313
|
+
process.exit(main());
|
|
314
|
+
}
|
package/src/scripts/condense.ts
CHANGED
|
@@ -1108,6 +1108,36 @@ function _scoped_rule_basenames(): string[] {
|
|
|
1108
1108
|
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
1109
1109
|
}
|
|
1110
1110
|
|
|
1111
|
+
/**
|
|
1112
|
+
* The emit plan of {@link generate_rule_symlinks}: active tool dir → the exact
|
|
1113
|
+
* rule basenames it projects there.
|
|
1114
|
+
*
|
|
1115
|
+
* Exported so a completeness gate can assert a host rule tree matches the
|
|
1116
|
+
* generator WITHOUT regenerating it. That distinction is the whole point:
|
|
1117
|
+
* `check_bridge_derivation` regenerates and then diffs, so it can only ever see
|
|
1118
|
+
* a fixpoint, and a five-week-stale tree on a working checkout is invisible to
|
|
1119
|
+
* it. A gate that reads the tree as it stands needs the expected set from
|
|
1120
|
+
* somewhere, and every other candidate source is wrong in a way that
|
|
1121
|
+
* propagates: a hand-maintained array goes obsolete silently, and
|
|
1122
|
+
* `dist/agent-src/rules/` over-counts by the ADR-004 `type: manual` rules these
|
|
1123
|
+
* trees deliberately omit (113 files, 5 manual, 108 projected).
|
|
1124
|
+
*
|
|
1125
|
+
* `generate_rule_symlinks` consumes this too, so there is one emit plan rather
|
|
1126
|
+
* than a gate's reconstruction of one.
|
|
1127
|
+
*/
|
|
1128
|
+
export function projected_rule_trees(
|
|
1129
|
+
user_home: string = process.env['HOME'] ?? os.homedir(),
|
|
1130
|
+
): Record<string, string[]> {
|
|
1131
|
+
const rules = _scoped_rule_basenames();
|
|
1132
|
+
const dedup_on = _read_projection_scope_dedup();
|
|
1133
|
+
const out: Record<string, string[]> = {};
|
|
1134
|
+
for (const tool_dir of Object.keys(_filter_tool_dirs(TOOL_DIRS))) {
|
|
1135
|
+
const skip = dedup_on ? _dedupable_rules(tool_dir, rules, user_home) : new Set<string>();
|
|
1136
|
+
out[tool_dir] = rules.filter((r) => !skip.has(r));
|
|
1137
|
+
}
|
|
1138
|
+
return out;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1111
1141
|
export function generate_rule_symlinks(): number {
|
|
1112
1142
|
const rules = _scoped_rule_basenames();
|
|
1113
1143
|
const tool_dirs = _filter_tool_dirs(TOOL_DIRS);
|
|
@@ -1130,14 +1160,18 @@ export function generate_rule_symlinks(): number {
|
|
|
1130
1160
|
);
|
|
1131
1161
|
}
|
|
1132
1162
|
|
|
1133
|
-
const dedup_on = _read_projection_scope_dedup();
|
|
1134
1163
|
const user_home = process.env['HOME'] ?? os.homedir();
|
|
1164
|
+
// Read the emit plan from the exported function rather than recomputing the
|
|
1165
|
+
// dedup skip here, so the gate that asserts this tree is complete and the
|
|
1166
|
+
// code that writes it cannot disagree.
|
|
1167
|
+
const plan = projected_rule_trees(user_home);
|
|
1135
1168
|
const skipped_per_tool: Record<string, Set<string>> = {};
|
|
1136
1169
|
|
|
1137
1170
|
let total = 0;
|
|
1138
1171
|
for (const [tool_dir, rel_prefix] of Object.entries(tool_dirs)) {
|
|
1139
1172
|
const target_dir = path.join(MODULE_STATE.PROJECT_ROOT, tool_dir);
|
|
1140
|
-
const
|
|
1173
|
+
const emit_here = new Set(plan[tool_dir] ?? rules);
|
|
1174
|
+
const skip = new Set(rules.filter((r) => !emit_here.has(r)));
|
|
1141
1175
|
skipped_per_tool[tool_dir] = skip;
|
|
1142
1176
|
_mkdirp(target_dir);
|
|
1143
1177
|
// Clean stale symlinks — a rule that became de-duplicable this run is
|
|
@@ -130,7 +130,17 @@ function _load_yaml(p: string): Dict {
|
|
|
130
130
|
return _isPlainDict(data) ? (data as Dict) : {};
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
/**
|
|
134
|
+
* The file `resolve_profile` will read for `profile_id`, or the legacy path
|
|
135
|
+
* when no candidate exists.
|
|
136
|
+
*
|
|
137
|
+
* Exported because a probe that reports "which profile is active" must name
|
|
138
|
+
* the file the resolver actually used. Re-deriving that path in the caller is
|
|
139
|
+
* how the two answers drift apart, which is the defect class
|
|
140
|
+
* `road-to-capability-answerability` exists to close — so there is exactly one
|
|
141
|
+
* implementation and both the resolver and `packs:active` call it.
|
|
142
|
+
*/
|
|
143
|
+
export function profile_file(project_root: string, profile_id: string): string {
|
|
134
144
|
const legacy = path.join(project_root, PROFILES_DIRNAME, `${profile_id}.yml`);
|
|
135
145
|
if (_exists(legacy)) {
|
|
136
146
|
return legacy;
|
|
@@ -230,11 +240,11 @@ export function resolve_profile(params: {
|
|
|
230
240
|
}
|
|
231
241
|
return _build_resolved(
|
|
232
242
|
DEFAULT_PROFILE_ID,
|
|
233
|
-
_load_yaml(
|
|
243
|
+
_load_yaml(profile_file(params.project_root, DEFAULT_PROFILE_ID)),
|
|
234
244
|
{ source: SOURCE_DEFAULT },
|
|
235
245
|
);
|
|
236
246
|
}
|
|
237
|
-
const yaml_path =
|
|
247
|
+
const yaml_path = profile_file(params.project_root, profile_id);
|
|
238
248
|
if (!_exists(yaml_path)) {
|
|
239
249
|
throw new ProfileError(
|
|
240
250
|
`profile.id=${_repr(profile_id)} (${source}) but ${yaml_path} not found`,
|