@event4u/agent-config 14.18.0 → 14.20.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +120 -129
- package/README.md +71 -149
- package/dist/agent-src/README.md +1 -1
- package/dist/agent-src/commands/agent-handoff.md +44 -43
- package/dist/agent-src/commands/review/changes.md +19 -19
- package/dist/agent-src/commands/review.md +2 -2
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +37 -4
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/git-history-discipline.md +2 -2
- package/dist/agent-src/rules/guidelines.md +1 -1
- package/dist/agent-src/rules/session-canary.md +2 -2
- package/dist/agent-src/rules/token-efficiency.md +1 -1
- package/dist/agent-src/rules/untrusted-input-defense.md +0 -2
- package/dist/agent-src/skills/adversarial-review/SKILL.md +0 -8
- package/dist/agent-src/skills/canvas-design/SKILL.md +1 -1
- package/dist/agent-src/skills/design-intelligence/SKILL.md +1 -1
- package/dist/agent-src/skills/design-intelligence/data/app-interface.csv +1 -1
- package/dist/agent-src/skills/design-intelligence/data/motion.csv +17 -17
- package/dist/agent-src/skills/design-intelligence/data/ux-guidelines.csv +2 -2
- package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +17 -0
- package/dist/agent-src/skills/design-intelligence/references/design-rules-checklist.md +4 -4
- package/dist/agent-src/skills/design-review/SKILL.md +1 -1
- package/dist/agent-src/skills/design-variations/SKILL.md +1 -1
- package/dist/agent-src/skills/evaluate-llm-feature/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +1 -1
- package/dist/agent-src/skills/fe-design/SKILL.md +32 -5
- package/dist/agent-src/skills/fe-design/references/craft-floor.md +7 -0
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +47 -2
- package/dist/agent-src/skills/laravel-scheduling/SKILL.md +3 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
- package/dist/agent-src/skills/project-analyzer/SKILL.md +0 -6
- package/dist/agent-src/skills/react-native-setup/SKILL.md +3 -3
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/sequential-thinking/SKILL.md +18 -8
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +28 -6
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- package/dist/agent-src/skills/test-driven-development/SKILL.md +1 -3
- package/dist/agent-src/skills/ui-apply-generic/SKILL.md +1 -1
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +1 -1
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +17 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +14 -1
- package/dist/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
- package/dist/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
- package/dist/agent-src/templates/scripts/telemetry_report.ts +22 -2
- package/dist/cli/commands/recordTriggerEval.js +2 -2
- package/dist/cli/main.js +1 -1
- package/dist/cli/mcp/dispatch.js +10 -0
- package/dist/cli/mcp/stdio.js +33 -2
- package/dist/cli/mcp/telemetry.js +227 -0
- package/dist/cli-delegate/{chunk-AKJGUQNO.js → chunk-C6IPZRDA.js} +484 -353
- package/dist/cli-delegate/{chunk-CYH5SP6M.js → chunk-I6RHFQEG.js} +151 -16
- package/dist/cli-delegate/{chunk-7RXIKF62.js → chunk-NUMQHLS5.js} +304 -136
- package/dist/cli-delegate/{chunk-PMEKGDED.js → chunk-PI2GU3DP.js} +163 -1
- package/dist/cli-delegate/{chunk-7PRE6ZV2.js → chunk-YS6XTLEK.js} +199 -4
- package/dist/cli-delegate/chunk-ZVBOHSVF.js +1109 -0
- package/dist/cli-delegate/cmd_adr_effective.js +318 -81
- package/dist/cli-delegate/cmd_conformance.js +489 -121
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +42 -12
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_session_recycle.js +59 -902
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +34 -33
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +4455 -3025
- package/dist/install/install.mjs +975 -689
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2087 -1134
- package/dist/scripts/_lib/model_tier.js +1 -1
- package/dist/scripts/mcp_server/telemetry.js +148 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +1 -1
- package/docs/guidelines/agent-infra/naming.md +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/docs/guidelines/agent-infra/security-lint-containment.md +46 -6
- package/docs/guidelines/agent-infra/self-improvement-pipeline.md +4 -4
- package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +1 -1
- package/docs/guidelines/design-antipatterns.md +87 -64
- package/docs/guidelines/redundancy-taxonomy.md +28 -0
- package/package.json +1 -1
- package/src/agent-src/README.md +1 -1
- package/src/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
- package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
- package/src/agent-src/templates/scripts/telemetry_report.ts +22 -2
- package/src/config/assurance-capability-registry.json +9 -0
- package/src/config/ci-local-parity.yml +6 -0
- package/src/config/gate-coverage.yml +225 -0
- package/src/config/gate-violation-baselines.json +18 -6
- package/src/config/release-gate-locality.yml +12 -0
- package/src/scripts/_cli/cmd_doctor.ts +2 -2
- package/src/scripts/_cli/cmd_handoff.ts +1 -1
- package/src/scripts/_cli/cmd_session_recycle.ts +51 -3
- package/src/scripts/_cli/cmd_update.ts +1 -1
- package/src/scripts/_cli/cmd_versions.ts +1 -1
- package/src/scripts/_lib/adr_frontmatter.ts +44 -0
- package/src/scripts/_lib/audit_field_provenance.ts +44 -3
- package/src/scripts/_lib/consequence_objects.ts +152 -0
- package/src/scripts/_lib/exec_evidence.ts +7 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +2 -2
- package/src/scripts/_lib/host_capability.ts +38 -0
- package/src/scripts/_lib/host_permission_checks.ts +251 -0
- package/src/scripts/_lib/install_reach_checks.ts +33 -1
- package/src/scripts/_lib/mcp_bridge.ts +222 -10
- package/src/scripts/_lib/mcp_consent_residual.ts +173 -0
- package/src/scripts/_lib/model_tier.ts +1 -1
- package/src/scripts/_lib/obligation_frequency.ts +2 -2
- package/src/scripts/_lib/orchestration_record.ts +23 -1
- package/src/scripts/_lib/recycle_envelope_paths.ts +190 -0
- package/src/scripts/_lib/release_highlights.ts +93 -0
- package/src/scripts/_lib/release_material.ts +64 -1
- package/src/scripts/_lib/release_position.ts +96 -0
- package/src/scripts/_lib/review_skipped_record.ts +17 -1
- package/src/scripts/_lib/runtime_journal.ts +26 -3
- package/src/scripts/_lib/runtime_wiring_checks.ts +12 -1
- package/src/scripts/_lib/security_lint.ts +179 -7
- package/src/scripts/_lib/self_repair.ts +141 -6
- package/src/scripts/_lib/self_repair_store.ts +273 -7
- package/src/scripts/_lib/session_register.ts +11 -6
- package/src/scripts/_lib/sqlite_schema.ts +22 -0
- package/src/scripts/_lib/subagent_capsule.ts +155 -3
- package/src/scripts/_lib/surface.ts +86 -0
- package/src/scripts/_lib/ui_authority.ts +116 -0
- package/src/scripts/adr/regenerate_index.ts +54 -1
- package/src/scripts/adr_cite_check.ts +71 -15
- package/src/scripts/ai_council/clients.ts +1 -1
- package/src/scripts/autonomy_friction_corpus.ts +229 -0
- package/src/scripts/build_proof.ts +15 -2
- package/src/scripts/build_rule_trigger_matrix.ts +5 -0
- package/src/scripts/check_beta_review_markers.ts +141 -17
- package/src/scripts/check_enforcement_coverage.ts +1 -1
- package/src/scripts/check_enforcement_denominator.ts +121 -11
- package/src/scripts/check_finding_dispositions.ts +66 -9
- package/src/scripts/check_gate_coverage.ts +96 -0
- package/src/scripts/check_mcp_doc_drift.ts +217 -0
- package/src/scripts/check_pack_size.ts +321 -3
- package/src/scripts/check_release_highlights.ts +74 -1
- package/src/scripts/check_skill_activation_claim.ts +179 -0
- package/src/scripts/check_trigger_evals.ts +1 -1
- package/src/scripts/conformance_scan.ts +44 -3
- package/src/scripts/explain_run.ts +1 -1
- package/src/scripts/generate_capability_matrix.ts +1 -1
- package/src/scripts/git_authorization_hook.ts +75 -1
- package/src/scripts/handoff_context_hook.ts +54 -7
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +14 -11
- package/src/scripts/hooks/augment-dispatcher.sh +1 -1
- package/src/scripts/hooks/category_a.ts +291 -0
- package/src/scripts/hooks/cline-dispatcher.sh +1 -1
- package/src/scripts/hooks/cursor-dispatcher.sh +11 -5
- package/src/scripts/hooks/dispatch_hook.ts +17 -22
- package/src/scripts/hooks/git_command_classifier.ts +156 -1
- package/src/scripts/hooks/host_lowering.ts +215 -0
- package/src/scripts/hooks/host_lowering.yaml +185 -0
- package/src/scripts/hooks/host_semantics.ts +105 -37
- package/src/scripts/hooks/journal_record_hook.ts +10 -0
- package/src/scripts/hooks/permission_gate.ts +117 -0
- package/src/scripts/hooks/session_eol_hook.ts +18 -7
- package/src/scripts/hooks_status.ts +12 -10
- package/src/scripts/install.ts +57 -86
- package/src/scripts/install_anthropic_key.sh +2 -2
- package/src/scripts/lint_agent_security.ts +316 -25
- package/src/scripts/lint_confusables.ts +42 -6
- package/src/scripts/lint_design_antipattern_parity.ts +80 -0
- package/src/scripts/lint_evidence_artifacts.ts +45 -1
- package/src/scripts/lint_handoffs.ts +0 -132
- package/src/scripts/lint_hidden_unicode.ts +49 -7
- package/src/scripts/lint_hook_manifest.ts +87 -3
- package/src/scripts/lint_instruction_smuggling.ts +42 -6
- package/src/scripts/lint_mcp_config_security.ts +42 -6
- package/src/scripts/lint_motion_authority_drift.ts +731 -0
- package/src/scripts/lint_readme_size.ts +158 -12
- package/src/scripts/lint_reasoning_fixtures.ts +216 -0
- package/src/scripts/lint_roadmap_later_disposition.ts +217 -27
- package/src/scripts/lint_skill_frontmatter_safety.ts +43 -7
- package/src/scripts/lint_skill_trigger_contradiction.ts +161 -0
- package/src/scripts/mcp_server/resources.ts +41 -0
- package/src/scripts/mcp_server/telemetry.ts +19 -4
- package/src/scripts/measure_sibling_naming.ts +150 -0
- package/src/scripts/orchestration_record.ts +12 -1
- package/src/scripts/redact_hook_capture.ts +1 -1
- package/src/scripts/release.ts +24 -24
- package/src/scripts/release_publication.ts +6 -2
- package/src/scripts/report_skill_activation.ts +319 -7
- package/src/scripts/schemas/review-findings.schema.json +5 -0
- package/src/scripts/schemas/skill.schema.json +2 -2
- package/src/scripts/self_repair_cli.ts +31 -1
- package/src/scripts/session_canary_hook.ts +1 -1
- package/src/scripts/session_register_hook.ts +4 -3
- package/src/scripts/skill_scout.ts +69 -0
- package/src/scripts/trigger_eval_grandfather.json +5 -1
- package/src/scripts/validate_agent_settings.ts +1 -1
- package/dist/cli-delegate/chunk-2GNWGMAL.js +0 -172
- package/dist/cli-delegate/chunk-LPPWO5CE.js +0 -161
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* autonomy_friction_corpus — how many host confirmations each payload class
|
|
3
|
+
* costs, measured through the real dispatcher.
|
|
4
|
+
*
|
|
5
|
+
* WHY A CONFIRMATION COUNT AND NOT A GATE COUNT. "Three gates fire" says
|
|
6
|
+
* nothing about what the operator experiences; "this call costs one
|
|
7
|
+
* confirmation" is the quantity the round is about. A corpus counting gates
|
|
8
|
+
* would go green on a change that fired fewer gates and prompted just as often.
|
|
9
|
+
*
|
|
10
|
+
* WHAT IT MEASURES, STATED NARROWLY. The number of confirmations THIS PACKAGE
|
|
11
|
+
* leaves on the table for a call — 0 when the dispatcher hands the host an
|
|
12
|
+
* explicit `permissionDecision: allow`, 1 when it emits no permission field and
|
|
13
|
+
* the host's own machinery decides. It is NOT a measurement of what the host
|
|
14
|
+
* finally does: a consumer whose settings already allow the call is not
|
|
15
|
+
* prompted either way, and a confined session ignores our allow entirely
|
|
16
|
+
* (`permissionDecision=allow ignored: a confined session takes grants only
|
|
17
|
+
* from its command line`). Reading this as a host-behaviour measurement is the
|
|
18
|
+
* fact-about-our-plumbing-written-as-a-fact-about-the-host collapse the hook
|
|
19
|
+
* contract warns about three times.
|
|
20
|
+
*
|
|
21
|
+
* A REFUSAL IS NOT A CONFIRMATION. A case the dispatcher blocks (exit 2) is
|
|
22
|
+
* reported as `refused` and scores no confirmations — counting a refusal as
|
|
23
|
+
* friction would reward removing a gate, which is the opposite of the point.
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* ./scripts-run src/scripts/autonomy_friction_corpus [--json]
|
|
27
|
+
* ./agent-config … (no verb — this is a package-internal measurement)
|
|
28
|
+
*/
|
|
29
|
+
import { spawnSync } from "node:child_process";
|
|
30
|
+
import * as fs from "node:fs";
|
|
31
|
+
import * as os from "node:os";
|
|
32
|
+
import * as path from "node:path";
|
|
33
|
+
import process from "node:process";
|
|
34
|
+
import { fileURLToPath } from "node:url";
|
|
35
|
+
|
|
36
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const REPO_ROOT = path.resolve(HERE, "..", "..");
|
|
38
|
+
const ENTRY = path.join(REPO_ROOT, "src", "scripts", "hooks", "dispatch_entry.ts");
|
|
39
|
+
const TSX_BIN = path.join(
|
|
40
|
+
REPO_ROOT,
|
|
41
|
+
"node_modules",
|
|
42
|
+
".bin",
|
|
43
|
+
process.platform === "win32" ? "tsx.cmd" : "tsx",
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
/** One payload class, with the confirmations it is expected to cost. */
|
|
47
|
+
export interface FrictionCase {
|
|
48
|
+
id: string;
|
|
49
|
+
/** The class the owner named, in their words. */
|
|
50
|
+
klass: string;
|
|
51
|
+
tool: string;
|
|
52
|
+
input: Record<string, unknown>;
|
|
53
|
+
/** Confirmations this call SHOULD cost. */
|
|
54
|
+
expected: number;
|
|
55
|
+
/** Why that number, in one line. */
|
|
56
|
+
because: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** One measured result. */
|
|
60
|
+
export interface FrictionResult extends FrictionCase {
|
|
61
|
+
observed: number;
|
|
62
|
+
refused: boolean;
|
|
63
|
+
pass: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The six classes the round named, plus the directory-flag form.
|
|
68
|
+
*
|
|
69
|
+
* The seventh is not padding: cases 1 and 2 are the shape the canon stopped
|
|
70
|
+
* teaching, and without the shape that replaced them the corpus would record
|
|
71
|
+
* the cost and not the fix.
|
|
72
|
+
*/
|
|
73
|
+
export const CASES: readonly FrictionCase[] = [
|
|
74
|
+
{
|
|
75
|
+
id: "directory-change",
|
|
76
|
+
klass: "directory change",
|
|
77
|
+
tool: "Bash",
|
|
78
|
+
input: { command: "cd sub" },
|
|
79
|
+
expected: 1,
|
|
80
|
+
because:
|
|
81
|
+
"a bare `cd` is not category A — the canon's answer is a directory flag, " +
|
|
82
|
+
"not an allow for changing directory",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "directory-change-compounded",
|
|
86
|
+
klass: "directory change compounded with a status read",
|
|
87
|
+
tool: "Bash",
|
|
88
|
+
input: { command: "cd sub && git status" },
|
|
89
|
+
expected: 1,
|
|
90
|
+
because:
|
|
91
|
+
"a compound command is refused before its argv is read — a safe head " +
|
|
92
|
+
"token must not be able to carry a second command",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "directory-flag-status",
|
|
96
|
+
klass: "the directory-flag form of the case above",
|
|
97
|
+
tool: "Bash",
|
|
98
|
+
input: { command: "git -C sub status" },
|
|
99
|
+
expected: 0,
|
|
100
|
+
because: "one simple command, a read-only git subcommand, inside the working tree",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: "test-runner-in-subdirectory",
|
|
104
|
+
klass: "a test runner invoked from a subdirectory",
|
|
105
|
+
tool: "Bash",
|
|
106
|
+
input: { command: "vitest run sub" },
|
|
107
|
+
expected: 0,
|
|
108
|
+
because: "a test runner admitted by name — the name IS the operation",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "workspace-package-manager",
|
|
112
|
+
klass: "a workspace package manager",
|
|
113
|
+
tool: "Bash",
|
|
114
|
+
input: { command: "npm run test" },
|
|
115
|
+
expected: 0,
|
|
116
|
+
because: "a declared subcommand with a declared script name",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "consequence-authorized",
|
|
120
|
+
klass: "a consequence operation with the authorizing phrase present",
|
|
121
|
+
tool: "Bash",
|
|
122
|
+
input: { command: "terraform apply" },
|
|
123
|
+
expected: 1,
|
|
124
|
+
because:
|
|
125
|
+
"the phrase changes the RECORD, never the prompt — after ADR-254 the " +
|
|
126
|
+
"Hard Floor is model-held and this package grants nothing",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "consequence-unauthorized",
|
|
130
|
+
klass: "the same operation with the authorizing phrase absent",
|
|
131
|
+
tool: "Bash",
|
|
132
|
+
input: { command: "terraform apply" },
|
|
133
|
+
expected: 1,
|
|
134
|
+
because:
|
|
135
|
+
"identical to the case above BY DESIGN — that the two are the same " +
|
|
136
|
+
"number is the finding, not a defect in the corpus",
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
/** Run one case through the real dispatcher and count its confirmations. */
|
|
141
|
+
export function measure(c: FrictionCase, workspace: string): FrictionResult {
|
|
142
|
+
const payload = JSON.stringify({
|
|
143
|
+
session_id: "autonomy-friction-corpus",
|
|
144
|
+
cwd: workspace,
|
|
145
|
+
hook_event_name: "PreToolUse",
|
|
146
|
+
tool_name: c.tool,
|
|
147
|
+
tool_input: c.input,
|
|
148
|
+
});
|
|
149
|
+
const r = spawnSync(
|
|
150
|
+
TSX_BIN,
|
|
151
|
+
[ENTRY, "--platform", "claude", "--event", "pre_tool_use", "--project-dir", workspace],
|
|
152
|
+
{
|
|
153
|
+
encoding: "utf-8",
|
|
154
|
+
cwd: workspace,
|
|
155
|
+
input: payload,
|
|
156
|
+
timeout: 120_000,
|
|
157
|
+
env: { ...process.env, AGENT_CONFIG_PACKAGE_ROOT: REPO_ROOT },
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
const refused = r.status === 2;
|
|
161
|
+
const allowed = !refused && (r.stdout ?? "").includes('"permissionDecision":"allow"');
|
|
162
|
+
const observed = refused || allowed ? 0 : 1;
|
|
163
|
+
return { ...c, observed, refused, pass: refused || observed === c.expected };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function runCorpus(): FrictionResult[] {
|
|
167
|
+
const workspace = fs.mkdtempSync(path.join(os.tmpdir(), "ac-friction-"));
|
|
168
|
+
fs.mkdirSync(path.join(workspace, "sub"), { recursive: true });
|
|
169
|
+
try {
|
|
170
|
+
return CASES.map((c) => measure(c, workspace));
|
|
171
|
+
} finally {
|
|
172
|
+
fs.rmSync(workspace, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function render(results: readonly FrictionResult[]): string {
|
|
177
|
+
const out: string[] = [];
|
|
178
|
+
out.push("autonomy_friction_corpus — confirmations per payload class");
|
|
179
|
+
out.push("");
|
|
180
|
+
out.push(" measured through the real dispatcher; a confirmation is one this");
|
|
181
|
+
out.push(" package leaves to the host, NOT one the host necessarily shows.");
|
|
182
|
+
out.push("");
|
|
183
|
+
for (const r of results) {
|
|
184
|
+
const mark = r.pass ? "✅" : "❌";
|
|
185
|
+
const obs = r.refused ? "refused" : String(r.observed);
|
|
186
|
+
out.push(` ${mark} ${r.id.padEnd(30)} expected ${r.expected} observed ${obs}`);
|
|
187
|
+
out.push(` ${r.klass}`);
|
|
188
|
+
out.push(` ${r.because}`);
|
|
189
|
+
}
|
|
190
|
+
const failures = results.filter((r) => !r.pass);
|
|
191
|
+
out.push("");
|
|
192
|
+
out.push(
|
|
193
|
+
failures.length === 0
|
|
194
|
+
? ` ${results.length} case(s), every observation matches its expectation`
|
|
195
|
+
: ` ${failures.length} of ${results.length} case(s) miss their expectation: ` +
|
|
196
|
+
failures.map((f) => f.id).join(", "),
|
|
197
|
+
);
|
|
198
|
+
return out.join("\n") + "\n";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
202
|
+
const json = argv.includes("--json");
|
|
203
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
204
|
+
process.stdout.write(
|
|
205
|
+
"usage: autonomy_friction_corpus [--json]\n" +
|
|
206
|
+
" Reports the confirmations each payload class costs, measured\n" +
|
|
207
|
+
" through the real dispatcher. Exit 1 when an observation misses\n" +
|
|
208
|
+
" its expectation.\n",
|
|
209
|
+
);
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
const results = runCorpus();
|
|
213
|
+
process.stdout.write(json ? `${JSON.stringify(results, null, 2)}\n` : render(results));
|
|
214
|
+
return results.every((r) => r.pass) ? 0 : 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function _isCliEntry(): boolean {
|
|
218
|
+
const argv1 = process.argv[1];
|
|
219
|
+
if (argv1 === undefined) return false;
|
|
220
|
+
try {
|
|
221
|
+
return fs.realpathSync(argv1) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
222
|
+
} catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (_isCliEntry()) {
|
|
228
|
+
process.exit(main());
|
|
229
|
+
}
|
|
@@ -413,10 +413,20 @@ function render(): string {
|
|
|
413
413
|
{
|
|
414
414
|
const rows = collectEnforcement();
|
|
415
415
|
const s = summariseEnforcement(rows);
|
|
416
|
+
// The split ships WITH the figure, and the word `yet` travels only with
|
|
417
|
+
// the half it is true of. `kernel_denied` rules cannot carry an
|
|
418
|
+
// `enforced_by` field at all — `block_kernel_rule_writes` refuses the
|
|
419
|
+
// write with no agent-accessible override — so publishing them as "not
|
|
420
|
+
// declared yet" promises a future that does not exist for them. They
|
|
421
|
+
// still count as uncovered — the counting policy is unchanged; only the
|
|
422
|
+
// claim about their future is.
|
|
416
423
|
L.push(
|
|
417
424
|
`**Axis 1 — enforcement level per rule.** ${s.total} rules · ` +
|
|
418
425
|
`${s.blocking} blocking (${s.blocking_pct}%) · ${s.observer} observer · ` +
|
|
419
|
-
`${s.local_only} local-only · ${s.undeclared} undeclared
|
|
426
|
+
`${s.local_only} local-only · ${s.undeclared} undeclared — of which ` +
|
|
427
|
+
`${s.kernel_denied} kernel-denied (\`block_kernel_rule_writes\` refuses an ` +
|
|
428
|
+
`\`enforced_by\` write on a kernel rule, so no declaration is reachable ` +
|
|
429
|
+
`for them at all) and ${s.undeclared - s.kernel_denied} not declared yet.`,
|
|
420
430
|
);
|
|
421
431
|
L.push('');
|
|
422
432
|
// The denominator, WITH the frame that produced it. Until 2026-08-23 the
|
|
@@ -436,7 +446,10 @@ function render(): string {
|
|
|
436
446
|
L.push(`| \`${r.id}\` | ${r.effective} | ${decl} |`);
|
|
437
447
|
}
|
|
438
448
|
L.push('');
|
|
439
|
-
L.push(
|
|
449
|
+
L.push(
|
|
450
|
+
`Undeclared rules (${s.undeclared}, of which ${s.kernel_denied} kernel-denied) ` +
|
|
451
|
+
'carry no row — an honest gap beats a false claim.',
|
|
452
|
+
);
|
|
440
453
|
}
|
|
441
454
|
L.push('');
|
|
442
455
|
{
|
|
@@ -126,6 +126,11 @@ add('artifact-engagement-recording.md', 'phase-step / task end', 'mechanical-alr
|
|
|
126
126
|
add('model-recommendation.md', 'task-start / topic-shift', 'hook',
|
|
127
127
|
'output', 'low', '2a', 'no',
|
|
128
128
|
'Phase 5 prototype target. Marker injection at first user msg + topic-change detection.');
|
|
129
|
+
// HISTORICAL row. `src/rules/capture-learnings.md` no longer exists, so this
|
|
130
|
+
// classification is never emitted (the generator keys on files actually present
|
|
131
|
+
// in src/rules/); the live rule for this trigger is `skill-improvement-trigger`,
|
|
132
|
+
// classified two rows below. Kept rather than deleted so the inventory this
|
|
133
|
+
// table records stays readable against the round it was taken in.
|
|
129
134
|
add('capture-learnings.md', 'task completion', 'hook', 'output',
|
|
130
135
|
'medium', '2a', 'no', 'Post-task marker; learning detection is fuzzy');
|
|
131
136
|
add('skill-improvement-trigger.md', 'task completion (settings.skill_improvement)',
|
|
@@ -63,28 +63,34 @@ interface LapsedBaseline {
|
|
|
63
63
|
contracts: string[];
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
// Keyed by root: an unkeyed cache returns the REPO baseline for a fixture root
|
|
67
|
+
// whenever the repo was read first, which silently judges a fixture against
|
|
68
|
+
// production data and is invisible in the result.
|
|
69
|
+
const _baselineCache = new Map<string, Set<string>>();
|
|
67
70
|
|
|
68
71
|
export function loadLapsedBaseline(root: string = ROOT): Set<string> {
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
const hit = _baselineCache.get(root);
|
|
73
|
+
if (hit !== undefined) {
|
|
74
|
+
return hit;
|
|
71
75
|
}
|
|
72
76
|
const p = path.join(root, BASELINE_REL);
|
|
73
77
|
if (!fs.existsSync(p)) {
|
|
74
78
|
// The baseline reaching zero is the SUCCESS state: the file is deleted
|
|
75
79
|
// in the same change that flips the severity. An absent file therefore
|
|
76
80
|
// means "no inherited debt", i.e. every lapse is fresh and errors.
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
const empty = new Set<string>();
|
|
82
|
+
_baselineCache.set(root, empty);
|
|
83
|
+
return empty;
|
|
79
84
|
}
|
|
80
85
|
const parsed = JSON.parse(fs.readFileSync(p, 'utf-8')) as LapsedBaseline;
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
const loaded = new Set(parsed.contracts);
|
|
87
|
+
_baselineCache.set(root, loaded);
|
|
88
|
+
return loaded;
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
/** Test seam — the module-level cache would otherwise outlive a fixture. */
|
|
86
92
|
export function _resetLapsedBaseline(): void {
|
|
87
|
-
_baselineCache
|
|
93
|
+
_baselineCache.clear();
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
/**
|
|
@@ -115,12 +121,43 @@ const SUPERSEDED_RE = /^superseded-by:\s*\S+\s*$/m;
|
|
|
115
121
|
|
|
116
122
|
const MAX_REVIEW_WINDOW_DAYS = 90;
|
|
117
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Forward horizon for the upcoming-lapse report.
|
|
126
|
+
*
|
|
127
|
+
* The gate reports a contract on the day it lapses, which is the day it is
|
|
128
|
+
* already too late to decide anything: a FRESH lapse errors, and every pull
|
|
129
|
+
* request in the repository reds until someone edits a contract under time
|
|
130
|
+
* pressure. The horizon names the set that is ABOUT to enter that branch.
|
|
131
|
+
*
|
|
132
|
+
* The number is bounded on both sides: too wide and the report names most of
|
|
133
|
+
* the forward-dated corpus on every run, which is the same as naming none; too
|
|
134
|
+
* narrow and it arrives after the decision needed to be made.
|
|
135
|
+
*/
|
|
136
|
+
const HORIZON_DAYS = 14;
|
|
137
|
+
|
|
118
138
|
interface Violation {
|
|
119
139
|
file: string;
|
|
120
140
|
reason: string;
|
|
121
141
|
severity: 'error' | 'warning';
|
|
122
142
|
}
|
|
123
143
|
|
|
144
|
+
/**
|
|
145
|
+
* A contract whose window closes within {@link HORIZON_DAYS} and which is NOT
|
|
146
|
+
* in the frozen baseline — i.e. one that will enter the ERROR branch, not the
|
|
147
|
+
* inherited-warning one.
|
|
148
|
+
*
|
|
149
|
+
* Deliberately NOT a {@link Violation}: it carries no severity and never moves
|
|
150
|
+
* the exit code. A third severity class printed into the same stream as the 84
|
|
151
|
+
* standing warnings is the way this work changes nothing (risk 1 on the
|
|
152
|
+
* roadmap's own register), so the horizon is a separate, clearly-labelled
|
|
153
|
+
* section and the exit code stays a function of real violations alone.
|
|
154
|
+
*/
|
|
155
|
+
interface Upcoming {
|
|
156
|
+
file: string;
|
|
157
|
+
date: string;
|
|
158
|
+
daysOut: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
124
161
|
function _exists(p: string): boolean {
|
|
125
162
|
try {
|
|
126
163
|
fs.statSync(p);
|
|
@@ -184,7 +221,7 @@ function _ordinalToISO(ordinal: number): string {
|
|
|
184
221
|
return `${y}-${m}-${d}`;
|
|
185
222
|
}
|
|
186
223
|
|
|
187
|
-
function check_one(p: string, todayOrdinal: number): Violation[] {
|
|
224
|
+
function check_one(p: string, todayOrdinal: number, root: string = ROOT): Violation[] {
|
|
188
225
|
const fm = read_frontmatter(p);
|
|
189
226
|
if (fm === null) {
|
|
190
227
|
return [];
|
|
@@ -199,7 +236,7 @@ function check_one(p: string, todayOrdinal: number): Violation[] {
|
|
|
199
236
|
['superseded-by', SUPERSEDED_RE.test(fm)],
|
|
200
237
|
];
|
|
201
238
|
const setMarkers = markers.filter(([, present]) => present).map(([name]) => name);
|
|
202
|
-
const rel = _relPosix(p,
|
|
239
|
+
const rel = _relPosix(p, root);
|
|
203
240
|
if (setMarkers.length === 0) {
|
|
204
241
|
return [
|
|
205
242
|
{
|
|
@@ -231,7 +268,7 @@ function check_one(p: string, todayOrdinal: number): Violation[] {
|
|
|
231
268
|
// bound because a lapsed date can never also exceed the forward window,
|
|
232
269
|
// so the order is a readability choice rather than a precedence one.
|
|
233
270
|
if (reviewOrdinal < todayOrdinal) {
|
|
234
|
-
const inherited = loadLapsedBaseline().has(rel);
|
|
271
|
+
const inherited = loadLapsedBaseline(root).has(rel);
|
|
235
272
|
const age = todayOrdinal - reviewOrdinal;
|
|
236
273
|
return [
|
|
237
274
|
{
|
|
@@ -263,6 +300,34 @@ function check_one(p: string, todayOrdinal: number): Violation[] {
|
|
|
263
300
|
return [];
|
|
264
301
|
}
|
|
265
302
|
|
|
303
|
+
/**
|
|
304
|
+
* The upcoming-lapse finding for one contract, or `null`.
|
|
305
|
+
*
|
|
306
|
+
* Baselined contracts are excluded EXPLICITLY rather than by relying on the
|
|
307
|
+
* fact that a baselined contract is already lapsed and so cannot also carry a
|
|
308
|
+
* future date. That implication holds today and is not a property anything
|
|
309
|
+
* enforces; a guard that is merely vacuous cannot be distinguished from a
|
|
310
|
+
* guard that is absent, and the difference shows up the first time a baseline
|
|
311
|
+
* entry is re-dated forward.
|
|
312
|
+
*/
|
|
313
|
+
export function upcoming_one(
|
|
314
|
+
p: string,
|
|
315
|
+
todayOrdinal: number,
|
|
316
|
+
horizonDays: number = HORIZON_DAYS,
|
|
317
|
+
root: string = ROOT,
|
|
318
|
+
): Upcoming | null {
|
|
319
|
+
const fm = read_frontmatter(p);
|
|
320
|
+
if (fm === null || !/^stability:\s*beta\s*$/m.test(fm)) return null;
|
|
321
|
+
const km = KEEP_RE.exec(fm);
|
|
322
|
+
if (!km) return null;
|
|
323
|
+
const rel = _relPosix(p, root);
|
|
324
|
+
if (loadLapsedBaseline(root).has(rel)) return null;
|
|
325
|
+
const [ry, rm, rd] = _parseISODate(km[1]!);
|
|
326
|
+
const reviewOrdinal = _dateOrdinal(ry, rm, rd);
|
|
327
|
+
const daysOut = reviewOrdinal - todayOrdinal;
|
|
328
|
+
if (daysOut < 0 || daysOut > horizonDays) return null;
|
|
329
|
+
return { file: rel, date: _ordinalToISO(reviewOrdinal), daysOut };
|
|
330
|
+
}
|
|
266
331
|
|
|
267
332
|
/**
|
|
268
333
|
* STABILITY.md's own re-audit condition, as a command instead of as prose.
|
|
@@ -302,10 +367,16 @@ interface ParsedArgs {
|
|
|
302
367
|
trigger: boolean;
|
|
303
368
|
/** Fixture root, for `--self-test`. A fixture is never judged against the repo baseline. */
|
|
304
369
|
root: string | null;
|
|
370
|
+
horizon: number;
|
|
305
371
|
}
|
|
306
372
|
|
|
307
373
|
function parse_args(argv: readonly string[]): ParsedArgs {
|
|
308
|
-
const args: ParsedArgs = {
|
|
374
|
+
const args: ParsedArgs = {
|
|
375
|
+
json: false,
|
|
376
|
+
trigger: false,
|
|
377
|
+
root: null,
|
|
378
|
+
horizon: HORIZON_DAYS,
|
|
379
|
+
};
|
|
309
380
|
for (let i = 0; i < argv.length; i += 1) {
|
|
310
381
|
const arg = argv[i] as string;
|
|
311
382
|
if (arg === '--json') {
|
|
@@ -313,14 +384,27 @@ function parse_args(argv: readonly string[]): ParsedArgs {
|
|
|
313
384
|
} else if (arg === '--root') {
|
|
314
385
|
args.root = argv[i + 1] ?? null;
|
|
315
386
|
i += 1;
|
|
387
|
+
} else if (arg === '--horizon') {
|
|
388
|
+
const raw = argv[i + 1] ?? '';
|
|
389
|
+
const n = Number.parseInt(raw, 10);
|
|
390
|
+
if (!/^\d+$/.test(raw) || Number.isNaN(n)) {
|
|
391
|
+
process.stderr.write(
|
|
392
|
+
`check_beta_review_markers: error: --horizon needs a non-negative integer, got: ${raw || '(nothing)'}\n`,
|
|
393
|
+
);
|
|
394
|
+
process.exit(2);
|
|
395
|
+
}
|
|
396
|
+
args.horizon = n;
|
|
397
|
+
i += 1;
|
|
316
398
|
} else if (arg === '--trigger') {
|
|
317
399
|
args.trigger = true;
|
|
318
400
|
} else if (arg === '-h' || arg === '--help') {
|
|
319
401
|
process.stdout.write(
|
|
320
|
-
'usage: check_beta_review_markers [-h] [--json] [--trigger]\n' +
|
|
402
|
+
'usage: check_beta_review_markers [-h] [--json] [--trigger] [--horizon N]\n' +
|
|
321
403
|
' --trigger print the lapsed percentage and whether STABILITY.md\n' +
|
|
322
404
|
' re-audit condition (>= 25 %) has fired. Reports; never fails.\n' +
|
|
323
|
-
' --root DIR judge a fixture tree instead of the repo (self-test seam).\n'
|
|
405
|
+
' --root DIR judge a fixture tree instead of the repo (self-test seam).\n' +
|
|
406
|
+
` --horizon N days ahead to report upcoming fresh lapses (default ${String(HORIZON_DAYS)}).\n` +
|
|
407
|
+
' Advisory: never changes the exit code.\n',
|
|
324
408
|
);
|
|
325
409
|
process.exit(0);
|
|
326
410
|
} else {
|
|
@@ -400,11 +484,15 @@ function main(): number {
|
|
|
400
484
|
const scanRoot = args.root ?? ROOT;
|
|
401
485
|
const contracts = _globMdSorted(path.join(scanRoot, CONTRACTS_DIR));
|
|
402
486
|
let betaContracts = 0;
|
|
487
|
+
const upcoming: Upcoming[] = [];
|
|
403
488
|
for (const p of contracts) {
|
|
404
489
|
const fm = read_frontmatter(p);
|
|
405
490
|
if (fm !== null && /^stability:\s*beta\s*$/m.test(fm)) betaContracts += 1;
|
|
406
|
-
violations.push(...check_one(p, todayOrdinal));
|
|
491
|
+
violations.push(...check_one(p, todayOrdinal, scanRoot));
|
|
492
|
+
const due = upcoming_one(p, todayOrdinal, args.horizon, scanRoot);
|
|
493
|
+
if (due !== null) upcoming.push(due);
|
|
407
494
|
}
|
|
495
|
+
upcoming.sort((a, b) => a.daysOut - b.daysOut || a.file.localeCompare(b.file));
|
|
408
496
|
// Count every contract read, not the `stability: beta` subset: over a moved
|
|
409
497
|
// `docs/contracts/` "no beta contracts" and "no contracts at all" produce
|
|
410
498
|
// the same clean line, and only the second is a dead gate. Exit 1 is the
|
|
@@ -446,7 +534,9 @@ function main(): number {
|
|
|
446
534
|
return 0;
|
|
447
535
|
}
|
|
448
536
|
if (args.json) {
|
|
449
|
-
process.stdout.write(
|
|
537
|
+
process.stdout.write(
|
|
538
|
+
JSON.stringify({ violations, upcoming, horizonDays: args.horizon }, null, 2) + '\n',
|
|
539
|
+
);
|
|
450
540
|
} else {
|
|
451
541
|
if (violations.length === 0) {
|
|
452
542
|
process.stdout.write('✅ All beta contracts carry a valid review marker.\n');
|
|
@@ -455,7 +545,39 @@ function main(): number {
|
|
|
455
545
|
const icon = v.severity === 'error' ? '❌' : '⚠️ ';
|
|
456
546
|
process.stdout.write(`${icon} ${v.file}: ${v.reason}\n`);
|
|
457
547
|
}
|
|
458
|
-
|
|
548
|
+
// Both counts, and the relationship between them, because a
|
|
549
|
+
// violation count alone cannot answer "how many contracts is this".
|
|
550
|
+
// Today they are equal — check_one returns at most one finding per
|
|
551
|
+
// contract — and printing only one of them hides the fact that it
|
|
552
|
+
// is the size of the decision, not just the size of the output.
|
|
553
|
+
const distinct = new Set(violations.map((v) => v.file)).size;
|
|
554
|
+
const baselineSize = loadLapsedBaseline(scanRoot).size;
|
|
555
|
+
const reportedInherited = violations.filter((v) =>
|
|
556
|
+
loadLapsedBaseline(scanRoot).has(v.file),
|
|
557
|
+
).length;
|
|
558
|
+
process.stdout.write(
|
|
559
|
+
`\n${String(violations.length)} violation(s) across ` +
|
|
560
|
+
`${String(distinct)} distinct contract(s) — one line per finding, so the ` +
|
|
561
|
+
'two differ only where a contract carries more than one.\n',
|
|
562
|
+
);
|
|
563
|
+
process.stdout.write(
|
|
564
|
+
`Frozen baseline: ${String(baselineSize)} entries · ` +
|
|
565
|
+
`${String(reportedInherited)} still reported as lapsed · ` +
|
|
566
|
+
`${String(baselineSize - reportedInherited)} inert ` +
|
|
567
|
+
'(left the lapsed set by promotion, supersession, or re-dating).\n',
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
if (upcoming.length > 0) {
|
|
571
|
+
process.stdout.write(
|
|
572
|
+
`\nUpcoming FRESH lapses within ${String(args.horizon)} day(s) — advisory, ` +
|
|
573
|
+
'exit code unchanged. These are absent from the frozen baseline, so each\n' +
|
|
574
|
+
'becomes an ERROR on its date rather than an inherited warning:\n',
|
|
575
|
+
);
|
|
576
|
+
for (const u of upcoming) {
|
|
577
|
+
process.stdout.write(
|
|
578
|
+
` ${u.file}: keep-beta-until=${u.date} in ${String(u.daysOut)} day(s) [fresh]\n`,
|
|
579
|
+
);
|
|
580
|
+
}
|
|
459
581
|
}
|
|
460
582
|
}
|
|
461
583
|
return violations.some((v) => v.severity === 'error') ? 1 : 0;
|
|
@@ -495,9 +617,11 @@ if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
|
495
617
|
|
|
496
618
|
export {
|
|
497
619
|
type Violation,
|
|
620
|
+
type Upcoming,
|
|
498
621
|
ROOT,
|
|
499
622
|
CONTRACTS_DIR,
|
|
500
623
|
MAX_REVIEW_WINDOW_DAYS,
|
|
624
|
+
HORIZON_DAYS,
|
|
501
625
|
read_frontmatter,
|
|
502
626
|
check_one,
|
|
503
627
|
main,
|
|
@@ -118,7 +118,7 @@ export interface RuleCoverage {
|
|
|
118
118
|
/**
|
|
119
119
|
* Carrier period per hook-capable platform — never a single scalar.
|
|
120
120
|
*
|
|
121
|
-
* A scalar is wrong twice over:
|
|
121
|
+
* A scalar is wrong twice over: this package binds nothing on copilot, so a
|
|
122
122
|
* weakest-platform collapse turns one platform property into a finding on
|
|
123
123
|
* every hook-carried rule; and cline maps `stop` from `TaskCancel`, so the
|
|
124
124
|
* same slot means per-turn on six platforms and per-interruption there. Both
|