@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
|
@@ -25,6 +25,7 @@ import * as fs from 'node:fs';
|
|
|
25
25
|
import * as os from 'node:os';
|
|
26
26
|
import * as path from 'node:path';
|
|
27
27
|
|
|
28
|
+
import { comparePair } from './_lib/carrier_divergence.js';
|
|
28
29
|
import { censusRuleDir } from './preamble_byte_census.js';
|
|
29
30
|
|
|
30
31
|
const REPO_ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..');
|
|
@@ -91,17 +92,15 @@ function dedupableCount(userDir: string, sourceDir: string): { skipped: number;
|
|
|
91
92
|
* perfect version alignment. Classifying the two apart is what turns "0/110"
|
|
92
93
|
* from a puzzle into an answer: `provenanceOnly == total` means aligning
|
|
93
94
|
* versions would buy exactly nothing.
|
|
95
|
+
*
|
|
96
|
+
* The three-way comparison now lives in `_lib/carrier_divergence.ts` because a
|
|
97
|
+
* second surface needs it (`report_carrier_divergence`, round-5 Phase 1.3). The
|
|
98
|
+
* DIRECTORY WALK stays here and stays anchored on `sourceDir`: this harness asks
|
|
99
|
+
* how many installed twins the dedup could skip, which is a question about the
|
|
100
|
+
* projection's own rules, not about the union of both carriers. Only the
|
|
101
|
+
* comparison is shared — that is the part that must not drift, and it is also
|
|
102
|
+
* the part `scope_dedup.test.ts` pins.
|
|
94
103
|
*/
|
|
95
|
-
const OWNERSHIP_KEYS = ['package', 'source_path'] as const;
|
|
96
|
-
|
|
97
|
-
function stripOwnershipKeys(buf: Buffer): string {
|
|
98
|
-
return buf
|
|
99
|
-
.toString('utf-8')
|
|
100
|
-
.split('\n')
|
|
101
|
-
.filter((line) => !OWNERSHIP_KEYS.some((k) => line.startsWith(`${k}:`)))
|
|
102
|
-
.join('\n');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
104
|
interface ReachabilitySplit {
|
|
106
105
|
total: number;
|
|
107
106
|
identical: number;
|
|
@@ -130,9 +129,16 @@ function classifyReachability(userDir: string, sourceDir: string): ReachabilityS
|
|
|
130
129
|
continue;
|
|
131
130
|
}
|
|
132
131
|
const b = fs.readFileSync(path.join(sourceDir, entry));
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
switch (comparePair(a, b)) {
|
|
133
|
+
case 'identical':
|
|
134
|
+
split.identical += 1;
|
|
135
|
+
break;
|
|
136
|
+
case 'provenance-only':
|
|
137
|
+
split.provenanceOnly += 1;
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
split.bodyDiff += 1;
|
|
141
|
+
}
|
|
136
142
|
}
|
|
137
143
|
return split;
|
|
138
144
|
}
|
|
@@ -39,6 +39,45 @@ import { atomic_write_json } from "./hooks/state_io.js";
|
|
|
39
39
|
import { readHookStdin } from "./hooks/hook_stdin.js";
|
|
40
40
|
|
|
41
41
|
export const SETTINGS_FILE = ".agent-settings.yml";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Project-relative settings locations, canonical first.
|
|
45
|
+
*
|
|
46
|
+
* The canonical project settings file is `agents/settings/.agent-settings.yml`;
|
|
47
|
+
* the repo-root file is a documented legacy fallback
|
|
48
|
+
* (`agent_settings.ts::project_settings_path`). This hook read ONLY the legacy
|
|
49
|
+
* path, so on a canonically installed consumer the file was always "missing" and
|
|
50
|
+
* the gate resolved to `settings_file_missing` → do not block. An Iron-Law gate
|
|
51
|
+
* that cannot fire on a correct install is not a gate.
|
|
52
|
+
*
|
|
53
|
+
* Inlined rather than imported: this module is deliberately dependency-free so
|
|
54
|
+
* it can run as a hook on every platform. The list is two entries and its source
|
|
55
|
+
* of truth is named above.
|
|
56
|
+
*/
|
|
57
|
+
export const SETTINGS_CANDIDATES: readonly string[] = [
|
|
58
|
+
path.join("agents", "settings", SETTINGS_FILE),
|
|
59
|
+
SETTINGS_FILE,
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* First existing settings file under `consumer_root`, canonical before legacy.
|
|
64
|
+
*
|
|
65
|
+
* Falls back to the CANONICAL path when neither exists, so the "missing" branch
|
|
66
|
+
* reports the file a consumer should create rather than the one they should not.
|
|
67
|
+
*/
|
|
68
|
+
export function resolve_settings_path(consumer_root: string): string {
|
|
69
|
+
for (const rel of SETTINGS_CANDIDATES) {
|
|
70
|
+
const candidate = path.join(consumer_root, rel);
|
|
71
|
+
try {
|
|
72
|
+
if (fs.statSync(candidate).isFile()) {
|
|
73
|
+
return candidate;
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return path.join(consumer_root, SETTINGS_CANDIDATES[0] as string);
|
|
80
|
+
}
|
|
42
81
|
// NOTE: the Python docstring says `agents/runtime/state/`, but the code
|
|
43
82
|
// constant is `agents/state/`. Replicated verbatim — this is a latent
|
|
44
83
|
// docstring/code divergence in the retired Python implementation (ADR-200 § replicate
|
|
@@ -141,7 +180,7 @@ export function run(options: {
|
|
|
141
180
|
}): number {
|
|
142
181
|
const { consumer_root } = options;
|
|
143
182
|
const verbose = options.verbose ?? false;
|
|
144
|
-
const settings_path =
|
|
183
|
+
const settings_path = resolve_settings_path(consumer_root);
|
|
145
184
|
let settings_present = false;
|
|
146
185
|
try {
|
|
147
186
|
settings_present = fs.statSync(settings_path).isFile();
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Backfill orchestration telemetry from the host transcript corpus.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists (road-to-orchestrator-first-execution Phase 1). Two roadmaps
|
|
6
|
+
* are blocked on "≥20 orchestration lines in the current month", with the
|
|
7
|
+
* stated remedy "use the agent on real delegable work". Measured 2026-08-07:
|
|
8
|
+
* the agent was used on real delegable work **370 times** in this repo over one
|
|
9
|
+
* month, and `orchestration_record` — a model-carried emit step — captured
|
|
10
|
+
* exactly **one** of them. The blocker was never a usage problem; it is an
|
|
11
|
+
* instrumentation defect, and the data it was waiting for is already on disk.
|
|
12
|
+
*
|
|
13
|
+
* What this reads. The host writes one JSONL transcript per session under
|
|
14
|
+
* `~/.claude/projects/<slugified-project-path>/`. An `Agent` dispatch appears
|
|
15
|
+
* as a `tool_use` block; its result appears as a `toolUseResult` object.
|
|
16
|
+
*
|
|
17
|
+
* The coverage limit, which is the point rather than a footnote. Only
|
|
18
|
+
* SYNCHRONOUS dispatches carry cost: `totalTokens`, `totalDurationMs`,
|
|
19
|
+
* `usage`. An ASYNCHRONOUS dispatch returns a launch acknowledgement
|
|
20
|
+
* (`isAsync: true`, `status: "async_launched"`) and its completion never
|
|
21
|
+
* writes cost back into the parent transcript. So the metric-bearing
|
|
22
|
+
* population is a strict subset, and this script reports both counts rather
|
|
23
|
+
* than the flattering one. Emitting 370 lines of which 331 carry a null cost
|
|
24
|
+
* would inflate n against a gate that counts lines.
|
|
25
|
+
*
|
|
26
|
+
* What this deliberately does NOT do. It does not compute a `token_delta`
|
|
27
|
+
* against an in-session baseline. That counterfactual is not on disk and is
|
|
28
|
+
* not measured by anything, so synthesising one here and stamping it
|
|
29
|
+
* `measured` is the exact laundering `orchestration-observed-dispatch-cost`
|
|
30
|
+
* criterion (2) forbids. The orchestrated side is measured; the baseline is a
|
|
31
|
+
* downstream decision that must name its own method.
|
|
32
|
+
*
|
|
33
|
+
* Read-only. Never mutates a transcript.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { readFileSync, readdirSync, existsSync, writeFileSync } from 'node:fs';
|
|
37
|
+
import { join, basename } from 'node:path';
|
|
38
|
+
import { homedir } from 'node:os';
|
|
39
|
+
|
|
40
|
+
/** One dispatch-family. `unclassified` is a real answer, never a guess. */
|
|
41
|
+
export type DispatchFamily =
|
|
42
|
+
| 'read-only-fanout'
|
|
43
|
+
| 'ordered-steps'
|
|
44
|
+
| 'competitive'
|
|
45
|
+
| 'verdict-judge'
|
|
46
|
+
| 'unclassified';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* One backfilled dispatch.
|
|
50
|
+
*
|
|
51
|
+
* PII/leak-exclusion by construction: the host's free-text `description` and
|
|
52
|
+
* `prompt` are read to CLASSIFY and are then dropped — no field here can carry
|
|
53
|
+
* free-form content. This is not fastidiousness: the first real run emitted a
|
|
54
|
+
* historical task description carrying a denylisted external-source name and
|
|
55
|
+
* tripped `check_no_external_sources`. A type that cannot hold arbitrary text
|
|
56
|
+
* has no scrubber to fail, which is the same principle
|
|
57
|
+
* `artifact-engagement-recording` states for telemetry events. Never widen
|
|
58
|
+
* this with a `description` / `notes` / `payload` field.
|
|
59
|
+
*/
|
|
60
|
+
export interface BackfilledDispatch {
|
|
61
|
+
session_id: string;
|
|
62
|
+
tool_use_id: string;
|
|
63
|
+
timestamp: string | null;
|
|
64
|
+
subagent_type: string | null;
|
|
65
|
+
family: DispatchFamily;
|
|
66
|
+
family_signal: string;
|
|
67
|
+
resolved_model: string | null;
|
|
68
|
+
/** Measured orchestrated-side cost. `null` on an async dispatch. */
|
|
69
|
+
total_tokens: number | null;
|
|
70
|
+
wall_clock_ms: number | null;
|
|
71
|
+
tool_use_count: number | null;
|
|
72
|
+
/** `measured` only when the host recorded the cost; never inferred. */
|
|
73
|
+
cost_provenance: 'measured' | 'absent';
|
|
74
|
+
async_launch: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Slugify a project path the way the host names its transcript directory:
|
|
79
|
+
* every character outside `[A-Za-z0-9]` becomes `-`.
|
|
80
|
+
*/
|
|
81
|
+
export function transcriptDirFor(projectPath: string, home = homedir()): string {
|
|
82
|
+
return join(home, '.claude', 'projects', projectPath.replace(/[^A-Za-z0-9]/g, '-'));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Classify a dispatch into a family from OBSERVABLE fields only.
|
|
87
|
+
*
|
|
88
|
+
* The council's round-2 finding was that eight dispatch templates were being
|
|
89
|
+
* treated as one hypothesis. Family is therefore carried per line. Where no
|
|
90
|
+
* enumerated signal matches, the answer is `unclassified` — the same
|
|
91
|
+
* discipline `classifyTask` applies when no delegable signal matches. A
|
|
92
|
+
* classifier that always produces a family would make the per-family split
|
|
93
|
+
* meaningless.
|
|
94
|
+
*/
|
|
95
|
+
export function classifyFamily(
|
|
96
|
+
subagentType: string | null,
|
|
97
|
+
description: string | null,
|
|
98
|
+
prompt: string | null,
|
|
99
|
+
): { family: DispatchFamily; signal: string } {
|
|
100
|
+
const hay = `${description ?? ''}\n${prompt ?? ''}`.toLowerCase();
|
|
101
|
+
|
|
102
|
+
// Verdict/judge first — it is the narrowest and can otherwise be swallowed
|
|
103
|
+
// by the read-only signal, since most verdict passes are also read-only.
|
|
104
|
+
if (subagentType === 'production-validator') {
|
|
105
|
+
return { family: 'verdict-judge', signal: 'subagent_type=production-validator' };
|
|
106
|
+
}
|
|
107
|
+
if (/\b(verify|verdict|judge|adjudicat|audit this|review this|acceptance criterion)\b/.test(hay)) {
|
|
108
|
+
return { family: 'verdict-judge', signal: 'verdict-shaped verb in description/prompt' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (subagentType === 'Explore') {
|
|
112
|
+
return { family: 'read-only-fanout', signal: 'subagent_type=Explore' };
|
|
113
|
+
}
|
|
114
|
+
if (/\b(read-only|do not edit|never edit|investigation only|map |inventory|locate)\b/.test(hay)) {
|
|
115
|
+
return { family: 'read-only-fanout', signal: 'read-only declaration in prompt' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (/\b(step \d|then |after (the )?previous|in order|sequentially|depends on step)\b/.test(hay)) {
|
|
119
|
+
return { family: 'ordered-steps', signal: 'ordering language in prompt' };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (/\b(variant|competing|alternative approach|candidate [ab]|independently propose)\b/.test(hay)) {
|
|
123
|
+
return { family: 'competitive', signal: 'competitive framing in prompt' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { family: 'unclassified', signal: 'no enumerated family signal matched' };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
interface Extraction {
|
|
130
|
+
dispatches: BackfilledDispatch[];
|
|
131
|
+
sessions_scanned: number;
|
|
132
|
+
unparseable_lines: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Walk one project's transcript corpus and pair each `Agent` tool_use with its result. */
|
|
136
|
+
export function extract(dir: string): Extraction {
|
|
137
|
+
if (!existsSync(dir)) {
|
|
138
|
+
return { dispatches: [], sessions_scanned: 0, unparseable_lines: 0 };
|
|
139
|
+
}
|
|
140
|
+
const files = readdirSync(dir).filter((f) => f.endsWith('.jsonl'));
|
|
141
|
+
const dispatches: BackfilledDispatch[] = [];
|
|
142
|
+
let unparseable = 0;
|
|
143
|
+
|
|
144
|
+
for (const file of files) {
|
|
145
|
+
const sessionId = file.replace(/\.jsonl$/, '');
|
|
146
|
+
// tool_use_id → the dispatch we are still waiting on a result for.
|
|
147
|
+
const pending = new Map<string, Partial<BackfilledDispatch> & { prompt: string | null; description: string | null }>();
|
|
148
|
+
const emitted = new Map<string, BackfilledDispatch>();
|
|
149
|
+
|
|
150
|
+
for (const line of readFileSync(join(dir, file), 'utf8').split('\n')) {
|
|
151
|
+
if (line.trim() === '') continue;
|
|
152
|
+
let obj: Record<string, unknown>;
|
|
153
|
+
try {
|
|
154
|
+
obj = JSON.parse(line) as Record<string, unknown>;
|
|
155
|
+
} catch {
|
|
156
|
+
unparseable += 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const message = obj.message as Record<string, unknown> | undefined;
|
|
161
|
+
const content = message?.content;
|
|
162
|
+
if (Array.isArray(content)) {
|
|
163
|
+
for (const block of content) {
|
|
164
|
+
const b = block as Record<string, unknown>;
|
|
165
|
+
if (b.type !== 'tool_use' || b.name !== 'Agent') continue;
|
|
166
|
+
const input = (b.input ?? {}) as Record<string, unknown>;
|
|
167
|
+
const id = String(b.id ?? '');
|
|
168
|
+
if (id === '') continue;
|
|
169
|
+
pending.set(id, {
|
|
170
|
+
session_id: sessionId,
|
|
171
|
+
tool_use_id: id,
|
|
172
|
+
timestamp: typeof obj.timestamp === 'string' ? obj.timestamp : null,
|
|
173
|
+
subagent_type: typeof input.subagent_type === 'string' ? input.subagent_type : null,
|
|
174
|
+
description: typeof input.description === 'string' ? input.description : null,
|
|
175
|
+
prompt: typeof input.prompt === 'string' ? input.prompt : null,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const result = obj.toolUseResult as Record<string, unknown> | undefined;
|
|
181
|
+
if (!result || typeof result !== 'object') continue;
|
|
182
|
+
// The result carries no tool_use_id of its own; the host pairs it via
|
|
183
|
+
// the enclosing user message's tool_result block.
|
|
184
|
+
let resultId: string | null = null;
|
|
185
|
+
if (Array.isArray(content)) {
|
|
186
|
+
for (const block of content) {
|
|
187
|
+
const b = block as Record<string, unknown>;
|
|
188
|
+
if (b.type === 'tool_result' && typeof b.tool_use_id === 'string') {
|
|
189
|
+
resultId = b.tool_use_id;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (resultId === null || !pending.has(resultId)) continue;
|
|
194
|
+
const head = pending.get(resultId)!;
|
|
195
|
+
pending.delete(resultId);
|
|
196
|
+
|
|
197
|
+
const totalTokens = typeof result.totalTokens === 'number' ? result.totalTokens : null;
|
|
198
|
+
const { family, signal } = classifyFamily(
|
|
199
|
+
head.subagent_type ?? null,
|
|
200
|
+
head.description ?? null,
|
|
201
|
+
head.prompt,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
emitted.set(resultId, {
|
|
205
|
+
session_id: sessionId,
|
|
206
|
+
tool_use_id: resultId,
|
|
207
|
+
timestamp: head.timestamp ?? null,
|
|
208
|
+
subagent_type: head.subagent_type ?? null,
|
|
209
|
+
family,
|
|
210
|
+
family_signal: signal,
|
|
211
|
+
resolved_model: typeof result.resolvedModel === 'string' ? result.resolvedModel : null,
|
|
212
|
+
total_tokens: totalTokens,
|
|
213
|
+
wall_clock_ms: typeof result.totalDurationMs === 'number' ? result.totalDurationMs : null,
|
|
214
|
+
tool_use_count: typeof result.totalToolUseCount === 'number' ? result.totalToolUseCount : null,
|
|
215
|
+
cost_provenance: totalTokens === null ? 'absent' : 'measured',
|
|
216
|
+
async_launch: result.isAsync === true,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
dispatches.push(...emitted.values());
|
|
221
|
+
// A dispatch whose result never landed in this transcript (session ended
|
|
222
|
+
// mid-flight) is reported as a gap rather than dropped silently.
|
|
223
|
+
for (const [id, head] of pending) {
|
|
224
|
+
const { family, signal } = classifyFamily(
|
|
225
|
+
head.subagent_type ?? null,
|
|
226
|
+
head.description ?? null,
|
|
227
|
+
head.prompt,
|
|
228
|
+
);
|
|
229
|
+
dispatches.push({
|
|
230
|
+
session_id: sessionId,
|
|
231
|
+
tool_use_id: id,
|
|
232
|
+
timestamp: head.timestamp ?? null,
|
|
233
|
+
subagent_type: head.subagent_type ?? null,
|
|
234
|
+
family,
|
|
235
|
+
family_signal: signal,
|
|
236
|
+
resolved_model: null,
|
|
237
|
+
total_tokens: null,
|
|
238
|
+
wall_clock_ms: null,
|
|
239
|
+
tool_use_count: null,
|
|
240
|
+
cost_provenance: 'absent',
|
|
241
|
+
async_launch: false,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return { dispatches, sessions_scanned: files.length, unparseable_lines: unparseable };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The shape that actually gets written to disk.
|
|
251
|
+
*
|
|
252
|
+
* `tool_use_id` is pairing plumbing: it exists to match a dispatch to its
|
|
253
|
+
* result during extraction and has no analytical role afterwards. It is
|
|
254
|
+
* dropped at the TYPE level rather than scrubbed at write time, because the
|
|
255
|
+
* host ids are high-entropy opaque tokens and `check_secret_leak` flags them
|
|
256
|
+
* as candidate credentials — correctly, in the sense that it cannot know they
|
|
257
|
+
* are not. Emitting a field only to allow-list it downstream is the worse
|
|
258
|
+
* trade: the artifact does not need it.
|
|
259
|
+
*/
|
|
260
|
+
export type EmittedDispatch = Omit<BackfilledDispatch, 'tool_use_id'>;
|
|
261
|
+
|
|
262
|
+
/** Project an extracted dispatch onto the emitted shape. */
|
|
263
|
+
export function toEmitted(d: BackfilledDispatch): EmittedDispatch {
|
|
264
|
+
const { tool_use_id: _pairingKey, ...rest } = d;
|
|
265
|
+
return rest;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** Per-family roll-up. Medians, never means — one 530k outlier should not carry a family. */
|
|
269
|
+
export function summarize(dispatches: BackfilledDispatch[]): Record<string, unknown> {
|
|
270
|
+
const byFamily = new Map<string, number[]>();
|
|
271
|
+
for (const d of dispatches) {
|
|
272
|
+
if (d.cost_provenance !== 'measured' || d.total_tokens === null) continue;
|
|
273
|
+
const bucket = byFamily.get(d.family) ?? [];
|
|
274
|
+
bucket.push(d.total_tokens);
|
|
275
|
+
byFamily.set(d.family, bucket);
|
|
276
|
+
}
|
|
277
|
+
const families: Record<string, unknown> = {};
|
|
278
|
+
for (const [family, tokens] of byFamily) {
|
|
279
|
+
tokens.sort((a, b) => a - b);
|
|
280
|
+
families[family] = {
|
|
281
|
+
n: tokens.length,
|
|
282
|
+
// criterion (1): n<5 is UNDERPOWERED and is never merged to reach n.
|
|
283
|
+
power: tokens.length < 5 ? 'UNDERPOWERED' : 'reportable',
|
|
284
|
+
median_tokens: tokens[Math.floor(tokens.length / 2)],
|
|
285
|
+
min_tokens: tokens[0],
|
|
286
|
+
max_tokens: tokens[tokens.length - 1],
|
|
287
|
+
sum_tokens: tokens.reduce((a, b) => a + b, 0),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const measured = dispatches.filter((d) => d.cost_provenance === 'measured').length;
|
|
291
|
+
return {
|
|
292
|
+
dispatches_total: dispatches.length,
|
|
293
|
+
dispatches_with_measured_cost: measured,
|
|
294
|
+
dispatches_without_cost: dispatches.length - measured,
|
|
295
|
+
async_launches: dispatches.filter((d) => d.async_launch).length,
|
|
296
|
+
cost_coverage_ratio: dispatches.length === 0 ? 0 : Number((measured / dispatches.length).toFixed(4)),
|
|
297
|
+
families,
|
|
298
|
+
models: dispatches
|
|
299
|
+
.filter((d) => d.cost_provenance === 'measured')
|
|
300
|
+
.reduce<Record<string, number>>((acc, d) => {
|
|
301
|
+
const key = d.resolved_model ?? 'unknown';
|
|
302
|
+
acc[key] = (acc[key] ?? 0) + 1;
|
|
303
|
+
return acc;
|
|
304
|
+
}, {}),
|
|
305
|
+
baseline_note:
|
|
306
|
+
'No in-session counterfactual exists on disk. token_delta is NOT computed here; ' +
|
|
307
|
+
'any downstream comparison must name its own baseline method and stamp it estimated.',
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function main(argv: string[]): number {
|
|
312
|
+
const projectArg = argv.indexOf('--project');
|
|
313
|
+
const project =
|
|
314
|
+
projectArg >= 0 && argv[projectArg + 1] !== undefined ? argv[projectArg + 1]! : process.cwd();
|
|
315
|
+
const outArg = argv.indexOf('--out');
|
|
316
|
+
const dir = transcriptDirFor(project);
|
|
317
|
+
|
|
318
|
+
if (!existsSync(dir)) {
|
|
319
|
+
process.stderr.write(`orchestration_backfill: no transcript corpus at ${dir}\n`);
|
|
320
|
+
return 1;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const { dispatches, sessions_scanned, unparseable_lines } = extract(dir);
|
|
324
|
+
const summary = summarize(dispatches);
|
|
325
|
+
// Emit the corpus identity, never the absolute path: the transcript
|
|
326
|
+
// directory is rooted at the operator's home and a committed report
|
|
327
|
+
// carrying it leaks an identity-revealing local path for no analytical
|
|
328
|
+
// gain (the basename already identifies which corpus was read).
|
|
329
|
+
const report = {
|
|
330
|
+
schema_version: 1,
|
|
331
|
+
source_corpus: basename(dir),
|
|
332
|
+
sessions_scanned,
|
|
333
|
+
unparseable_lines,
|
|
334
|
+
...summary,
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
338
|
+
|
|
339
|
+
if (outArg >= 0 && argv[outArg + 1] !== undefined) {
|
|
340
|
+
const lines = dispatches
|
|
341
|
+
.filter((d) => d.cost_provenance === 'measured')
|
|
342
|
+
.map((d) => JSON.stringify({ input_kind: 'orchestration', orchestration: toEmitted(d) }))
|
|
343
|
+
.join('\n');
|
|
344
|
+
writeFileSync(argv[outArg + 1]!, lines === '' ? '' : `${lines}\n`, 'utf8');
|
|
345
|
+
process.stderr.write(
|
|
346
|
+
`orchestration_backfill: wrote ${summary.dispatches_with_measured_cost} measured line(s) to ${argv[outArg + 1]}\n`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
return 0;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (process.argv[1] !== undefined && process.argv[1].includes('orchestration_backfill')) {
|
|
353
|
+
process.exit(main(process.argv.slice(2)));
|
|
354
|
+
}
|
|
@@ -41,7 +41,10 @@ import {
|
|
|
41
41
|
type RouteTaken,
|
|
42
42
|
} from './_lib/orchestration_record.js';
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
// Exported so other writers of the same audit-log-v1 orchestration line
|
|
45
|
+
// (e.g. orchestration_record_hook.ts, the deterministic PostToolUse capture)
|
|
46
|
+
// target the exact same directory instead of duplicating the literal.
|
|
47
|
+
export const DEFAULT_DIR = 'agents/runtime/state/audit';
|
|
45
48
|
|
|
46
49
|
type Flags = Record<string, string | boolean>;
|
|
47
50
|
|
|
@@ -44,6 +44,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
44
44
|
import * as YAML from 'yaml';
|
|
45
45
|
|
|
46
46
|
import { resolveGlobalProfilePath } from './_lib/agent_user_profile.js';
|
|
47
|
+
import { censusRuleDir, type RuleDirCensus } from './_lib/carrier_divergence.js';
|
|
47
48
|
import { DEFAULT_PROJECTS_ROOT, scanTranscripts } from './_lib/cc_transcript.js';
|
|
48
49
|
import { censusDuplicateScope } from './_lib/duplicate_scope_census.js';
|
|
49
50
|
import { computeColdStarts } from './cache_realization_report.js';
|
|
@@ -71,26 +72,14 @@ function readCharsIfExists(p: string): number {
|
|
|
71
72
|
|
|
72
73
|
// ── source 1/2: rule directories ────────────────────────────────────────
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
export
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
let chars = 0;
|
|
83
|
-
for (const name of fs.readdirSync(dir)) {
|
|
84
|
-
if (!name.endsWith('.md')) continue;
|
|
85
|
-
try {
|
|
86
|
-
chars += fs.statSync(path.join(dir, name)).size;
|
|
87
|
-
files += 1;
|
|
88
|
-
} catch {
|
|
89
|
-
// Unreadable entry — skip rather than fail the whole census.
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return { files, chars };
|
|
93
|
-
}
|
|
75
|
+
// Moved to `_lib/carrier_divergence.ts` when `conformance_scan` needed the same
|
|
76
|
+
// count (round-6 Phase 4.3): that module is on a bundled CLI path and this one
|
|
77
|
+
// pulls `yaml` plus the cold-start report, so importing this file to reach fifteen
|
|
78
|
+
// lines of `statSync` was the wrong direction. Re-exported so every existing
|
|
79
|
+
// caller keeps its import and there is still one definition. Imported as well as
|
|
80
|
+
// re-exported: a bare `export … from` does not bind the name in this module's own
|
|
81
|
+
// scope, and this file uses both below.
|
|
82
|
+
export { censusRuleDir, type RuleDirCensus };
|
|
94
83
|
|
|
95
84
|
export interface PerRuleCost {
|
|
96
85
|
file: string;
|