@event4u/agent-config 16.0.0 → 16.1.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 +297 -137
- package/CONTRIBUTING.md +9 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +26 -0
- package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
- package/dist/agent-src/commands/challenge-me/closure.md +178 -0
- package/dist/agent-src/commands/challenge-me.md +9 -2
- package/dist/agent-src/commands/council/default.md +25 -5
- package/dist/agent-src/commands/feature/plan.md +28 -1
- package/dist/agent-src/commands/feature/roadmap.md +28 -1
- package/dist/agent-src/commands/implement-ticket.md +27 -0
- package/dist/agent-src/commands/jira-ticket.md +27 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/pr/merge.md +60 -9
- package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
- package/dist/agent-src/commands/roadmap/create.md +74 -27
- package/dist/agent-src/commands/roadmap/materialize.md +27 -0
- package/dist/agent-src/commands/roadmap/process-full.md +123 -18
- package/dist/agent-src/commands/team.md +22 -0
- package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +15 -15
- package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
- package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
- package/dist/agent-src/rules/autonomous-execution.md +15 -8
- package/dist/agent-src/rules/delegation-policy.md +23 -45
- package/dist/agent-src/rules/design-fidelity.md +32 -37
- package/dist/agent-src/rules/evaluator-independence.md +43 -49
- package/dist/agent-src/rules/test-first.md +41 -0
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
- package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
- package/dist/agent-src/skills/database/data/manifest.json +4 -4
- package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
- package/dist/agent-src/skills/design-review/SKILL.md +50 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
- package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
- package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
- package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
- package/dist/agent-src/templates/agent-settings.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +188 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/dist/cli-delegate/check_update_banner.js +40 -2
- package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
- package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
- package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
- package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
- package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
- package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
- package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
- package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
- package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
- package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
- package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
- package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
- package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
- package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +90 -56
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +9 -7
- package/dist/cli-delegate/cmd_fleet.js +4 -2
- package/dist/cli-delegate/cmd_handoff.js +4 -4
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_preflight.js +4 -2
- package/dist/cli-delegate/cmd_prune.js +10 -8
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_get.js +28 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +10 -8
- package/dist/cli-delegate/cmd_uninstall.js +16 -13
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +12 -10
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +85 -38
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +1832 -1007
- package/dist/install/claudeRuleRewrite.js +14 -3
- package/dist/install/conflict.js +60 -10
- package/dist/install/conflictTracker.js +218 -0
- package/dist/install/fsPrimitives.js +54 -0
- package/dist/install/install.mjs +2007 -1481
- package/dist/install/manifestFiles.js +259 -0
- package/dist/install/preflight.js +14 -2
- package/dist/install/preserve.js +157 -0
- package/dist/install/recordedOwnership.js +132 -0
- package/dist/install/txlog.js +11 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +398 -39
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +3 -2
- package/dist/scripts/_lib/fs_atomic.js +167 -0
- package/dist/scripts/_lib/installed_tools.js +460 -0
- package/dist/scripts/_lib/lean_projection_mode.js +3 -2
- package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
- package/dist/server/routes/install.js +8 -0
- package/dist/server/routes/wizard.js +9 -3
- package/dist/server/schemas/settings.js +17 -5
- package/dist/shared/missionExecution.js +97 -0
- package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +103 -0
- package/docs/contracts/settings-classes.md +26 -7
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/docs/guidelines/design-fidelity-routing.md +29 -0
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/src/config/agent-settings.template.yml +139 -27
- package/src/config/auto-rule-budget.json +16 -0
- package/src/config/ci-local-parity.yml +18 -5
- package/src/config/continuity-surface.json +21 -0
- package/src/config/gate-coverage.yml +146 -13
- package/src/config/gate-violation-baselines.json +6 -6
- package/src/config/hook-token-budget.json +2 -0
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/release-gate-locality.yml +11 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/install/claudeRuleRewrite.ts +16 -2
- package/src/install/conflict.ts +65 -9
- package/src/install/conflictTracker.ts +299 -0
- package/src/install/fsPrimitives.ts +54 -0
- package/src/install/manifestFiles.ts +312 -0
- package/src/install/preflight.ts +16 -2
- package/src/install/preserve.ts +205 -0
- package/src/install/recordedOwnership.ts +149 -0
- package/src/install/txlog.ts +11 -2
- package/src/install/types.ts +19 -4
- package/src/scripts/_cli/cmd_conformance.ts +88 -15
- package/src/scripts/_cli/cmd_doctor.ts +4 -3
- package/src/scripts/_cli/cmd_route_explain.ts +9 -2
- package/src/scripts/_cli/cmd_settings_get.ts +44 -0
- package/src/scripts/_cli/doctor_execution.ts +90 -0
- package/src/scripts/_lib/activation_payload.ts +247 -0
- package/src/scripts/_lib/agent_settings.ts +4 -2
- package/src/scripts/_lib/authority_path.ts +102 -0
- package/src/scripts/_lib/cascade_base.ts +94 -0
- package/src/scripts/_lib/changelog_eras.ts +7 -2
- package/src/scripts/_lib/continuation_ladder.ts +149 -2
- package/src/scripts/_lib/council_transport.ts +129 -0
- package/src/scripts/_lib/delivery_ready.ts +112 -0
- package/src/scripts/_lib/forge_protection.ts +267 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/json_python_parity.ts +127 -0
- package/src/scripts/_lib/lean_projection_mode.ts +14 -3
- package/src/scripts/_lib/loop_surfaces.ts +382 -0
- package/src/scripts/_lib/mission_record.ts +190 -0
- package/src/scripts/_lib/obligation_frequency.ts +217 -0
- package/src/scripts/_lib/obligations.ts +402 -0
- package/src/scripts/_lib/orchestration_record.ts +11 -20
- package/src/scripts/_lib/release_highlights.ts +199 -10
- package/src/scripts/_lib/release_holds.ts +493 -0
- package/src/scripts/_lib/release_migration_gate.ts +184 -0
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/typed_op_grant.ts +246 -0
- package/src/scripts/_lib/typed_op_watch.ts +185 -0
- package/src/scripts/ai_council/config.ts +8 -11
- package/src/scripts/ai_council/fallback_config.ts +5 -3
- package/src/scripts/ai_council/ownership_classes.ts +118 -0
- package/src/scripts/ask_block_census.ts +285 -15
- package/src/scripts/check_enforcement_matrix.ts +624 -0
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +954 -0
- package/src/scripts/check_preamble_payload_budget.ts +75 -2
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +567 -0
- package/src/scripts/check_rule_projection_integrity.ts +22 -12
- package/src/scripts/check_test_delta.ts +294 -0
- package/src/scripts/check_test_weakening.ts +299 -0
- package/src/scripts/closure_scan.ts +333 -0
- package/src/scripts/compile_hook_manifest.ts +43 -3
- package/src/scripts/council_record_shape.ts +209 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +88 -9
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/design_pass_hook.ts +128 -3
- package/src/scripts/hooks/evidence_independence.ts +96 -0
- package/src/scripts/hooks/memory_recall_hook.ts +540 -0
- package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
- package/src/scripts/hooks/rule_inject_hook.ts +49 -1
- package/src/scripts/hooks/run_continuation_hook.ts +17 -20
- package/src/scripts/hooks_doctor.ts +85 -0
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +106 -206
- package/src/scripts/janitor.ts +7 -0
- package/src/scripts/lint_decision_classes.ts +464 -0
- package/src/scripts/lint_hook_manifest.ts +11 -0
- package/src/scripts/lint_major_migration_sections.ts +450 -0
- package/src/scripts/lint_plan_risk_register.ts +47 -3
- package/src/scripts/lint_roadmap_blockers.ts +61 -0
- package/src/scripts/lint_roadmap_producers.ts +202 -0
- package/src/scripts/measure_rule_budget.ts +101 -5
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/release.ts +14 -98
- package/src/scripts/release_drill.ts +267 -1
- package/src/scripts/release_publication.ts +51 -0
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_host_injection_effect.ts +3 -11
- package/src/scripts/report_standing_payload_by_host.ts +26 -152
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/command.schema.json +4 -0
- package/src/scripts/scope_growth.ts +219 -0
- package/src/scripts/trigger_eval_grandfather.json +0 -1
- package/src/scripts/ui_conformance_probe.ts +622 -0
- package/src/server/schemas/settings.ts +31 -7
- package/src/shared/missionExecution.ts +126 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* obligation-settle — the reader the delivered-obligation record never had.
|
|
4
|
+
*
|
|
5
|
+
* The injector has always known which rules it delivered into a session. Until
|
|
6
|
+
* this concern, nothing asked whether the turn that received them discharged
|
|
7
|
+
* any. This is that question, asked at turn-end, and — for now — asked out
|
|
8
|
+
* loud and answered with nothing.
|
|
9
|
+
*
|
|
10
|
+
* SHADOW ONLY. IT REFUSES NOTHING, AND THAT IS NOT A PLACEHOLDER.
|
|
11
|
+
* It computes the verdict a future armed detector would reach, records it, and
|
|
12
|
+
* returns allow on every path. Arming is gated on a bar pre-registered in
|
|
13
|
+
* `docs/CLAIMS.md` BEFORE any code able to refuse exists, so that the bar
|
|
14
|
+
* cannot be chosen to fit the numbers the shadow window happened to produce.
|
|
15
|
+
* The estate already has one blocking turn-end concern and the first-ranked
|
|
16
|
+
* risk against this work is that a second one refuses a clean turn and an
|
|
17
|
+
* operator disables the carrier for good.
|
|
18
|
+
*
|
|
19
|
+
* THE TOUCHED SET COMES FROM THE DIFF, NOT FROM TOOL EVENTS.
|
|
20
|
+
* A tool event carries the path the tool was given. A shell heredoc writing
|
|
21
|
+
* three files reports one Bash call and no paths at all, so a detector reading
|
|
22
|
+
* tool events is blind to exactly the turns that wrote the most. `git diff
|
|
23
|
+
* --numstat HEAD` plus `git ls-files --others` sees the tree as it actually
|
|
24
|
+
* is, which is the only description that cannot be evaded by how the write was
|
|
25
|
+
* spelled. The helpers are the end-review concern's own, imported rather than
|
|
26
|
+
* re-derived.
|
|
27
|
+
*
|
|
28
|
+
* IT REFUSES ONLY ON CLASSES THAT COULD HAVE A DISCHARGE.
|
|
29
|
+
* `hook`, `validator` and `test` name carriers that run and leave traces.
|
|
30
|
+
* `observer`, `instruction-only` and `none` do not, so demanding a discharge
|
|
31
|
+
* for one would demand evidence nobody can produce — and 69 of 97 delivered
|
|
32
|
+
* rules on the frozen corpus are class `none`.
|
|
33
|
+
*
|
|
34
|
+
* IT IS SILENT WHILE A SUBAGENT DISPATCH IS OPEN.
|
|
35
|
+
* Three of the turn-end gate's six detectors are gated this way and three are
|
|
36
|
+
* not; the difference is whether the finding could be about work a dispatch is
|
|
37
|
+
* still doing. This one could — a subagent writing UI files is the canonical
|
|
38
|
+
* case — so it takes the gated side.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import path from 'node:path';
|
|
42
|
+
import process from 'node:process';
|
|
43
|
+
|
|
44
|
+
import { may_refuse_on } from '../_lib/obligation_frequency.js';
|
|
45
|
+
import { appendShadow, readDelivered, readDischarged, stamp } from '../_lib/obligations.js';
|
|
46
|
+
import { loadRouter, matchTierRules } from '../_lib/rule_injection.js';
|
|
47
|
+
import { gitNumstatRows, isDocPath, untrackedNonDocFiles } from './end_review_nudge_hook.js';
|
|
48
|
+
import { openRecordStats } from './subagent_ledger_hook.js';
|
|
49
|
+
|
|
50
|
+
const EXIT_ALLOW = 0;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Every non-doc path this turn wrote, tracked or not.
|
|
54
|
+
*
|
|
55
|
+
* Doc paths are excluded on the same grounds the end-review concern excludes
|
|
56
|
+
* them: a markdown edit is not the kind of change whose obligations this
|
|
57
|
+
* detector can speak to, and including them would make almost every turn a
|
|
58
|
+
* candidate.
|
|
59
|
+
*/
|
|
60
|
+
export function touchedPaths(cwd: string): string[] {
|
|
61
|
+
const tracked = gitNumstatRows(cwd)
|
|
62
|
+
.map((r) => r.path)
|
|
63
|
+
.filter((p) => p !== '' && !isDocPath(p));
|
|
64
|
+
const untracked = untrackedNonDocFiles(cwd);
|
|
65
|
+
return [...new Set([...tracked, ...untracked])];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SettleVerdict {
|
|
69
|
+
/** Non-doc paths this turn wrote. */
|
|
70
|
+
readonly touched: string[];
|
|
71
|
+
/** Delivered rules whose path triggers match something touched. */
|
|
72
|
+
readonly candidates: string[];
|
|
73
|
+
/** Candidates with no recorded discharge, and a class that could have one. */
|
|
74
|
+
readonly missing: string[];
|
|
75
|
+
/** True when a subagent dispatch is open — every other field is then advisory only. */
|
|
76
|
+
readonly dispatchOpen: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* What an armed detector would conclude, computed without refusing anything.
|
|
81
|
+
*
|
|
82
|
+
* Exported whole rather than as a boolean so the shadow row, the tests and any
|
|
83
|
+
* later armed path all read the SAME computation. A detector whose shadow
|
|
84
|
+
* measurement and live behaviour come from different code measures nothing.
|
|
85
|
+
*/
|
|
86
|
+
export function computeVerdict(root: string, session: string): SettleVerdict {
|
|
87
|
+
const dispatchOpen = (() => {
|
|
88
|
+
try {
|
|
89
|
+
return openRecordStats(root).open_count > 0;
|
|
90
|
+
} catch {
|
|
91
|
+
// An unreadable ledger is not a reason to change the verdict.
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
|
|
96
|
+
const touched = touchedPaths(root);
|
|
97
|
+
if (touched.length === 0) {
|
|
98
|
+
return { touched, candidates: [], missing: [], dispatchOpen };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const delivered = readDelivered(root, session);
|
|
102
|
+
if (delivered.length === 0) {
|
|
103
|
+
return { touched, candidates: [], missing: [], dispatchOpen };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let pathMatched = new Set<string>();
|
|
107
|
+
try {
|
|
108
|
+
const router = loadRouter(root);
|
|
109
|
+
// Empty prompt, so only PATH triggers can fire. A turn's file writes
|
|
110
|
+
// are not a restatement of the user's request and must not re-fire
|
|
111
|
+
// keyword rules — the same reasoning the injector applies on its own
|
|
112
|
+
// tool slot.
|
|
113
|
+
pathMatched = new Set(matchTierRules(router, '', touched, null).map((m) => m.id));
|
|
114
|
+
} catch {
|
|
115
|
+
return { touched, candidates: [], missing: [], dispatchOpen };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const candidates = delivered
|
|
119
|
+
.filter((r) => pathMatched.has(r.rule) && may_refuse_on(r.cls))
|
|
120
|
+
.map((r) => r.rule);
|
|
121
|
+
|
|
122
|
+
const discharged = new Set(readDischarged(root, session).map((r) => r.rule));
|
|
123
|
+
const missing = candidates.filter((id) => !discharged.has(id));
|
|
124
|
+
return { touched, candidates: [...new Set(candidates)], missing: [...new Set(missing)] , dispatchOpen };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether this reading should force a continuation, were anything armed.
|
|
129
|
+
*
|
|
130
|
+
* An UNCHANGED missing set on a second reading means the previous nudge moved
|
|
131
|
+
* nothing, so nudging again would loop rather than progress. The obligations
|
|
132
|
+
* stay OPEN — that is the whole point, and it is the side of the source set's
|
|
133
|
+
* self-contradiction this ships. A budget running out is a fact about the
|
|
134
|
+
* budget; letting it write a policy verdict is how an unmet obligation becomes
|
|
135
|
+
* a satisfied one with nobody deciding, and an exhausted budget is exactly the
|
|
136
|
+
* moment the obligation is least likely to have been met.
|
|
137
|
+
*
|
|
138
|
+
* There is therefore no `waived` state anywhere in this file, and no code path
|
|
139
|
+
* that writes one.
|
|
140
|
+
*/
|
|
141
|
+
export function shouldContinue(attempt: number): boolean {
|
|
142
|
+
return attempt <= 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function main(): number {
|
|
146
|
+
const root = process.cwd();
|
|
147
|
+
const session = (process.env['CLAUDE_CODE_SESSION_ID'] ?? '').trim();
|
|
148
|
+
if (session === '') return EXIT_ALLOW;
|
|
149
|
+
|
|
150
|
+
let verdict: SettleVerdict;
|
|
151
|
+
try {
|
|
152
|
+
verdict = computeVerdict(root, session);
|
|
153
|
+
} catch {
|
|
154
|
+
// A detector that cannot compute has nothing to say. It never has a
|
|
155
|
+
// refusal to withhold, so there is no safe-vs-unsafe choice here.
|
|
156
|
+
return EXIT_ALLOW;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Silent while a dispatch is open: the files may be a subagent's, still
|
|
160
|
+
// being written, and a shadow row claiming otherwise would poison the very
|
|
161
|
+
// corpus the arming decision is measured against.
|
|
162
|
+
if (verdict.dispatchOpen) return EXIT_ALLOW;
|
|
163
|
+
if (verdict.missing.length === 0) return EXIT_ALLOW;
|
|
164
|
+
|
|
165
|
+
const attempt = appendShadow(root, session, verdict.missing, stamp());
|
|
166
|
+
if (attempt === 0) return EXIT_ALLOW;
|
|
167
|
+
|
|
168
|
+
// ONE line for the whole missing set, never one per obligation — the same
|
|
169
|
+
// aggregation the row itself uses, for the same reason.
|
|
170
|
+
process.stderr.write(
|
|
171
|
+
`obligation-settle (shadow, refusing nothing): ${verdict.missing.length} `
|
|
172
|
+
+ `obligation(s) with no recorded discharge after writing `
|
|
173
|
+
+ `${verdict.touched.length} file(s): ${verdict.missing.join(', ')}`
|
|
174
|
+
+ `${shouldContinue(attempt) ? '' : ' — unchanged since the last reading; still open'}\n`,
|
|
175
|
+
);
|
|
176
|
+
return EXIT_ALLOW;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Bundle-safety: never auto-run when inlined into an esbuild bundle.
|
|
180
|
+
if (process.argv[1] !== undefined && path.basename(process.argv[1]).startsWith('obligation_settle')) {
|
|
181
|
+
process.exit(main());
|
|
182
|
+
}
|
|
@@ -11,7 +11,14 @@
|
|
|
11
11
|
* the 36.2 %-against-48 % run at `docs/CLAIMS.md:188-189`. Whatever that
|
|
12
12
|
* instrument's flaws — and ADR-202 closed it — it points one way.
|
|
13
13
|
*
|
|
14
|
-
* SHIPPED ON FOR CLAUDE CODE SINCE ADR-
|
|
14
|
+
* SHIPPED ON FOR CLAUDE CODE SINCE ADR-267 — the delivery-default record, and
|
|
15
|
+
* the citation is corrected here (2026-09-13) from ADR-265, which decides
|
|
16
|
+
* something else entirely: whether the iron-law reserve is activated, answered
|
|
17
|
+
* no, with the verifier kept inside the change. Nothing in it ships a
|
|
18
|
+
* projection mode. ADR-267 `delivery-default-for-claude-code` is the record
|
|
19
|
+
* that turns this concern on for this host, and it is the one whose
|
|
20
|
+
* `review_trigger` fires if a rule stops arriving. Previously corrected
|
|
21
|
+
* 2026-09-08 (R2 finding
|
|
15
22
|
* 4), because this paragraph read "DEFAULT OFF, AND OFF MEANS ZERO BYTES …
|
|
16
23
|
* under every shipped default it emits nothing … there is no measured emission
|
|
17
24
|
* to register yet" and all four clauses became false in the same change that
|
|
@@ -94,6 +101,8 @@ import {
|
|
|
94
101
|
normalizeLeanProjectionMode,
|
|
95
102
|
resolveLeanProjectionHosts,
|
|
96
103
|
} from '../_lib/lean_projection_mode.js';
|
|
104
|
+
import { enforcement_class_from_frontmatter } from '../_lib/obligation_frequency.js';
|
|
105
|
+
import { appendDelivered, stamp, type DeliveredRow } from '../_lib/obligations.js';
|
|
97
106
|
import {
|
|
98
107
|
loadRuleBody,
|
|
99
108
|
loadRouter,
|
|
@@ -237,6 +246,44 @@ export interface Injection {
|
|
|
237
246
|
body: string;
|
|
238
247
|
}
|
|
239
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Record what this fire delivered, so something can later ask whether the turn
|
|
251
|
+
* discharged it.
|
|
252
|
+
*
|
|
253
|
+
* The write happens HERE and not in a later slot because this is the only
|
|
254
|
+
* place that knows the answer. By the time a turn ends, the selection that
|
|
255
|
+
* produced these ids is gone — the router would have to be re-run against a
|
|
256
|
+
* prompt nobody kept, which is a different computation that could disagree.
|
|
257
|
+
*
|
|
258
|
+
* EMITTER RECORD, NEVER A COMPLIANCE ONE. A row says a body was emitted into
|
|
259
|
+
* this slot. It does not say the model read it, and the ledger's own header
|
|
260
|
+
* carries the council lock that forbids reading it that way.
|
|
261
|
+
*
|
|
262
|
+
* Best-effort by construction: every failure path inside `appendDelivered`
|
|
263
|
+
* returns rather than throws, and this wrapper adds one more guard so a
|
|
264
|
+
* ledger problem can never turn a successful injection into a failed hook.
|
|
265
|
+
* A missed reading is cheaper than a refused turn, and this concern has no
|
|
266
|
+
* refusal to offer anyway.
|
|
267
|
+
*/
|
|
268
|
+
export function recordDelivered(root: string, session: string, ruleIds: string[]): number {
|
|
269
|
+
try {
|
|
270
|
+
const now = stamp();
|
|
271
|
+
const rows: DeliveredRow[] = ruleIds.map((id) => {
|
|
272
|
+
const body = loadRuleBody(root, id);
|
|
273
|
+
return {
|
|
274
|
+
rule: id,
|
|
275
|
+
// A rule with no projected body declares nothing this can read,
|
|
276
|
+
// which is `none` — the same answer as a body declaring none.
|
|
277
|
+
cls: body === null ? 'none' : enforcement_class_from_frontmatter(body),
|
|
278
|
+
at: now,
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
return appendDelivered(root, session, rows);
|
|
282
|
+
} catch {
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
240
287
|
/**
|
|
241
288
|
* Build the injection for one event, or `null` for silence.
|
|
242
289
|
*
|
|
@@ -371,6 +418,7 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
371
418
|
|
|
372
419
|
for (const id of injection.rules) seen.add(id);
|
|
373
420
|
writeSeen(root, session, seen);
|
|
421
|
+
recordDelivered(root, session, injection.rules);
|
|
374
422
|
|
|
375
423
|
process.stdout.write(
|
|
376
424
|
`${JSON.stringify({
|
|
@@ -185,6 +185,7 @@ import { premiseMoved, readContextObservation } from '../_lib/context_observatio
|
|
|
185
185
|
import { RUN_TERMINAL_VOCABULARY_VERSION } from '../_lib/outcome_vocabularies.js';
|
|
186
186
|
import {
|
|
187
187
|
HALT_ACTIONS,
|
|
188
|
+
buildLedgerEvent, parseDeliveryState, type DeliveryState,
|
|
188
189
|
ladder,
|
|
189
190
|
MAX_ITERATIONS,
|
|
190
191
|
parseHaltStamp,
|
|
@@ -322,6 +323,8 @@ export interface RunState {
|
|
|
322
323
|
* observed one, which is never a disagreement (`premiseMoved`).
|
|
323
324
|
*/
|
|
324
325
|
context_fingerprint?: string;
|
|
326
|
+
/** DELIVERY position (8.1), written by the run; absent is no hold. */
|
|
327
|
+
delivery?: DeliveryState;
|
|
325
328
|
}
|
|
326
329
|
|
|
327
330
|
/**
|
|
@@ -651,6 +654,10 @@ function readState(file: string): RunState | null {
|
|
|
651
654
|
if (o['inert_reported'] === true) {
|
|
652
655
|
rec.inert_reported = true;
|
|
653
656
|
}
|
|
657
|
+
// Round-tripped for the same reason `history_source` is: without this the
|
|
658
|
+
// 8.1 hold is dead code that reads as shipped.
|
|
659
|
+
const dlv = parseDeliveryState(o['delivery']);
|
|
660
|
+
if (dlv !== undefined) rec.delivery = dlv;
|
|
654
661
|
// Round-tripped for the same reason `history_source` is: dropped, the
|
|
655
662
|
// premise would be re-recorded every fire from the newest observation, so
|
|
656
663
|
// the two sides could never differ and the rung would be dead code.
|
|
@@ -1384,14 +1391,14 @@ export function main(): number {
|
|
|
1384
1391
|
const observed = readContextObservation(workspaceRoot)?.fingerprint ?? null;
|
|
1385
1392
|
const premiseInvalidated = premiseMoved(state.context_fingerprint, observed);
|
|
1386
1393
|
|
|
1394
|
+
const delivery = parseDeliveryState(state.delivery) ?? null;
|
|
1387
1395
|
const action = ladder(
|
|
1388
|
-
state,
|
|
1389
|
-
scan.open,
|
|
1390
|
-
Date.now(),
|
|
1396
|
+
state, scan.open, Date.now(),
|
|
1391
1397
|
scan.blocked,
|
|
1392
1398
|
undefined,
|
|
1393
1399
|
unavailable,
|
|
1394
1400
|
premiseInvalidated,
|
|
1401
|
+
delivery,
|
|
1395
1402
|
);
|
|
1396
1403
|
|
|
1397
1404
|
if (action !== 'engage') {
|
|
@@ -1408,23 +1415,13 @@ export function main(): number {
|
|
|
1408
1415
|
const alreadyBlocked =
|
|
1409
1416
|
action === 'blocked' && eventAlreadyLogged(workspaceRoot, runId, slug, 'blocked');
|
|
1410
1417
|
if (!alreadyStamped && !alreadyBlocked) {
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
blocked: scan.blocked,
|
|
1419
|
-
// A ledger line is a persisted shape carrying a `RunTerminalState`,
|
|
1420
|
-
// so it names the vocabulary version it was written against: a
|
|
1421
|
-
// reader meeting an unknown value can then tell a newer writer
|
|
1422
|
-
// from a corrupt row.
|
|
1423
|
-
terminal_state: terminalStateFor(action),
|
|
1424
|
-
terminal_vocabulary_version: RUN_TERMINAL_VOCABULARY_VERSION,
|
|
1425
|
-
at: new Date().toISOString(),
|
|
1426
|
-
...roots,
|
|
1427
|
-
});
|
|
1418
|
+
const line = buildLedgerEvent(
|
|
1419
|
+
{ action, runId, slug, turn: turnOrdinal, iterations: state.iterations,
|
|
1420
|
+
open: scan.open, blocked: scan.blocked, delivery },
|
|
1421
|
+
RUN_TERMINAL_VOCABULARY_VERSION,
|
|
1422
|
+
new Date(),
|
|
1423
|
+
);
|
|
1424
|
+
appendEvent(workspaceRoot, { ...line, ...roots } as JsonObject);
|
|
1428
1425
|
}
|
|
1429
1426
|
if (action === 'complete') {
|
|
1430
1427
|
// Only a COMPLETION clears the state, so a later run on the same
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
type InstallBoundary,
|
|
46
46
|
type RefusalStats,
|
|
47
47
|
} from "./_lib/turn_end_refusals.js";
|
|
48
|
+
import { ledgerWritable, readDelivered } from "./_lib/obligations.js";
|
|
48
49
|
|
|
49
50
|
// src/scripts/hooks_doctor.ts → parents[2] (../../..) is the repo root,
|
|
50
51
|
// mirroring the Python `Path(__file__).resolve().parents[2]`.
|
|
@@ -191,10 +192,35 @@ export interface DoctorPayload {
|
|
|
191
192
|
* concern's health is already read, so this is where the number belongs.
|
|
192
193
|
*/
|
|
193
194
|
turn_end_refusals: RefusalStats;
|
|
195
|
+
/**
|
|
196
|
+
* The obligation ledger's two readiness figures.
|
|
197
|
+
*
|
|
198
|
+
* Reported here and nowhere else, because the doctor is where a concern's
|
|
199
|
+
* health is already read and the ledger's failure mode is silence: it never
|
|
200
|
+
* throws, never refuses and never fails a turn, so an unwritable ledger
|
|
201
|
+
* looks exactly like a session that triggered no rules. These two lines are
|
|
202
|
+
* the difference between those, and they are the ONLY consumer the ledger
|
|
203
|
+
* has — reporting a count is not acting on the rows.
|
|
204
|
+
*/
|
|
205
|
+
obligation_ledger: ObligationLedgerStats;
|
|
194
206
|
/** The machine's install stamp, for reading the version split below it. */
|
|
195
207
|
install_boundary: InstallBoundary;
|
|
196
208
|
}
|
|
197
209
|
|
|
210
|
+
/** What the doctor can say about the ledger without interpreting a single row. */
|
|
211
|
+
export interface ObligationLedgerStats {
|
|
212
|
+
/** Probed by writing and removing a sentinel, not by reading mode bits. */
|
|
213
|
+
writable: boolean;
|
|
214
|
+
/**
|
|
215
|
+
* Rows for THIS session, or `null` when no session id was resolvable.
|
|
216
|
+
*
|
|
217
|
+
* `null` and `0` are different answers and are rendered differently: `0`
|
|
218
|
+
* means this session was delivered no rule, `null` means the doctor could
|
|
219
|
+
* not tell which session it is in and so has nothing to count.
|
|
220
|
+
*/
|
|
221
|
+
rows_this_session: number | null;
|
|
222
|
+
}
|
|
223
|
+
|
|
198
224
|
/** Build the doctor payload — JSON-serialisable. */
|
|
199
225
|
export function collect(
|
|
200
226
|
project_root: string,
|
|
@@ -288,10 +314,67 @@ export function collect(
|
|
|
288
314
|
trampolines,
|
|
289
315
|
dispatch_issues: issues,
|
|
290
316
|
turn_end_refusals,
|
|
317
|
+
obligation_ledger: collectLedgerStats(project_root),
|
|
291
318
|
install_boundary: readInstallBoundary(),
|
|
292
319
|
};
|
|
293
320
|
}
|
|
294
321
|
|
|
322
|
+
/**
|
|
323
|
+
* The ledger's two figures, never throwing.
|
|
324
|
+
*
|
|
325
|
+
* The session id comes from the host's own environment variable, which is the
|
|
326
|
+
* same source the concerns key their state on. Absent means the doctor is
|
|
327
|
+
* being run outside a session — by a human at a shell, most often — and the
|
|
328
|
+
* honest answer is `null` rather than 0, because there is no session whose
|
|
329
|
+
* rows could be counted.
|
|
330
|
+
*/
|
|
331
|
+
export function collectLedgerStats(project_root: string): ObligationLedgerStats {
|
|
332
|
+
let writable = false;
|
|
333
|
+
try {
|
|
334
|
+
writable = ledgerWritable(project_root);
|
|
335
|
+
} catch {
|
|
336
|
+
writable = false;
|
|
337
|
+
}
|
|
338
|
+
const session = (process.env["CLAUDE_CODE_SESSION_ID"] ?? "").trim();
|
|
339
|
+
if (session === "") return { writable, rows_this_session: null };
|
|
340
|
+
try {
|
|
341
|
+
return { writable, rows_this_session: readDelivered(project_root, session).length };
|
|
342
|
+
} catch {
|
|
343
|
+
return { writable, rows_this_session: null };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* The ledger section.
|
|
349
|
+
*
|
|
350
|
+
* Deliberately states what a row is NOT. The council lock this ledger sits
|
|
351
|
+
* under says installation proves availability and not exposure, so a reader
|
|
352
|
+
* who sees a healthy row count and concludes the rules were followed has made
|
|
353
|
+
* the one claim these records may never support. The line is cheap; the
|
|
354
|
+
* misreading it prevents is the whole reason the ledger needed a non-goal.
|
|
355
|
+
*/
|
|
356
|
+
export function _render_ledger(payload: DoctorPayload): string[] {
|
|
357
|
+
const s = payload.obligation_ledger;
|
|
358
|
+
const lines: string[] = [];
|
|
359
|
+
lines.push("Obligation ledger");
|
|
360
|
+
lines.push("-".repeat(60));
|
|
361
|
+
lines.push(
|
|
362
|
+
`${s.writable ? "✅ " : "❌ "}writable ${
|
|
363
|
+
s.writable ? "agents/runtime/state/obligations/" : "cannot write agents/runtime/state/obligations/"
|
|
364
|
+
}`,
|
|
365
|
+
);
|
|
366
|
+
if (s.rows_this_session === null) {
|
|
367
|
+
lines.push("· rows this session: no session id resolved — nothing to count");
|
|
368
|
+
} else {
|
|
369
|
+
lines.push(`· rows this session: ${s.rows_this_session}`);
|
|
370
|
+
}
|
|
371
|
+
lines.push(
|
|
372
|
+
" Rows record that an emitter ran. They are not evidence that a rule",
|
|
373
|
+
);
|
|
374
|
+
lines.push(" reached the model, and may not be read as compliance.");
|
|
375
|
+
return lines;
|
|
376
|
+
}
|
|
377
|
+
|
|
295
378
|
function _strVal(v: JsonValue | undefined): string {
|
|
296
379
|
return v === undefined || v === null ? "undefined" : String(v);
|
|
297
380
|
}
|
|
@@ -415,6 +498,8 @@ export function _render_table(payload: DoctorPayload): string {
|
|
|
415
498
|
}
|
|
416
499
|
lines.push("");
|
|
417
500
|
lines.push(..._render_refusals(payload));
|
|
501
|
+
lines.push("");
|
|
502
|
+
lines.push(..._render_ledger(payload));
|
|
418
503
|
// Dispatch-issues detail — last 20 grouped by concern.
|
|
419
504
|
if (payload.dispatch_issues.length > 0) {
|
|
420
505
|
lines.push("");
|
|
@@ -73,11 +73,22 @@ export const PLATFORM_BRIDGES: Record<string, [string, string]> = {
|
|
|
73
73
|
copilot: ["", "rule-only fallback — this package binds no hook here"],
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
+
/** The shape of a host's user-facing question primitive. */
|
|
77
|
+
export type AskShape = "native" | "text";
|
|
78
|
+
|
|
76
79
|
export interface PlatformRow {
|
|
77
80
|
platform: string;
|
|
78
81
|
status: string;
|
|
79
82
|
bridge_path: string | null;
|
|
80
83
|
fallback_only: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* DECLARED in the manifest, never probed. A host with no row reads `text`:
|
|
86
|
+
* the conservative answer, and the one that is true of every host that has
|
|
87
|
+
* not said otherwise. Absent must not read as "unknown" here — an unknown
|
|
88
|
+
* that the contract then treats as native is exactly the silent degrade the
|
|
89
|
+
* row exists to prevent.
|
|
90
|
+
*/
|
|
91
|
+
ask: AskShape;
|
|
81
92
|
bindings: Record<string, string[]>;
|
|
82
93
|
hint: string | null;
|
|
83
94
|
}
|
|
@@ -122,6 +133,7 @@ export function collect(project_root: string, manifest: JsonObject): StatusMatri
|
|
|
122
133
|
const blockRaw = platforms[platform];
|
|
123
134
|
const block = _isObject(blockRaw) ? blockRaw : {};
|
|
124
135
|
const fallback_only = Boolean(block["fallback_only"]);
|
|
136
|
+
const ask: AskShape = block["ask"] === "native" ? "native" : "text";
|
|
125
137
|
const bindings: Record<string, string[]> = {};
|
|
126
138
|
if (!fallback_only) {
|
|
127
139
|
for (const [ev, c] of Object.entries(block)) {
|
|
@@ -138,6 +150,7 @@ export function collect(project_root: string, manifest: JsonObject): StatusMatri
|
|
|
138
150
|
status,
|
|
139
151
|
bridge_path: rel || null,
|
|
140
152
|
fallback_only,
|
|
153
|
+
ask,
|
|
141
154
|
bindings,
|
|
142
155
|
hint: ["missing", "empty", "degraded", "n/a"].includes(status)
|
|
143
156
|
? hint
|
|
@@ -166,6 +179,12 @@ export function _render_table(matrix: StatusMatrix): string {
|
|
|
166
179
|
head += ` (${row.bridge_path})`;
|
|
167
180
|
}
|
|
168
181
|
lines.push(head);
|
|
182
|
+
lines.push(
|
|
183
|
+
` ask → ${row.ask}` +
|
|
184
|
+
(row.ask === "native"
|
|
185
|
+
? " (structured-ask tool; the recommendation is its default option)"
|
|
186
|
+
: " (numbered text block — the fallback, and it says so)"),
|
|
187
|
+
);
|
|
169
188
|
if (row.fallback_only) {
|
|
170
189
|
lines.push(
|
|
171
190
|
" degraded: rule-only fallback " +
|