@event4u/agent-config 16.1.0 → 16.2.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 +237 -248
- package/dist/agent-src/commands/roadmap/process-full.md +1 -1
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- package/dist/agent-src/skills/design-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +16 -1
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
- package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-7XGBX6ZH.js} +471 -387
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-FP36SWJD.js} +432 -144
- package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-IGD6ERX6.js} +31 -0
- package/dist/cli-delegate/{chunk-FXYUYU4N.js → chunk-JNMVLWLR.js} +42 -1
- package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
- package/dist/cli-delegate/cmd_conformance.js +902 -782
- 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 +4 -4
- 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 +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- 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 +10 -10
- 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 +6666 -2581
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/install.mjs +848 -733
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2747 -1510
- package/dist/server/schemas/settings.js +23 -0
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/contracts/settings-classes.md +6 -4
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +29 -0
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +2 -2
- package/src/config/gate-coverage.yml +234 -3
- package/src/config/gate-violation-baselines.json +7 -3
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +2 -2
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/scripts/_cli/cmd_doctor.ts +5 -19
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/turn_end_refusals.ts +525 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/before_complete_hook.ts +340 -23
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +245 -3
- package/src/scripts/check_finding_dispositions.ts +22 -3
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +47 -5
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -1
- package/src/scripts/compile_hook_manifest.ts +60 -16
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +42 -5
- package/src/scripts/hooks/block_config_weakening.ts +229 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +25 -19
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +1 -2
- package/src/scripts/hooks/exit_codes.ts +128 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +128 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +8 -5
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +49 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/install.ts +3 -4
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +55 -0
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_standing_payload_by_host.ts +1 -1
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +359 -10
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +0 -2
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +31 -0
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
- /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
|
@@ -60,8 +60,7 @@ import { derive_session_tag } from '../chat_history.js';
|
|
|
60
60
|
import { unwrap, type JsonObject, type JsonValue } from './envelope.js';
|
|
61
61
|
import { readHookStdin } from './hook_stdin.js';
|
|
62
62
|
import { HANDBACK, finalParagraph, readTranscriptTail } from './turn_end_gate_hook.js';
|
|
63
|
-
|
|
64
|
-
const EXIT_OK = 0;
|
|
63
|
+
import { EXIT_ALLOW as EXIT_OK } from './exit_codes.js';
|
|
65
64
|
|
|
66
65
|
/** Same cap the turn-end gate reads its tail under. */
|
|
67
66
|
const TRANSCRIPT_READ_MAX_BYTES = 2 * 1024 * 1024;
|
|
@@ -102,8 +102,7 @@ import {
|
|
|
102
102
|
} from '../_lib/runtime_journal.js';
|
|
103
103
|
import { readHookStdin } from './hook_stdin.js';
|
|
104
104
|
import { is_replay_mode } from './state_io.js';
|
|
105
|
-
|
|
106
|
-
const EXIT_ALLOW = 0;
|
|
105
|
+
import { EXIT_ALLOW } from './exit_codes.js';
|
|
107
106
|
|
|
108
107
|
/** The `.agent-settings.yml` section under `hooks:` that arms this concern. */
|
|
109
108
|
export const SETTINGS_SECTION = 'runtime_journal';
|
|
@@ -68,13 +68,12 @@ import { COMMAND_TOOLS } from '../before_complete_hook.js';
|
|
|
68
68
|
import { unwrap, type JsonObject, type JsonValue } from './envelope.js';
|
|
69
69
|
import { readHookStdin } from './hook_stdin.js';
|
|
70
70
|
import { atomic_write_json } from './state_io.js';
|
|
71
|
+
import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
|
|
71
72
|
|
|
72
|
-
const EXIT_ALLOW = 0;
|
|
73
73
|
/**
|
|
74
74
|
* Severity comes from the exit code, not from the `decision` field. See the
|
|
75
75
|
* header: this never blocks, and never returns 1 on any path.
|
|
76
76
|
*/
|
|
77
|
-
const EXIT_WARN = 2;
|
|
78
77
|
|
|
79
78
|
/** Hard ceiling on `reason` + `additional_context`, matching the registered row. */
|
|
80
79
|
export const CAP_BYTES = 768;
|
|
@@ -41,13 +41,19 @@
|
|
|
41
41
|
import path from 'node:path';
|
|
42
42
|
import process from 'node:process';
|
|
43
43
|
|
|
44
|
+
import { readHookStdin } from './hook_stdin.js';
|
|
45
|
+
// The WRITER's own root resolver, imported rather than re-derived. A second
|
|
46
|
+
// spelling of "which tree is this" is how a reader comes to address a ledger
|
|
47
|
+
// the writer never wrote — which is the defect this whole file is repairing,
|
|
48
|
+
// one field over.
|
|
49
|
+
import { workspaceRoot as writerWorkspaceRoot } from './rule_inject_hook.js';
|
|
50
|
+
|
|
44
51
|
import { may_refuse_on } from '../_lib/obligation_frequency.js';
|
|
45
52
|
import { appendShadow, readDelivered, readDischarged, stamp } from '../_lib/obligations.js';
|
|
46
53
|
import { loadRouter, matchTierRules } from '../_lib/rule_injection.js';
|
|
47
54
|
import { gitNumstatRows, isDocPath, untrackedNonDocFiles } from './end_review_nudge_hook.js';
|
|
48
55
|
import { openRecordStats } from './subagent_ledger_hook.js';
|
|
49
|
-
|
|
50
|
-
const EXIT_ALLOW = 0;
|
|
56
|
+
import { EXIT_ALLOW } from './exit_codes.js';
|
|
51
57
|
|
|
52
58
|
/**
|
|
53
59
|
* Every non-doc path this turn wrote, tracked or not.
|
|
@@ -142,9 +148,144 @@ export function shouldContinue(attempt: number): boolean {
|
|
|
142
148
|
return attempt <= 1;
|
|
143
149
|
}
|
|
144
150
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
151
|
+
/**
|
|
152
|
+
* The session and root this reading is about, resolved the way the WRITER does.
|
|
153
|
+
*
|
|
154
|
+
* THE DEFECT THIS EXISTS TO CLOSE. This hook used to read
|
|
155
|
+
* `CLAUDE_CODE_SESSION_ID` out of the process environment and return allow when
|
|
156
|
+
* it was empty. The dispatcher sets no such variable — it hands each concern
|
|
157
|
+
* the envelope on stdin and `AGENT_CONFIG_PACKAGE_ROOT` in the environment,
|
|
158
|
+
* nothing else — so on every dispatched stop event the reader looked for a
|
|
159
|
+
* ledger under a key the writer never used. Measured 2026-09-29 on this
|
|
160
|
+
* machine's own ledgers: 187 delivered rows, 0 shadow rows, across 8 sessions.
|
|
161
|
+
*
|
|
162
|
+
* WHY THE ORDER IS ENVELOPE-THEN-ENVIRONMENT AND NOT THE REVERSE. The envelope
|
|
163
|
+
* id is the key `rule_inject_hook.ts` wrote the delivered rows under, so it is
|
|
164
|
+
* not merely the better source — it is the ONLY one that can find them. The
|
|
165
|
+
* environment fallback survives for a raw invocation that carries no envelope
|
|
166
|
+
* at all (a maintainer piping nothing, a host shim that skips the dispatcher),
|
|
167
|
+
* which is a case where guessing wrong costs a missing reading rather than a
|
|
168
|
+
* wrong one.
|
|
169
|
+
*
|
|
170
|
+
* THE ROOT IS PART OF THE SAME JOIN, not a second change riding along. A ledger
|
|
171
|
+
* is addressed by root AND session; reading the session from the envelope while
|
|
172
|
+
* taking the root from `process.cwd()` would leave the pair half-joined, and the
|
|
173
|
+
* two disagree exactly on the hosts whose shim does not chdir. `cwd` stays the
|
|
174
|
+
* fallback because that is what the dispatcher sets it to.
|
|
175
|
+
*
|
|
176
|
+
* Both spellings of each key are accepted because the injector accepts both.
|
|
177
|
+
*/
|
|
178
|
+
export function resolveSettleContext(
|
|
179
|
+
envelope: Record<string, unknown>,
|
|
180
|
+
payload: Record<string, unknown>,
|
|
181
|
+
env: Record<string, string | undefined>,
|
|
182
|
+
): { root: string; session: string } {
|
|
183
|
+
const str = (...values: unknown[]): string => {
|
|
184
|
+
for (const v of values) {
|
|
185
|
+
if (typeof v === 'string' && v.trim() !== '') return v.trim();
|
|
186
|
+
}
|
|
187
|
+
return '';
|
|
188
|
+
};
|
|
189
|
+
return {
|
|
190
|
+
// `workspaceRoot` is the INJECTOR's function, not a copy of it. It reads
|
|
191
|
+
// `workspace` / `cwd` / `project_dir`; this file used to read
|
|
192
|
+
// `workspace_root` / `workspace` / `cwd`, so the two key sets overlapped
|
|
193
|
+
// without matching and the pair was joined on the session only. It
|
|
194
|
+
// happened to agree on both real envelope shapes — the dispatcher sets
|
|
195
|
+
// `workspace_root` and chdirs concerns to it, a native wiring passes
|
|
196
|
+
// `cwd` — which made the asymmetry a property of today's envelopes
|
|
197
|
+
// rather than of the code. `workspace_root` is appended ahead of the
|
|
198
|
+
// shared resolver because the dispatcher is the only producer that sets
|
|
199
|
+
// it and nothing else reads it.
|
|
200
|
+
root:
|
|
201
|
+
str(envelope['workspace_root'])
|
|
202
|
+
|| writerWorkspaceRoot(envelope as Parameters<typeof writerWorkspaceRoot>[0]),
|
|
203
|
+
session: str(
|
|
204
|
+
envelope['session_id'],
|
|
205
|
+
envelope['sessionId'],
|
|
206
|
+
payload['session_id'],
|
|
207
|
+
payload['sessionId'],
|
|
208
|
+
// The one pre-existing source, kept as the last resort and NOT
|
|
209
|
+
// joined by siblings: adding a name the writer never keys on
|
|
210
|
+
// would address a ledger that cannot exist.
|
|
211
|
+
env['CLAUDE_CODE_SESSION_ID'],
|
|
212
|
+
),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The session id the WRITER would have used, for the ledger nobody reads.
|
|
218
|
+
*
|
|
219
|
+
* `rule_inject_hook.ts` defaults an unnamed session to the literal `unknown`
|
|
220
|
+
* and writes delivered rows there; this reader used to return `''` and allow, so
|
|
221
|
+
* that population was invisible to every reading of the bar. Reported rather
|
|
222
|
+
* than silently joined: a shared `unknown` bucket aggregates unrelated sessions,
|
|
223
|
+
* so reading it as one session's turn would be worse than not reading it. The
|
|
224
|
+
* constant is exported so a future census can count what is sitting there.
|
|
225
|
+
*/
|
|
226
|
+
export const WRITER_UNNAMED_SESSION = 'unknown';
|
|
227
|
+
|
|
228
|
+
/** The envelope on stdin, or an empty object when there is none to read. */
|
|
229
|
+
function readEnvelope(): { envelope: Record<string, unknown>; payload: Record<string, unknown> } {
|
|
230
|
+
let envelope: Record<string, unknown> = {};
|
|
231
|
+
try {
|
|
232
|
+
const raw = readHookStdin();
|
|
233
|
+
const parsed: unknown = raw.trim() === '' ? {} : JSON.parse(raw);
|
|
234
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
235
|
+
envelope = parsed as Record<string, unknown>;
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
// A malformed envelope is not a reason to refuse, and this concern
|
|
239
|
+
// refuses nothing anyway. It is a reason to read nothing from it.
|
|
240
|
+
}
|
|
241
|
+
const nested = envelope['payload'];
|
|
242
|
+
const payload =
|
|
243
|
+
typeof nested === 'object' && nested !== null && !Array.isArray(nested)
|
|
244
|
+
? (nested as Record<string, unknown>)
|
|
245
|
+
: envelope;
|
|
246
|
+
return { envelope, payload };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The resolver as it stood BEFORE the join fix, kept reachable for one test.
|
|
251
|
+
*
|
|
252
|
+
* A reproduction that cannot run the old code is not a reproduction — it is an
|
|
253
|
+
* assertion about the new function's behavior on inputs nobody sends, and
|
|
254
|
+
* `tests/hooks/obligation_settle.test.ts` carried exactly that until an
|
|
255
|
+
* independent review named it. This function is dead on every production path
|
|
256
|
+
* (nothing but the fixture calls it) and exists so the defect stays executable:
|
|
257
|
+
* environment-only resolution, allow when empty, root from the cwd.
|
|
258
|
+
*/
|
|
259
|
+
export function resolveSettleContextPreFix(
|
|
260
|
+
_envelope: Record<string, unknown>,
|
|
261
|
+
_payload: Record<string, unknown>,
|
|
262
|
+
env: Record<string, string | undefined>,
|
|
263
|
+
): { root: string; session: string } {
|
|
264
|
+
// Same arity as the live resolver ON PURPOSE. A narrower signature would let
|
|
265
|
+
// the fixture pass the envelope in as `env`, where the lookup misses for the
|
|
266
|
+
// wrong reason and the test goes green without exercising the defect.
|
|
267
|
+
return { root: process.cwd(), session: (env['CLAUDE_CODE_SESSION_ID'] ?? '').trim() };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Which context resolver this reading uses — the seam the pre-fix fixture needs.
|
|
272
|
+
*
|
|
273
|
+
* It is NOT a parameter of `main()`, and that is a contract rather than a
|
|
274
|
+
* preference: `concern_main_signature` requires every concern's `main()` to take
|
|
275
|
+
* argv first or nothing, because the dispatcher calls `main(argv)` — so a
|
|
276
|
+
* resolver in first position would receive an argv array at runtime and resolve
|
|
277
|
+
* a session from it. Caught by that test, which is the reason this indirection
|
|
278
|
+
* exists instead of the obvious default parameter.
|
|
279
|
+
*/
|
|
280
|
+
export type SettleResolver = (
|
|
281
|
+
envelope: Record<string, unknown>,
|
|
282
|
+
payload: Record<string, unknown>,
|
|
283
|
+
env: Record<string, string | undefined>,
|
|
284
|
+
) => { root: string; session: string };
|
|
285
|
+
|
|
286
|
+
export function runSettle(resolve: SettleResolver = resolveSettleContext): number {
|
|
287
|
+
const { envelope, payload } = readEnvelope();
|
|
288
|
+
const { root, session } = resolve(envelope, payload, process.env);
|
|
148
289
|
if (session === '') return EXIT_ALLOW;
|
|
149
290
|
|
|
150
291
|
let verdict: SettleVerdict;
|
|
@@ -176,6 +317,11 @@ export function main(): number {
|
|
|
176
317
|
return EXIT_ALLOW;
|
|
177
318
|
}
|
|
178
319
|
|
|
320
|
+
/** The dispatcher entry point. Argv-shaped, per `concern_main_signature`. */
|
|
321
|
+
export function main(_argv: readonly string[] = process.argv.slice(2)): number {
|
|
322
|
+
return runSettle();
|
|
323
|
+
}
|
|
324
|
+
|
|
179
325
|
// Bundle-safety: never auto-run when inlined into an esbuild bundle.
|
|
180
326
|
if (process.argv[1] !== undefined && path.basename(process.argv[1]).startsWith('obligation_settle')) {
|
|
181
327
|
process.exit(main());
|
|
@@ -16,10 +16,31 @@
|
|
|
16
16
|
*
|
|
17
17
|
* WHAT IT SEES, honestly. `_lib/structured_ask.ts` carries no observed
|
|
18
18
|
* per-host tool, so this guard matches on a name SHAPE and is filtered by the
|
|
19
|
-
* manifest's per-concern `tools:` key to the same candidate names.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* manifest's per-concern `tools:` key to the same candidate names. No host's
|
|
20
|
+
* delivered surface has been OBSERVED in this tree carrying a question-picker
|
|
21
|
+
* tool, which is why `STRUCTURED_ASK_SHAPES` is empty and no per-host shape row
|
|
22
|
+
* exists — the same sentence `_lib/structured_ask.ts` already uses about itself.
|
|
23
|
+
*
|
|
24
|
+
* That is NARROWER than the claim this header used to make. The earlier
|
|
25
|
+
* sentence asserted that on every host measured today the concern therefore
|
|
26
|
+
* matches nothing at all, and the two are different claims with only the first
|
|
27
|
+
* supported. The old wording is described here rather than quoted, because
|
|
28
|
+
* 1.1's drift check is a grep and a grep cannot tell a refuted quotation from
|
|
29
|
+
* a live assertion. The measurement base is ONE row:
|
|
30
|
+
* `_lib/host_capability.ts`'s registry records `structured_ask: false` for
|
|
31
|
+
* `claude` alone, observed-absent on Claude Code 2.1.263 on 2026-09-07, and the
|
|
32
|
+
* other eight hosts in `docs/enforcement-by-host.md` have no row at all — never
|
|
33
|
+
* looked, not measured. "Every host measured" read against that table therefore
|
|
34
|
+
* describes one host, and a present-tense claim about what the concern matches
|
|
35
|
+
* turns one dated single-version reading into a standing property, which the registry's own
|
|
36
|
+
* comment refuses in the same breath: the observation "is not a claim that the
|
|
37
|
+
* vendor ships no such tool anywhere, and a later session observing one writes
|
|
38
|
+
* the `true` row over it".
|
|
39
|
+
*
|
|
40
|
+
* So the honest statement is about what has been looked at, not about what
|
|
41
|
+
* fires. The concern exists so the first host whose delivered surface carries a
|
|
42
|
+
* picker meets the rule already in force, not because a violation has been
|
|
43
|
+
* observed.
|
|
23
44
|
*
|
|
24
45
|
* ENFORCEMENT REACH, also honestly. `fail_closed: false` and the exit-1 deny
|
|
25
46
|
* are only a deny where the host both binds `pre_tool_use` AND honours the
|
|
@@ -72,8 +72,7 @@ import { buildOrchestrationLine, type RecordInput } from '../_lib/orchestration_
|
|
|
72
72
|
import { DEFAULT_DIR } from '../orchestration_record.js';
|
|
73
73
|
import { is_replay_mode } from './state_io.js';
|
|
74
74
|
import { readHookStdin } from './hook_stdin.js';
|
|
75
|
-
|
|
76
|
-
const EXIT_ALLOW = 0;
|
|
75
|
+
import { EXIT_ALLOW } from './exit_codes.js';
|
|
77
76
|
|
|
78
77
|
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
79
78
|
type JsonObject = { [k: string]: JsonValue };
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type PermissionDecision,
|
|
21
21
|
type PermissionEmission,
|
|
22
22
|
} from "./host_semantics.js";
|
|
23
|
+
import { EXIT_ALLOW, EXIT_BLOCK } from './exit_codes.js';
|
|
23
24
|
|
|
24
25
|
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
25
26
|
type JsonObject = { [k: string]: JsonValue };
|
|
@@ -29,8 +30,6 @@ function _isObject(v: unknown): v is JsonObject {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/** Mirrors `dispatch_hook.EXIT_ALLOW` / `EXIT_BLOCK`; imported would be a cycle. */
|
|
32
|
-
const EXIT_ALLOW = 0;
|
|
33
|
-
const EXIT_BLOCK = 1;
|
|
34
33
|
|
|
35
34
|
/** The host's `tool_name`, or `""` when the event carries none. */
|
|
36
35
|
export function _payload_tool_name(envelope: JsonObject): string {
|
|
@@ -54,9 +54,7 @@ import path from "node:path";
|
|
|
54
54
|
import { execFileSync } from "node:child_process";
|
|
55
55
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
56
56
|
import { readHookStdin } from "./hook_stdin.js";
|
|
57
|
-
|
|
58
|
-
const EXIT_ALLOW = 0;
|
|
59
|
-
const EXIT_WARN = 2;
|
|
57
|
+
import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
|
|
60
58
|
|
|
61
59
|
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
62
60
|
type JsonObject = { [k: string]: JsonValue };
|
|
@@ -36,6 +36,7 @@ import { spawnSync } from "node:child_process";
|
|
|
36
36
|
import fs from "node:fs";
|
|
37
37
|
import path from "node:path";
|
|
38
38
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
39
|
+
import { EXIT_USAGE } from './exit_codes.js';
|
|
39
40
|
|
|
40
41
|
// src/scripts/hooks/replay_hook.ts → parents[3] is the repo root.
|
|
41
42
|
const REPO_ROOT = path.resolve(
|
|
@@ -175,7 +176,7 @@ function _parse_args(argv: string[]): ReplayArgs {
|
|
|
175
176
|
process.stderr.write(
|
|
176
177
|
`replay_hook: the following arguments are required: ${missing.join(", ")}\n`,
|
|
177
178
|
);
|
|
178
|
-
process.exit(
|
|
179
|
+
process.exit(EXIT_USAGE);
|
|
179
180
|
}
|
|
180
181
|
return args;
|
|
181
182
|
}
|
|
@@ -69,12 +69,11 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
69
69
|
import { unwrap, type JsonObject, type JsonValue } from './envelope.js';
|
|
70
70
|
import { readHookStdin } from './hook_stdin.js';
|
|
71
71
|
import { atomic_write_json } from './state_io.js';
|
|
72
|
+
import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
|
|
72
73
|
|
|
73
|
-
const EXIT_ALLOW = 0;
|
|
74
74
|
// Severity is taken from the EXIT CODE, not from the `decision` field — see
|
|
75
75
|
// the file header for why this never blocks on `claude`. This concern never
|
|
76
76
|
// returns exit 1 on any path.
|
|
77
|
-
const EXIT_WARN = 2;
|
|
78
77
|
|
|
79
78
|
/**
|
|
80
79
|
* Committed exemption list — deliberately duplicated from
|
|
@@ -49,8 +49,7 @@ import { deriveSessionKey, totalNonDocMutatedLinesWithMeasure } from './end_revi
|
|
|
49
49
|
import { unwrap, type JsonObject, type JsonValue } from './envelope.js';
|
|
50
50
|
import { readHookStdin } from './hook_stdin.js';
|
|
51
51
|
import { atomic_write_json } from './state_io.js';
|
|
52
|
-
|
|
53
|
-
const EXIT_OK = 0;
|
|
52
|
+
import { EXIT_ALLOW as EXIT_OK } from './exit_codes.js';
|
|
54
53
|
|
|
55
54
|
export function buildBaseline(workspaceRoot: string, now: Date = new Date()): ReviewBaseline {
|
|
56
55
|
const measured = totalNonDocMutatedLinesWithMeasure(workspaceRoot);
|
|
@@ -63,10 +63,9 @@ import {
|
|
|
63
63
|
} from "./block_no_verify.js";
|
|
64
64
|
import { readHookStdin } from "./hook_stdin.js";
|
|
65
65
|
import { detectRtkCached } from "../../install/rtkDetection.js";
|
|
66
|
+
import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
|
|
66
67
|
|
|
67
68
|
const SETTINGS_FILE = ".agent-settings.yml";
|
|
68
|
-
const EXIT_ALLOW = 0;
|
|
69
|
-
const EXIT_WARN = 2;
|
|
70
69
|
|
|
71
70
|
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
72
71
|
type JsonObject = { [k: string]: JsonValue };
|
|
@@ -102,7 +102,12 @@ import {
|
|
|
102
102
|
resolveLeanProjectionHosts,
|
|
103
103
|
} from '../_lib/lean_projection_mode.js';
|
|
104
104
|
import { enforcement_class_from_frontmatter } from '../_lib/obligation_frequency.js';
|
|
105
|
-
import {
|
|
105
|
+
import {
|
|
106
|
+
appendDelivered,
|
|
107
|
+
stamp,
|
|
108
|
+
type DeliveredRow,
|
|
109
|
+
type WriterInput,
|
|
110
|
+
} from '../_lib/obligations.js';
|
|
106
111
|
import {
|
|
107
112
|
loadRuleBody,
|
|
108
113
|
loadRouter,
|
|
@@ -110,9 +115,7 @@ import {
|
|
|
110
115
|
selectForInjection,
|
|
111
116
|
} from '../_lib/rule_injection.js';
|
|
112
117
|
import { readHookStdin } from './hook_stdin.js';
|
|
113
|
-
|
|
114
|
-
const EXIT_ALLOW = 0;
|
|
115
|
-
const EXIT_WARN = 2;
|
|
118
|
+
import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
|
|
116
119
|
|
|
117
120
|
/**
|
|
118
121
|
* Per-prompt injection ceiling, in UTF-8 BYTES.
|
|
@@ -268,7 +271,7 @@ export interface Injection {
|
|
|
268
271
|
export function recordDelivered(root: string, session: string, ruleIds: string[]): number {
|
|
269
272
|
try {
|
|
270
273
|
const now = stamp();
|
|
271
|
-
const rows: DeliveredRow[] = ruleIds.map((id) => {
|
|
274
|
+
const rows: WriterInput<DeliveredRow>[] = ruleIds.map((id) => {
|
|
272
275
|
const body = loadRuleBody(root, id);
|
|
273
276
|
return {
|
|
274
277
|
rule: id,
|
|
@@ -152,10 +152,9 @@ import {
|
|
|
152
152
|
sessionRefusalFile,
|
|
153
153
|
} from '../_lib/turn_end_refusals.js';
|
|
154
154
|
import { phaseLines } from '../_lib/roadmap_checkboxes.js';
|
|
155
|
+
import { commandBearing, renderVerifyLine, type VerifyClause } from '../_lib/verify_clause.js';
|
|
155
156
|
|
|
156
|
-
const EXIT_ALLOW = 0;
|
|
157
157
|
/** Dispatcher-internal block code; the dispatcher maps stop-slot 1 → host 2. */
|
|
158
|
-
const EXIT_BLOCK = 1;
|
|
159
158
|
|
|
160
159
|
/**
|
|
161
160
|
* The gate's cap, IMPORTED rather than restated — the two must agree by
|
|
@@ -194,6 +193,7 @@ import {
|
|
|
194
193
|
WALL_CLOCK_CAP_MS,
|
|
195
194
|
type LadderAction,
|
|
196
195
|
} from '../_lib/continuation_ladder.js';
|
|
196
|
+
import { EXIT_ALLOW, EXIT_BLOCK } from './exit_codes.js';
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
199
|
* DECLARED in `_lib/continuation_ladder.ts`, re-exported here: one declaration,
|
|
@@ -375,8 +375,8 @@ export function isDuplicateFire(
|
|
|
375
375
|
export interface NextStep {
|
|
376
376
|
/** The step's own line, checkbox stripped, truncated for the message. */
|
|
377
377
|
text: string;
|
|
378
|
-
/** The step's `verify:`
|
|
379
|
-
verify:
|
|
378
|
+
/** The step's `verify:` clause — command plus optional expectation. */
|
|
379
|
+
verify: VerifyClause | null;
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
export interface ScanResult {
|
|
@@ -481,15 +481,15 @@ export function scanOpenSteps(text: string): ScanResult {
|
|
|
481
481
|
* The comment form wins when both are present: it is the machine-readable one,
|
|
482
482
|
* and a step carrying both has a human-facing line and a tooling-facing line
|
|
483
483
|
* rather than two commands.
|
|
484
|
+
*
|
|
485
|
+
* The hook's narrowing only — a prose clause is not a command, and the
|
|
486
|
+
* continuation message renders commands. It parses nothing itself: a second
|
|
487
|
+
* copy of the arrow grammar is how this hook and `closure_scan` would drift,
|
|
488
|
+
* and a drifted grammar stops reading expectations without failing anything.
|
|
489
|
+
* `_lib/verify_clause.ts` owns the grammar and the sweep that keeps it single.
|
|
484
490
|
*/
|
|
485
|
-
export function extractVerify(stepText: string):
|
|
486
|
-
|
|
487
|
-
if (html !== null) return (html[1] as string).trim() || null;
|
|
488
|
-
// `verify:` then the command, both backticked. The label's own backticks
|
|
489
|
-
// are optional because the tree carries `verify:` and `` `verify:` ``.
|
|
490
|
-
const backticked = /`?verify:`?\s*`([^`]+)`/.exec(stepText);
|
|
491
|
-
if (backticked !== null) return (backticked[1] as string).trim() || null;
|
|
492
|
-
return null;
|
|
491
|
+
export function extractVerify(stepText: string): VerifyClause | null {
|
|
492
|
+
return commandBearing(stepText);
|
|
493
493
|
}
|
|
494
494
|
|
|
495
495
|
/**
|
|
@@ -1259,7 +1259,6 @@ export function main(): number {
|
|
|
1259
1259
|
return EXIT_ALLOW;
|
|
1260
1260
|
}
|
|
1261
1261
|
|
|
1262
|
-
|
|
1263
1262
|
// The two-tree provenance, carried on every event this run emits. See
|
|
1264
1263
|
// `provenance()` for the six fields and why none of them is a boolean.
|
|
1265
1264
|
//
|
|
@@ -1487,8 +1486,7 @@ export function main(): number {
|
|
|
1487
1486
|
|
|
1488
1487
|
function _continuationText(slug: string, scan: ScanResult, iteration: number): string {
|
|
1489
1488
|
const stepLine = scan.next
|
|
1490
|
-
? `Next step: ${scan.next.text}`
|
|
1491
|
-
(scan.next.verify ? `\n verify: ${scan.next.verify}` : '')
|
|
1489
|
+
? `Next step: ${scan.next.text}${renderVerifyLine(scan.next.verify)}`
|
|
1492
1490
|
: 'Next step: (first open checkbox in the roadmap)';
|
|
1493
1491
|
return (
|
|
1494
1492
|
`run-continuation: the contracted roadmap \`${slug}\` has ${scan.open} open ` +
|
|
@@ -143,10 +143,9 @@ import { isSyntheticPrompt } from "../_lib/prompt_shape.js";
|
|
|
143
143
|
import { OBSERVATION_LOG, readObservationLog, resolveSkillCatalogueRoots } from "../_lib/skill_catalogue.js";
|
|
144
144
|
import { knownBareNames } from "../_lib/skill_catalogue_series.js";
|
|
145
145
|
import { _tokenize, rank, type RankRow } from "../skill_tools/score_skill_relevance.js";
|
|
146
|
+
import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
|
|
146
147
|
|
|
147
|
-
const EXIT_ALLOW = 0;
|
|
148
148
|
/** See the header: reduced to a real exit of 0 by `emitFor`'s warn branch. */
|
|
149
|
-
const EXIT_WARN = 2;
|
|
150
149
|
|
|
151
150
|
/**
|
|
152
151
|
* Minimum top-1 ranker score before this concern speaks.
|
|
@@ -95,8 +95,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
95
95
|
import { is_replay_mode } from './state_io.js';
|
|
96
96
|
import { readHookStdin } from './hook_stdin.js';
|
|
97
97
|
import { resolveConsumerRoot } from './subagent_ledger_hook.js';
|
|
98
|
-
|
|
99
|
-
const EXIT_ALLOW = 0;
|
|
98
|
+
import { EXIT_ALLOW } from './exit_codes.js';
|
|
100
99
|
|
|
101
100
|
/** Would-warns per session before the valve goes silent — `MAX_NUDGES` shape. */
|
|
102
101
|
export const MAX_SHADOW_WARNS = 2;
|
|
@@ -55,8 +55,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
55
55
|
|
|
56
56
|
import { appendLedgerLine, openRecordStats, resolveConsumerRoot } from './subagent_ledger_hook.js';
|
|
57
57
|
import { readHookStdin } from './hook_stdin.js';
|
|
58
|
-
|
|
59
|
-
const EXIT_ALLOW = 0;
|
|
58
|
+
import { EXIT_ALLOW } from './exit_codes.js';
|
|
60
59
|
|
|
61
60
|
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
62
61
|
type JsonObject = { [k: string]: JsonValue };
|
|
@@ -115,8 +115,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
115
115
|
import { countContractFields, validateResponse } from '../_lib/subagent_response.js';
|
|
116
116
|
import { atomic_write_json, is_replay_mode } from './state_io.js';
|
|
117
117
|
import { readHookStdin } from './hook_stdin.js';
|
|
118
|
-
|
|
119
|
-
const EXIT_ALLOW = 0;
|
|
118
|
+
import { EXIT_ALLOW } from './exit_codes.js';
|
|
120
119
|
|
|
121
120
|
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
122
121
|
type JsonObject = { [k: string]: JsonValue };
|
|
@@ -51,8 +51,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
51
51
|
import { unwrap } from './envelope.js';
|
|
52
52
|
import { readHookStdin } from './hook_stdin.js';
|
|
53
53
|
import { humanTypedThisTurn } from '../_lib/machine_wake.js';
|
|
54
|
+
import { EXIT_ALLOW } from './exit_codes.js';
|
|
54
55
|
|
|
55
|
-
const EXIT_ALLOW = 0;
|
|
56
56
|
const SETTINGS_FILE = '.agent-settings.yml';
|
|
57
57
|
const SINK_REL = path.join('agents', 'runtime', 'state', 'audit', 'suggestion-capture.jsonl');
|
|
58
58
|
const LATCH_REL = path.join('agents', 'runtime', 'state', 'suggestion-latch.json');
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* table_fingerprint — the content fingerprint a compiled plumbing table is
|
|
5
|
+
* validated against.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from `dispatch_hook._manifest_fingerprint` so a SECOND table
|
|
8
|
+
* (`host_lowering.yaml`) can use the same helper without importing the
|
|
9
|
+
* dispatcher: `dispatch_hook` → `host_semantics` → `host_lowering` is a live
|
|
10
|
+
* import chain, so the reverse edge would be a cycle.
|
|
11
|
+
*
|
|
12
|
+
* DELIBERATELY CONTENT-DERIVED, NOT MTIME. The first version of the manifest
|
|
13
|
+
* fast path compared mtimes and that was a measured defect, not a theoretical
|
|
14
|
+
* one: on a fresh `actions/checkout` both files carry the checkout timestamp in
|
|
15
|
+
* whatever order git wrote them, so whether the optimisation applied at all was
|
|
16
|
+
* a coin flip — it won on a PR run (p95 129 ms) and lost on the trunk
|
|
17
|
+
* (p95 186 ms) for the same commit. A fingerprint is deterministic wherever the
|
|
18
|
+
* tree came from.
|
|
19
|
+
*
|
|
20
|
+
* SHA-256 over the UTF-8 bytes, plus the length. This replaced an FNV-1a loop
|
|
21
|
+
* in step 3.1 of `road-to-a-kernel-that-guards-its-plumbing`, and the reason
|
|
22
|
+
* the old one existed no longer holds. Its header said a cryptographic hash was
|
|
23
|
+
* refused because `require('node:crypto')` costs 8 ms of process start, which
|
|
24
|
+
* is most of what the precompiled table saves. Both halves were re-measured
|
|
25
|
+
* against the tree as it now stands, and both had gone stale:
|
|
26
|
+
*
|
|
27
|
+
* - The module is ALREADY LOADED. `dist/hooks/dispatch.js` carries 23
|
|
28
|
+
* top-level `import … from "node:crypto"` statements from other parts of the
|
|
29
|
+
* graph, so the 8 ms is paid on every dispatch whether this function uses it
|
|
30
|
+
* or not. There is no startup cost left to avoid.
|
|
31
|
+
* - SHA-256 is FASTER here, not slower: 0.106 ms against 0.113 ms over the
|
|
32
|
+
* 99,792-byte manifest, because Node's digest is native and the FNV-1a loop
|
|
33
|
+
* was 99,792 interpreted `charCodeAt` calls.
|
|
34
|
+
*
|
|
35
|
+
* The change is therefore free, and it buys the property the old comment had to
|
|
36
|
+
* disclaim: this now guards against a CRAFTED sibling as well as a stale one,
|
|
37
|
+
* which is what a file called a fingerprint should mean when the roadmap around
|
|
38
|
+
* it is about integrity.
|
|
39
|
+
*
|
|
40
|
+
* Producer and both consumers import THIS function — `compile_hook_manifest`
|
|
41
|
+
* writes the value, `dispatch_hook` and `host_lowering` check it — so the
|
|
42
|
+
* algorithm can never drift between the two sides. A mismatch is not a failure
|
|
43
|
+
* either way: the reader falls through to parsing the YAML source, which is
|
|
44
|
+
* correct and merely slower.
|
|
45
|
+
*/
|
|
46
|
+
export function tableFingerprint(text: string): string {
|
|
47
|
+
const digest = createHash('sha256').update(text, 'utf-8').digest('hex');
|
|
48
|
+
return `${digest}:${String(text.length)}`;
|
|
49
|
+
}
|
|
@@ -45,8 +45,7 @@ import {
|
|
|
45
45
|
import { is_replay_mode } from './state_io.js';
|
|
46
46
|
import { readHookStdin } from './hook_stdin.js';
|
|
47
47
|
import { _resolveRoot, readSettingsFor } from './telemetry_usage_hook.js';
|
|
48
|
-
|
|
49
|
-
const EXIT_ALLOW = 0;
|
|
48
|
+
import { EXIT_ALLOW } from './exit_codes.js';
|
|
50
49
|
|
|
51
50
|
/**
|
|
52
51
|
* Where `flush_sender.mjs` lives, relative to this module.
|
|
@@ -57,8 +57,7 @@ import {
|
|
|
57
57
|
} from '../../agent-src/templates/scripts/telemetry/transport.js';
|
|
58
58
|
import { is_replay_mode } from './state_io.js';
|
|
59
59
|
import { readHookStdin } from './hook_stdin.js';
|
|
60
|
-
|
|
61
|
-
const EXIT_ALLOW = 0;
|
|
60
|
+
import { EXIT_ALLOW } from './exit_codes.js';
|
|
62
61
|
|
|
63
62
|
/** The tool name Phase 0 spike 1 confirmed for a skill invocation. */
|
|
64
63
|
export const SKILL_TOOL_NAME = 'Skill';
|