@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
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
|
+
NO_RED_EVIDENCE_REASON,
|
|
3
4
|
deriveSessionKey,
|
|
5
|
+
describeRecordFinding,
|
|
4
6
|
foldRefusal,
|
|
7
|
+
foldShadow,
|
|
8
|
+
hasRedThenGreen,
|
|
5
9
|
isCiPoll,
|
|
6
10
|
isVacuousOutput,
|
|
11
|
+
isVerificationCommand,
|
|
7
12
|
parseRecord,
|
|
13
|
+
parseShadowRecord,
|
|
8
14
|
pendingCount,
|
|
9
15
|
readInstallBoundary,
|
|
16
|
+
readRunEvidence,
|
|
17
|
+
readTurnRunState,
|
|
10
18
|
sessionRefusalFile,
|
|
19
|
+
sessionShadowFile,
|
|
11
20
|
statePathFor,
|
|
12
21
|
validateAssumption
|
|
13
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-YJ72UIAK.js";
|
|
14
23
|
import {
|
|
15
24
|
readHookStdin
|
|
16
25
|
} from "./chunk-F7DQWYR5.js";
|
|
@@ -36,7 +45,7 @@ import {
|
|
|
36
45
|
frontmatterPaths,
|
|
37
46
|
proseEqual,
|
|
38
47
|
splitFrontmatter
|
|
39
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-FP36SWJD.js";
|
|
40
49
|
import "./chunk-FTAISGDQ.js";
|
|
41
50
|
import "./chunk-M2DPGFZ4.js";
|
|
42
51
|
import "./chunk-ROYYTEZU.js";
|
|
@@ -44,7 +53,7 @@ import "./chunk-BTWUYDGN.js";
|
|
|
44
53
|
import {
|
|
45
54
|
projectStoreSlug,
|
|
46
55
|
resolveSessionRole
|
|
47
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-JNMVLWLR.js";
|
|
48
57
|
import {
|
|
49
58
|
atomic_write_json,
|
|
50
59
|
atomic_write_text,
|
|
@@ -91,7 +100,7 @@ import {
|
|
|
91
100
|
|
|
92
101
|
// src/scripts/_cli/cmd_conformance.ts
|
|
93
102
|
var YAML = __toESM(require_dist(), 1);
|
|
94
|
-
import * as
|
|
103
|
+
import * as fs16 from "node:fs";
|
|
95
104
|
import * as os7 from "node:os";
|
|
96
105
|
import * as path15 from "node:path";
|
|
97
106
|
import process5 from "node:process";
|
|
@@ -124,7 +133,7 @@ function routeTargetPathsPosix(target) {
|
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
// src/scripts/report_conformance_funnel.ts
|
|
127
|
-
import * as
|
|
136
|
+
import * as fs15 from "node:fs";
|
|
128
137
|
import * as os6 from "node:os";
|
|
129
138
|
import * as path14 from "node:path";
|
|
130
139
|
import process4 from "node:process";
|
|
@@ -132,7 +141,7 @@ import { fileURLToPath as fileURLToPath13, pathToFileURL as pathToFileURL13 } fr
|
|
|
132
141
|
|
|
133
142
|
// src/scripts/conformance_scan.ts
|
|
134
143
|
import { createHash as createHash3 } from "node:crypto";
|
|
135
|
-
import
|
|
144
|
+
import fs11 from "node:fs";
|
|
136
145
|
import os2 from "node:os";
|
|
137
146
|
import path10 from "node:path";
|
|
138
147
|
import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL9 } from "node:url";
|
|
@@ -1918,7 +1927,7 @@ var COMMAND_OBJECTS = {
|
|
|
1918
1927
|
};
|
|
1919
1928
|
|
|
1920
1929
|
// src/scripts/hooks/turn_end_gate_hook.ts
|
|
1921
|
-
import * as
|
|
1930
|
+
import * as fs8 from "node:fs";
|
|
1922
1931
|
import * as path7 from "node:path";
|
|
1923
1932
|
import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
|
|
1924
1933
|
|
|
@@ -2298,162 +2307,6 @@ function detectDroppedDecision(assistantTurnTexts, closingFromPayload = false) {
|
|
|
2298
2307
|
return null;
|
|
2299
2308
|
}
|
|
2300
2309
|
|
|
2301
|
-
// src/scripts/hooks/end_review_nudge_hook.ts
|
|
2302
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2303
|
-
import * as crypto from "node:crypto";
|
|
2304
|
-
import * as fs5 from "node:fs";
|
|
2305
|
-
import * as os from "node:os";
|
|
2306
|
-
import * as path5 from "node:path";
|
|
2307
|
-
import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
2308
|
-
|
|
2309
|
-
// src/scripts/_lib/review_baseline.ts
|
|
2310
|
-
import { spawnSync } from "node:child_process";
|
|
2311
|
-
import * as fs4 from "node:fs";
|
|
2312
|
-
import * as path4 from "node:path";
|
|
2313
|
-
function currentHeadSha(cwd) {
|
|
2314
|
-
try {
|
|
2315
|
-
const r = spawnSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
|
|
2316
|
-
if (r.status !== 0) return null;
|
|
2317
|
-
const out = (r.stdout ?? "").trim();
|
|
2318
|
-
return out === "" ? null : out;
|
|
2319
|
-
} catch {
|
|
2320
|
-
return null;
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
var BASELINE_STATE_REL = path4.join("agents", "runtime", "state", "review-baseline");
|
|
2324
|
-
function baselineStateDir(workspaceRoot) {
|
|
2325
|
-
return path4.join(workspaceRoot, BASELINE_STATE_REL);
|
|
2326
|
-
}
|
|
2327
|
-
function baselineStateFile(workspaceRoot, sessionKey) {
|
|
2328
|
-
return path4.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
2329
|
-
}
|
|
2330
|
-
function parseBaseline(raw) {
|
|
2331
|
-
let decoded;
|
|
2332
|
-
try {
|
|
2333
|
-
decoded = JSON.parse(raw);
|
|
2334
|
-
} catch {
|
|
2335
|
-
return null;
|
|
2336
|
-
}
|
|
2337
|
-
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
|
|
2338
|
-
const o = decoded;
|
|
2339
|
-
const lines = o["baseline_lines"];
|
|
2340
|
-
if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
|
|
2341
|
-
const written = o["written_at"];
|
|
2342
|
-
if (typeof written !== "string") return null;
|
|
2343
|
-
const head = o["head_sha"];
|
|
2344
|
-
const measure = o["measure"];
|
|
2345
|
-
return {
|
|
2346
|
-
head_sha: typeof head === "string" && head !== "" ? head : null,
|
|
2347
|
-
baseline_lines: lines,
|
|
2348
|
-
...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
|
|
2349
|
-
written_at: written
|
|
2350
|
-
};
|
|
2351
|
-
}
|
|
2352
|
-
function readBaseline(workspaceRoot, sessionKey) {
|
|
2353
|
-
const file = baselineStateFile(workspaceRoot, sessionKey);
|
|
2354
|
-
let raw;
|
|
2355
|
-
try {
|
|
2356
|
-
raw = fs4.readFileSync(file, "utf-8");
|
|
2357
|
-
} catch (exc) {
|
|
2358
|
-
const code = exc?.code;
|
|
2359
|
-
return code === "ENOENT" ? "absent" : "unreadable";
|
|
2360
|
-
}
|
|
2361
|
-
return parseBaseline(raw) ?? "unreadable";
|
|
2362
|
-
}
|
|
2363
|
-
function applyBaseline(measuredLines, baseline, headSha, measuredMeasure = "exact") {
|
|
2364
|
-
if (baseline === "absent" || baseline === "unreadable") {
|
|
2365
|
-
return { applied: false, fallback: baseline, lines: measuredLines };
|
|
2366
|
-
}
|
|
2367
|
-
if (baseline.measure !== "exact" || measuredMeasure !== "exact") {
|
|
2368
|
-
return { applied: false, fallback: "mixed-measure", lines: measuredLines };
|
|
2369
|
-
}
|
|
2370
|
-
if (baseline.head_sha !== headSha()) {
|
|
2371
|
-
return { applied: false, fallback: "head-moved", lines: measuredLines };
|
|
2372
|
-
}
|
|
2373
|
-
return {
|
|
2374
|
-
applied: true,
|
|
2375
|
-
baseline,
|
|
2376
|
-
lines: Math.max(0, measuredLines - baseline.baseline_lines)
|
|
2377
|
-
};
|
|
2378
|
-
}
|
|
2379
|
-
|
|
2380
|
-
// src/scripts/_lib/audit_field_provenance.ts
|
|
2381
|
-
var MAX_APPLIED_IDS = 32;
|
|
2382
|
-
function appliedIds(values) {
|
|
2383
|
-
const out = [];
|
|
2384
|
-
for (const raw of values ?? []) {
|
|
2385
|
-
const v = String(raw).trim();
|
|
2386
|
-
if (v === "" || out.includes(v)) continue;
|
|
2387
|
-
out.push(v);
|
|
2388
|
-
if (out.length >= MAX_APPLIED_IDS) break;
|
|
2389
|
-
}
|
|
2390
|
-
return out;
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
// src/scripts/_lib/review_skipped_record.ts
|
|
2394
|
-
var BASELINE_APPLICATIONS = [
|
|
2395
|
-
"applied",
|
|
2396
|
-
"absent",
|
|
2397
|
-
"unreadable",
|
|
2398
|
-
"head_moved",
|
|
2399
|
-
"mixed_measure"
|
|
2400
|
-
];
|
|
2401
|
-
var PRODUCER_PRIVACY_CLASS = "ids-only";
|
|
2402
|
-
function isNonNegInt(n) {
|
|
2403
|
-
return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
|
|
2404
|
-
}
|
|
2405
|
-
var HIGH_RISK_DIFF_LINES = 200;
|
|
2406
|
-
function buildReviewSkippedLine(input) {
|
|
2407
|
-
const errors = [];
|
|
2408
|
-
if (!isNonNegInt(input.diff_lines)) {
|
|
2409
|
-
errors.push("diff_lines must be a non-negative integer");
|
|
2410
|
-
}
|
|
2411
|
-
if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
|
|
2412
|
-
errors.push('mutation_measure must be "exact" or "capped_approximation"');
|
|
2413
|
-
}
|
|
2414
|
-
if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
|
|
2415
|
-
if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
|
|
2416
|
-
if (input.baseline !== void 0 && !BASELINE_APPLICATIONS.includes(input.baseline)) {
|
|
2417
|
-
errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(", ")}`);
|
|
2418
|
-
}
|
|
2419
|
-
if (errors.length) return { line: null, errors };
|
|
2420
|
-
const line = {
|
|
2421
|
-
schema_version: 1,
|
|
2422
|
-
id: input.id,
|
|
2423
|
-
ts: input.ts,
|
|
2424
|
-
work_id: input.work_id ?? `review-skipped-${input.ts}`,
|
|
2425
|
-
phase: "report",
|
|
2426
|
-
outcome: "skipped",
|
|
2427
|
-
confidence_band: "medium",
|
|
2428
|
-
risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
|
|
2429
|
-
memory: { asks: 0, hits: 0 },
|
|
2430
|
-
verify: { claims: 0, first_try_passes: 0 },
|
|
2431
|
-
// COMPUTED, never a producer constant.
|
|
2432
|
-
rules_applied: appliedIds(input.rules_applied),
|
|
2433
|
-
// MANDATORY on every audit line, same as the orchestration producer.
|
|
2434
|
-
privacy_class: PRODUCER_PRIVACY_CLASS,
|
|
2435
|
-
// `skills_applied` is OMITTED here, deliberately and not by oversight.
|
|
2436
|
-
// audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
|
|
2437
|
-
// ("recorded, none applied"), and this writer observes a review that
|
|
2438
|
-
// did not happen -- it has no skill observation to offer in either
|
|
2439
|
-
// direction. Emitting `[]` would assert "no skills applied" on evidence
|
|
2440
|
-
// this producer never gathered, which is the exact fabrication the
|
|
2441
|
-
// absent/empty split exists to make impossible.
|
|
2442
|
-
persona: null,
|
|
2443
|
-
input_kind: "prompt",
|
|
2444
|
-
type: "note",
|
|
2445
|
-
review_skipped: {
|
|
2446
|
-
diff_lines: input.diff_lines,
|
|
2447
|
-
mutation_measure: input.mutation_measure,
|
|
2448
|
-
// Omitted, not defaulted, when the producer recorded nothing —
|
|
2449
|
-
// audit-log-v1's absent-versus-present split is the same one
|
|
2450
|
-
// `skills_applied` uses above, for the same reason.
|
|
2451
|
-
...input.baseline === void 0 ? {} : { baseline: input.baseline }
|
|
2452
|
-
}
|
|
2453
|
-
};
|
|
2454
|
-
return { line, errors: [] };
|
|
2455
|
-
}
|
|
2456
|
-
|
|
2457
2310
|
// src/scripts/hooks/envelope.ts
|
|
2458
2311
|
var ENVELOPE_KEYS = [
|
|
2459
2312
|
"schema_version",
|
|
@@ -2508,355 +2361,50 @@ function unwrap(stdin_text, default_platform = "generic") {
|
|
|
2508
2361
|
];
|
|
2509
2362
|
}
|
|
2510
2363
|
|
|
2511
|
-
// src/scripts/hooks/
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2364
|
+
// src/scripts/hooks/subagent_ledger_hook.ts
|
|
2365
|
+
import * as crypto from "node:crypto";
|
|
2366
|
+
import * as fs4 from "node:fs";
|
|
2367
|
+
import * as path4 from "node:path";
|
|
2368
|
+
import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
2369
|
+
|
|
2370
|
+
// src/scripts/_lib/subagent_response.ts
|
|
2371
|
+
var MAX_SUMMARY_CHARS = 2e3;
|
|
2372
|
+
var MAX_RESPONSE_LINE_CHARS = 240;
|
|
2373
|
+
var MAX_RESPONSE_ENTRIES = 40;
|
|
2374
|
+
var MAX_ARTIFACT_REF_CHARS = 200;
|
|
2375
|
+
var MAX_ENVELOPE_CHARS = 12e3;
|
|
2376
|
+
var CONFIDENCE = /* @__PURE__ */ new Set(["low", "medium", "high"]);
|
|
2377
|
+
var RESPONSE_REQUIRED_FIELDS = [
|
|
2378
|
+
"summary",
|
|
2379
|
+
"handoff",
|
|
2380
|
+
"confidence",
|
|
2381
|
+
"findings",
|
|
2382
|
+
"risks"
|
|
2383
|
+
];
|
|
2384
|
+
function countContractFields(input) {
|
|
2385
|
+
if (input === null || typeof input !== "object") return 0;
|
|
2386
|
+
const r = input;
|
|
2387
|
+
return RESPONSE_REQUIRED_FIELDS.filter((k) => Object.hasOwn(r, k)).length;
|
|
2531
2388
|
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
|
|
2537
|
-
if (!m) continue;
|
|
2538
|
-
const addedStr = m[1];
|
|
2539
|
-
const deletedStr = m[2];
|
|
2540
|
-
rows.push({
|
|
2541
|
-
added: addedStr === "-" ? 0 : Number(addedStr),
|
|
2542
|
-
deleted: deletedStr === "-" ? 0 : Number(deletedStr),
|
|
2543
|
-
path: m[3]
|
|
2544
|
-
});
|
|
2389
|
+
function validateResponse(input) {
|
|
2390
|
+
const errors = [];
|
|
2391
|
+
if (input === null || typeof input !== "object") {
|
|
2392
|
+
return { valid: false, errors: ["response is not an object"] };
|
|
2545
2393
|
}
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
}
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
maxBuffer: 16 * 1024 * 1024
|
|
2561
|
-
});
|
|
2562
|
-
if (r.status !== 0) return [];
|
|
2563
|
-
return parseNumstat(r.stdout ?? "");
|
|
2564
|
-
} catch {
|
|
2565
|
-
return [];
|
|
2566
|
-
}
|
|
2567
|
-
}
|
|
2568
|
-
function untrackedNonDocFiles(cwd) {
|
|
2569
|
-
try {
|
|
2570
|
-
const r = spawnSync2("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
2571
|
-
cwd,
|
|
2572
|
-
encoding: "utf8",
|
|
2573
|
-
maxBuffer: 16 * 1024 * 1024
|
|
2574
|
-
});
|
|
2575
|
-
if (r.status !== 0) return [];
|
|
2576
|
-
return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
|
|
2577
|
-
} catch {
|
|
2578
|
-
return [];
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
var BINARY_PROBE_BYTES = 8e3;
|
|
2582
|
-
var UNTRACKED_FILE_READ_CAP_BYTES = 8 * 1024 * 1024;
|
|
2583
|
-
function untrackedFileLineCount(cwd, relPath) {
|
|
2584
|
-
try {
|
|
2585
|
-
const abs = path5.resolve(cwd, relPath);
|
|
2586
|
-
const size = fs5.statSync(abs).size;
|
|
2587
|
-
if (size === 0) return 0;
|
|
2588
|
-
if (size > UNTRACKED_FILE_READ_CAP_BYTES) {
|
|
2589
|
-
return MUTATION_LINE_THRESHOLD + 1;
|
|
2590
|
-
}
|
|
2591
|
-
const buf = fs5.readFileSync(abs);
|
|
2592
|
-
if (buf.length === 0) return 0;
|
|
2593
|
-
const probe = buf.subarray(0, Math.min(buf.length, BINARY_PROBE_BYTES));
|
|
2594
|
-
if (probe.includes(0)) return 0;
|
|
2595
|
-
let lines = 0;
|
|
2596
|
-
for (const byte of buf) {
|
|
2597
|
-
if (byte === 10) lines += 1;
|
|
2598
|
-
}
|
|
2599
|
-
if (buf[buf.length - 1] !== 10) lines += 1;
|
|
2600
|
-
return lines;
|
|
2601
|
-
} catch {
|
|
2602
|
-
return 0;
|
|
2603
|
-
}
|
|
2604
|
-
}
|
|
2605
|
-
function totalNonDocMutatedLinesWithMeasure(cwd) {
|
|
2606
|
-
const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
|
|
2607
|
-
const untracked = untrackedNonDocFiles(cwd);
|
|
2608
|
-
if (untracked.length > UNTRACKED_FILE_CAP) {
|
|
2609
|
-
return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
|
|
2610
|
-
}
|
|
2611
|
-
const untrackedLines = untracked.reduce(
|
|
2612
|
-
(sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
|
|
2613
|
-
0
|
|
2614
|
-
);
|
|
2615
|
-
return { lines: trackedLines + untrackedLines, measure: "exact" };
|
|
2616
|
-
}
|
|
2617
|
-
function isObject(v) {
|
|
2618
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2619
|
-
}
|
|
2620
|
-
function str(v) {
|
|
2621
|
-
return typeof v === "string" ? v : "";
|
|
2622
|
-
}
|
|
2623
|
-
var DISPATCH_TOOL_RE = /^(agent|task)$/i;
|
|
2624
|
-
var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
|
|
2625
|
-
var REVIEW_SHAPED_PROMPT_RE = /\b(code\s+review|neutral\s+review|adversarial|judge|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
|
|
2626
|
-
var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
|
|
2627
|
-
function scanTranscriptForReviewer(transcriptText) {
|
|
2628
|
-
for (const raw of transcriptText.split("\n")) {
|
|
2629
|
-
const line = raw.trim();
|
|
2630
|
-
if (!line) continue;
|
|
2631
|
-
let obj;
|
|
2632
|
-
try {
|
|
2633
|
-
obj = JSON.parse(line);
|
|
2634
|
-
} catch {
|
|
2635
|
-
continue;
|
|
2636
|
-
}
|
|
2637
|
-
if (!isObject(obj) || obj["type"] !== "assistant") continue;
|
|
2638
|
-
const msg = obj["message"];
|
|
2639
|
-
if (!isObject(msg)) continue;
|
|
2640
|
-
const content = msg["content"];
|
|
2641
|
-
if (!Array.isArray(content)) continue;
|
|
2642
|
-
for (const blk of content) {
|
|
2643
|
-
if (!isObject(blk) || blk["type"] !== "tool_use") continue;
|
|
2644
|
-
const name = str(blk["name"]);
|
|
2645
|
-
const input = blk["input"];
|
|
2646
|
-
const inputObj = isObject(input) ? input : {};
|
|
2647
|
-
if (DISPATCH_TOOL_RE.test(name)) {
|
|
2648
|
-
const label = [
|
|
2649
|
-
str(inputObj["description"]),
|
|
2650
|
-
str(inputObj["subagent_type"])
|
|
2651
|
-
].filter(Boolean).join(" ");
|
|
2652
|
-
if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
|
|
2653
|
-
const prompt = str(inputObj["prompt"]);
|
|
2654
|
-
if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
|
|
2655
|
-
continue;
|
|
2656
|
-
}
|
|
2657
|
-
if (/^skill$/i.test(name)) {
|
|
2658
|
-
const skillName = str(inputObj["skill"]) || str(inputObj["name"]) || str(inputObj["command"]);
|
|
2659
|
-
if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
|
|
2660
|
-
continue;
|
|
2661
|
-
}
|
|
2662
|
-
if (REVIEW_SKILL_NAME_RE.test(name)) return true;
|
|
2663
|
-
}
|
|
2664
|
-
}
|
|
2665
|
-
return false;
|
|
2666
|
-
}
|
|
2667
|
-
function buildAdvisoryLine(diffLines) {
|
|
2668
|
-
return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
|
|
2669
|
-
}
|
|
2670
|
-
function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure, baseline) {
|
|
2671
|
-
if (is_replay_mode()) return;
|
|
2672
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2673
|
-
const { line, errors } = buildReviewSkippedLine({
|
|
2674
|
-
diff_lines: diffLines,
|
|
2675
|
-
mutation_measure: mutationMeasure,
|
|
2676
|
-
ts,
|
|
2677
|
-
id: crypto.randomUUID(),
|
|
2678
|
-
baseline
|
|
2679
|
-
});
|
|
2680
|
-
if (errors.length || line === null) return;
|
|
2681
|
-
try {
|
|
2682
|
-
const dir = path5.join(workspaceRoot, "agents", "runtime", "state", "audit");
|
|
2683
|
-
fs5.mkdirSync(dir, { recursive: true });
|
|
2684
|
-
const file = path5.join(dir, `${ts.slice(0, 7)}.jsonl`);
|
|
2685
|
-
fs5.appendFileSync(file, `${JSON.stringify(line)}
|
|
2686
|
-
`, "utf8");
|
|
2687
|
-
} catch {
|
|
2688
|
-
}
|
|
2689
|
-
}
|
|
2690
|
-
function sessionStateDir(workspaceRoot) {
|
|
2691
|
-
return path5.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
|
|
2692
|
-
}
|
|
2693
|
-
function sessionStateFile(workspaceRoot, sessionKey) {
|
|
2694
|
-
return path5.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
2695
|
-
}
|
|
2696
|
-
function deriveSessionKey2(envelope, payload) {
|
|
2697
|
-
const raw = str(envelope["session_id"]) || str(payload["transcript_path"] ?? payload["transcriptPath"]);
|
|
2698
|
-
const material = raw || "unknown-session";
|
|
2699
|
-
return crypto.createHash("sha256").update(material).digest("hex");
|
|
2700
|
-
}
|
|
2701
|
-
function hasFiredThisSession(workspaceRoot, sessionKey) {
|
|
2702
|
-
try {
|
|
2703
|
-
return fs5.existsSync(sessionStateFile(workspaceRoot, sessionKey));
|
|
2704
|
-
} catch {
|
|
2705
|
-
return false;
|
|
2706
|
-
}
|
|
2707
|
-
}
|
|
2708
|
-
function markFiredThisSession(workspaceRoot, sessionKey, ts) {
|
|
2709
|
-
try {
|
|
2710
|
-
atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
|
|
2711
|
-
} catch {
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
function _read_stdin() {
|
|
2715
|
-
return readHookStdin();
|
|
2716
|
-
}
|
|
2717
|
-
var FALLBACK_TO_TELEMETRY = {
|
|
2718
|
-
absent: "absent",
|
|
2719
|
-
unreadable: "unreadable",
|
|
2720
|
-
"head-moved": "head_moved",
|
|
2721
|
-
"mixed-measure": "mixed_measure"
|
|
2722
|
-
};
|
|
2723
|
-
function main4() {
|
|
2724
|
-
const [envelope, payload] = unwrap(_read_stdin(), "claude");
|
|
2725
|
-
const event = String(envelope["event"] ?? "");
|
|
2726
|
-
if (event !== "" && event !== "stop") {
|
|
2727
|
-
return 0;
|
|
2728
|
-
}
|
|
2729
|
-
const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
|
|
2730
|
-
let measuredLines;
|
|
2731
|
-
let mutationMeasure;
|
|
2732
|
-
try {
|
|
2733
|
-
const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
|
|
2734
|
-
measuredLines = measured.lines;
|
|
2735
|
-
mutationMeasure = measured.measure;
|
|
2736
|
-
} catch {
|
|
2737
|
-
return 0;
|
|
2738
|
-
}
|
|
2739
|
-
const sessionKey = deriveSessionKey2(envelope, payload);
|
|
2740
|
-
const baselineOutcome = applyBaseline(
|
|
2741
|
-
measuredLines,
|
|
2742
|
-
readBaseline(workspace_root, sessionKey),
|
|
2743
|
-
// A THUNK, not a value: on the `absent` and `unreadable` branches the
|
|
2744
|
-
// result is discarded, and those two are the steady state on every host
|
|
2745
|
-
// without this nudge and on every session before its first baseline
|
|
2746
|
-
// write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
|
|
2747
|
-
// each of those stops for nothing.
|
|
2748
|
-
() => currentHeadSha(workspace_root),
|
|
2749
|
-
mutationMeasure
|
|
2750
|
-
);
|
|
2751
|
-
const diffLines = baselineOutcome.lines;
|
|
2752
|
-
const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
|
|
2753
|
-
if (diffLines <= MUTATION_LINE_THRESHOLD) {
|
|
2754
|
-
return 0;
|
|
2755
|
-
}
|
|
2756
|
-
if (hasFiredThisSession(workspace_root, sessionKey)) {
|
|
2757
|
-
return 0;
|
|
2758
|
-
}
|
|
2759
|
-
const transcriptPath = str(
|
|
2760
|
-
payload["transcript_path"] ?? payload["transcriptPath"]
|
|
2761
|
-
).trim();
|
|
2762
|
-
let reviewerRan = false;
|
|
2763
|
-
if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
|
|
2764
|
-
try {
|
|
2765
|
-
reviewerRan = scanTranscriptForReviewer(fs5.readFileSync(transcriptPath, "utf-8"));
|
|
2766
|
-
} catch {
|
|
2767
|
-
reviewerRan = false;
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
if (reviewerRan) {
|
|
2771
|
-
return 0;
|
|
2772
|
-
}
|
|
2773
|
-
markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
2774
|
-
try {
|
|
2775
|
-
appendReviewSkippedTelemetry(
|
|
2776
|
-
workspace_root,
|
|
2777
|
-
diffLines,
|
|
2778
|
-
mutationMeasure,
|
|
2779
|
-
baselineApplication
|
|
2780
|
-
);
|
|
2781
|
-
} catch {
|
|
2782
|
-
}
|
|
2783
|
-
process.stdout.write(
|
|
2784
|
-
`${JSON.stringify({
|
|
2785
|
-
decision: "warn",
|
|
2786
|
-
reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
|
|
2787
|
-
additional_context: buildAdvisoryLine(diffLines)
|
|
2788
|
-
})}
|
|
2789
|
-
`
|
|
2790
|
-
);
|
|
2791
|
-
return EXIT_WARN2;
|
|
2792
|
-
}
|
|
2793
|
-
function _isCliEntry4() {
|
|
2794
|
-
if (true) {
|
|
2795
|
-
return false;
|
|
2796
|
-
}
|
|
2797
|
-
if (process.argv[1] === void 0) {
|
|
2798
|
-
return false;
|
|
2799
|
-
}
|
|
2800
|
-
const argvUrl = pathToFileURL4(path5.resolve(process.argv[1])).href;
|
|
2801
|
-
if (import.meta.url === argvUrl) {
|
|
2802
|
-
return true;
|
|
2803
|
-
}
|
|
2804
|
-
try {
|
|
2805
|
-
const here = fs5.realpathSync(fileURLToPath4(import.meta.url));
|
|
2806
|
-
const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
|
|
2807
|
-
return here === argv;
|
|
2808
|
-
} catch {
|
|
2809
|
-
return false;
|
|
2810
|
-
}
|
|
2811
|
-
}
|
|
2812
|
-
if (_isCliEntry4()) {
|
|
2813
|
-
process.exit(main4());
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
|
-
// src/scripts/hooks/subagent_ledger_hook.ts
|
|
2817
|
-
import * as crypto2 from "node:crypto";
|
|
2818
|
-
import * as fs6 from "node:fs";
|
|
2819
|
-
import * as path6 from "node:path";
|
|
2820
|
-
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
|
|
2821
|
-
|
|
2822
|
-
// src/scripts/_lib/subagent_response.ts
|
|
2823
|
-
var MAX_SUMMARY_CHARS = 2e3;
|
|
2824
|
-
var MAX_RESPONSE_LINE_CHARS = 240;
|
|
2825
|
-
var MAX_RESPONSE_ENTRIES = 40;
|
|
2826
|
-
var MAX_ARTIFACT_REF_CHARS = 200;
|
|
2827
|
-
var MAX_ENVELOPE_CHARS = 12e3;
|
|
2828
|
-
var CONFIDENCE = /* @__PURE__ */ new Set(["low", "medium", "high"]);
|
|
2829
|
-
var RESPONSE_REQUIRED_FIELDS = [
|
|
2830
|
-
"summary",
|
|
2831
|
-
"handoff",
|
|
2832
|
-
"confidence",
|
|
2833
|
-
"findings",
|
|
2834
|
-
"risks"
|
|
2835
|
-
];
|
|
2836
|
-
function countContractFields(input) {
|
|
2837
|
-
if (input === null || typeof input !== "object") return 0;
|
|
2838
|
-
const r = input;
|
|
2839
|
-
return RESPONSE_REQUIRED_FIELDS.filter((k) => Object.hasOwn(r, k)).length;
|
|
2840
|
-
}
|
|
2841
|
-
function validateResponse(input) {
|
|
2842
|
-
const errors = [];
|
|
2843
|
-
if (input === null || typeof input !== "object") {
|
|
2844
|
-
return { valid: false, errors: ["response is not an object"] };
|
|
2845
|
-
}
|
|
2846
|
-
const r = input;
|
|
2847
|
-
if (typeof r.summary !== "string") errors.push("summary missing or not a string");
|
|
2848
|
-
if (typeof r.handoff !== "string") errors.push("handoff missing or not a string");
|
|
2849
|
-
if (!CONFIDENCE.has(r.confidence)) errors.push(`confidence must be low|medium|high (got ${String(r.confidence)})`);
|
|
2850
|
-
if (!Array.isArray(r.findings)) errors.push("findings missing or not an array");
|
|
2851
|
-
if (!Array.isArray(r.risks)) errors.push("risks missing or not an array");
|
|
2852
|
-
if (Array.isArray(r.findings)) {
|
|
2853
|
-
for (const [i, f] of r.findings.entries()) {
|
|
2854
|
-
if (!f || typeof f.title !== "string") errors.push(`finding[${i}] missing title`);
|
|
2855
|
-
const refs = f?.evidence_refs;
|
|
2856
|
-
if (refs && refs.some((x) => typeof x !== "string" || x.includes("\n"))) {
|
|
2857
|
-
errors.push(`finding[${i}] evidence_refs must be ref tokens, not bodies`);
|
|
2858
|
-
}
|
|
2859
|
-
}
|
|
2394
|
+
const r = input;
|
|
2395
|
+
if (typeof r.summary !== "string") errors.push("summary missing or not a string");
|
|
2396
|
+
if (typeof r.handoff !== "string") errors.push("handoff missing or not a string");
|
|
2397
|
+
if (!CONFIDENCE.has(r.confidence)) errors.push(`confidence must be low|medium|high (got ${String(r.confidence)})`);
|
|
2398
|
+
if (!Array.isArray(r.findings)) errors.push("findings missing or not an array");
|
|
2399
|
+
if (!Array.isArray(r.risks)) errors.push("risks missing or not an array");
|
|
2400
|
+
if (Array.isArray(r.findings)) {
|
|
2401
|
+
for (const [i, f] of r.findings.entries()) {
|
|
2402
|
+
if (!f || typeof f.title !== "string") errors.push(`finding[${i}] missing title`);
|
|
2403
|
+
const refs = f?.evidence_refs;
|
|
2404
|
+
if (refs && refs.some((x) => typeof x !== "string" || x.includes("\n"))) {
|
|
2405
|
+
errors.push(`finding[${i}] evidence_refs must be ref tokens, not bodies`);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2860
2408
|
}
|
|
2861
2409
|
if (r.assumptions !== void 0) {
|
|
2862
2410
|
if (!Array.isArray(r.assumptions)) {
|
|
@@ -2918,22 +2466,57 @@ function validateResponse(input) {
|
|
|
2918
2466
|
return { valid: errors.length === 0, errors };
|
|
2919
2467
|
}
|
|
2920
2468
|
|
|
2921
|
-
// src/scripts/hooks/
|
|
2469
|
+
// src/scripts/hooks/exit_codes.ts
|
|
2922
2470
|
var EXIT_ALLOW3 = 0;
|
|
2923
|
-
|
|
2471
|
+
var EXIT_BLOCK = 1;
|
|
2472
|
+
var EXIT_WARN2 = 2;
|
|
2473
|
+
var EXIT_ERROR = 3;
|
|
2474
|
+
var EXIT_CODES = Object.freeze([
|
|
2475
|
+
Object.freeze({
|
|
2476
|
+
code: EXIT_ALLOW3,
|
|
2477
|
+
name: "allow",
|
|
2478
|
+
meaning: "no objection; the dispatcher passes the call through",
|
|
2479
|
+
owner: "every concern \u2014 the default verdict",
|
|
2480
|
+
authorizedBy: "nothing; allowing needs no authority"
|
|
2481
|
+
}),
|
|
2482
|
+
Object.freeze({
|
|
2483
|
+
code: EXIT_BLOCK,
|
|
2484
|
+
name: "block",
|
|
2485
|
+
meaning: "refuse the call; the dispatcher lowers this onto the host deny channel",
|
|
2486
|
+
owner: "a concern declared `severity: blocking` in hook_manifest.yaml",
|
|
2487
|
+
authorizedBy: "the manifest entry plus its row in the BLOCKING_ALLOWLIST of tests/hooks/concern_severity.test.ts \u2014 an advisory concern emitting this is downgraded to warn by dispatch_hook._is_advisory"
|
|
2488
|
+
}),
|
|
2489
|
+
Object.freeze({
|
|
2490
|
+
code: EXIT_WARN2,
|
|
2491
|
+
name: "warn",
|
|
2492
|
+
meaning: "advisory; the dispatcher exits 0 and surfaces the reason",
|
|
2493
|
+
owner: "any concern",
|
|
2494
|
+
authorizedBy: "nothing; warning is the ceiling for an advisory concern and is always available to a blocking one"
|
|
2495
|
+
}),
|
|
2496
|
+
Object.freeze({
|
|
2497
|
+
code: EXIT_ERROR,
|
|
2498
|
+
name: "error",
|
|
2499
|
+
meaning: "the concern crashed, timed out, or could not decide \u2014 any rc >= 3. The dispatcher resolves it by the concern`s declared severity, never by the number itself",
|
|
2500
|
+
owner: "no concern emits this on purpose; the runtime does",
|
|
2501
|
+
authorizedBy: "hook_manifest.yaml `fail_closed` and `severity` together \u2014 see docs/contracts/hook-architecture-v1.md \xA7 Exit-code semantics"
|
|
2502
|
+
})
|
|
2503
|
+
]);
|
|
2504
|
+
|
|
2505
|
+
// src/scripts/hooks/subagent_ledger_hook.ts
|
|
2506
|
+
function isObject(v) {
|
|
2924
2507
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2925
2508
|
}
|
|
2926
|
-
var LEDGER_DIR =
|
|
2509
|
+
var LEDGER_DIR = path4.join("agents", "runtime", "state", "subagent-ledger");
|
|
2927
2510
|
var OPEN_SUBDIR = "open";
|
|
2928
2511
|
var OPEN_RECORD_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2929
2512
|
function refFor(agentId) {
|
|
2930
|
-
return
|
|
2513
|
+
return crypto.createHash("sha256").update(agentId, "utf8").digest("hex").slice(0, 12);
|
|
2931
2514
|
}
|
|
2932
2515
|
function unwrapPayload(envelope) {
|
|
2933
2516
|
const inner = envelope["payload"];
|
|
2934
|
-
return
|
|
2517
|
+
return isObject(inner) ? inner : envelope;
|
|
2935
2518
|
}
|
|
2936
|
-
function
|
|
2519
|
+
function str(payload, ...keys) {
|
|
2937
2520
|
for (const k of keys) {
|
|
2938
2521
|
const v = payload[k];
|
|
2939
2522
|
if (typeof v === "string" && v) return v;
|
|
@@ -2941,11 +2524,11 @@ function str2(payload, ...keys) {
|
|
|
2941
2524
|
return null;
|
|
2942
2525
|
}
|
|
2943
2526
|
function extractParentId(payload, ownId) {
|
|
2944
|
-
const explicit =
|
|
2527
|
+
const explicit = str(payload, "parent_agent_id", "parentAgentId");
|
|
2945
2528
|
if (explicit) return explicit;
|
|
2946
2529
|
const session = payload["session"];
|
|
2947
|
-
if (
|
|
2948
|
-
const sessionAgent =
|
|
2530
|
+
if (isObject(session)) {
|
|
2531
|
+
const sessionAgent = str(session, "agent_id", "agentId");
|
|
2949
2532
|
if (sessionAgent && sessionAgent !== ownId) return sessionAgent;
|
|
2950
2533
|
}
|
|
2951
2534
|
return null;
|
|
@@ -3013,14 +2596,14 @@ function _jsonObjectCandidates(message) {
|
|
|
3013
2596
|
return out;
|
|
3014
2597
|
}
|
|
3015
2598
|
function openDir(root) {
|
|
3016
|
-
return
|
|
2599
|
+
return path4.join(root, LEDGER_DIR, OPEN_SUBDIR);
|
|
3017
2600
|
}
|
|
3018
2601
|
function openFile(root, ref) {
|
|
3019
|
-
return
|
|
2602
|
+
return path4.join(openDir(root), `${ref}.json`);
|
|
3020
2603
|
}
|
|
3021
2604
|
var DEPTH_BASES = /* @__PURE__ */ new Set(["observed", "asserted-parent", "assumed-root"]);
|
|
3022
2605
|
function _asOpenRecord(value, expectedRef) {
|
|
3023
|
-
if (!
|
|
2606
|
+
if (!isObject(value)) return null;
|
|
3024
2607
|
const ref = value["ref"];
|
|
3025
2608
|
if (typeof ref !== "string" || ref !== expectedRef) return null;
|
|
3026
2609
|
if (typeof value["started_at"] !== "string") return null;
|
|
@@ -3043,7 +2626,7 @@ function readOpenRecords(root) {
|
|
|
3043
2626
|
const out = /* @__PURE__ */ new Map();
|
|
3044
2627
|
let names;
|
|
3045
2628
|
try {
|
|
3046
|
-
names =
|
|
2629
|
+
names = fs4.readdirSync(openDir(root));
|
|
3047
2630
|
} catch {
|
|
3048
2631
|
return out;
|
|
3049
2632
|
}
|
|
@@ -3051,7 +2634,7 @@ function readOpenRecords(root) {
|
|
|
3051
2634
|
if (!name.endsWith(".json")) continue;
|
|
3052
2635
|
const expectedRef = name.slice(0, -".json".length);
|
|
3053
2636
|
try {
|
|
3054
|
-
const raw =
|
|
2637
|
+
const raw = fs4.readFileSync(path4.join(openDir(root), name), "utf8");
|
|
3055
2638
|
const rec = _asOpenRecord(JSON.parse(raw), expectedRef);
|
|
3056
2639
|
if (rec) out.set(rec.ref, rec);
|
|
3057
2640
|
} catch {
|
|
@@ -3066,13 +2649,13 @@ function resolveDepth(parentRef, open) {
|
|
|
3066
2649
|
return { depth: parent.depth + 1, depth_basis: "observed" };
|
|
3067
2650
|
}
|
|
3068
2651
|
function ledgerFileFor2(root, ts) {
|
|
3069
|
-
return
|
|
2652
|
+
return path4.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
|
|
3070
2653
|
}
|
|
3071
2654
|
function appendLedgerLine(root, ts, line) {
|
|
3072
2655
|
if (is_replay_mode()) return;
|
|
3073
2656
|
const file = ledgerFileFor2(root, ts);
|
|
3074
|
-
|
|
3075
|
-
|
|
2657
|
+
fs4.mkdirSync(path4.dirname(file), { recursive: true });
|
|
2658
|
+
fs4.appendFileSync(file, `${JSON.stringify(line)}
|
|
3076
2659
|
`, "utf8");
|
|
3077
2660
|
}
|
|
3078
2661
|
var WALL_CLOCK_ARMS_MS = [
|
|
@@ -3086,7 +2669,7 @@ function armsExceeded(elapsedMs) {
|
|
|
3086
2669
|
return WALL_CLOCK_ARMS_MS.filter((a) => elapsedMs >= a.ms).map((a) => a.label);
|
|
3087
2670
|
}
|
|
3088
2671
|
function openRecordStats(root, nowMs = Date.now()) {
|
|
3089
|
-
const ledgerPresent =
|
|
2672
|
+
const ledgerPresent = fs4.existsSync(path4.join(root, LEDGER_DIR));
|
|
3090
2673
|
const open = readOpenRecords(root);
|
|
3091
2674
|
let maxDepth = 0;
|
|
3092
2675
|
let counted = 0;
|
|
@@ -3114,13 +2697,13 @@ function writeOpenRecord(root, rec) {
|
|
|
3114
2697
|
function removeOpenRecord(root, ref) {
|
|
3115
2698
|
if (is_replay_mode()) return;
|
|
3116
2699
|
try {
|
|
3117
|
-
|
|
2700
|
+
fs4.unlinkSync(openFile(root, ref));
|
|
3118
2701
|
} catch {
|
|
3119
2702
|
}
|
|
3120
2703
|
}
|
|
3121
2704
|
function countOpen(root) {
|
|
3122
2705
|
try {
|
|
3123
|
-
return
|
|
2706
|
+
return fs4.readdirSync(openDir(root)).filter((n) => n.endsWith(".json")).length;
|
|
3124
2707
|
} catch {
|
|
3125
2708
|
return 0;
|
|
3126
2709
|
}
|
|
@@ -3161,12 +2744,12 @@ function appendUnidentified(root, event, nowIso, payload) {
|
|
|
3161
2744
|
unidentified: true,
|
|
3162
2745
|
// The agent TYPE is an id-shaped enum and may still be present; it is
|
|
3163
2746
|
// the only thing salvageable from a payload with no id.
|
|
3164
|
-
agent_type:
|
|
2747
|
+
agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
|
|
3165
2748
|
reason: "payload carried no agent id \u2014 nothing to correlate start with stop"
|
|
3166
2749
|
});
|
|
3167
2750
|
}
|
|
3168
2751
|
function handleStart(root, payload, nowIso, sessionId) {
|
|
3169
|
-
const agentId =
|
|
2752
|
+
const agentId = str(payload, "agent_id", "agentId");
|
|
3170
2753
|
if (agentId === null) {
|
|
3171
2754
|
appendUnidentified(root, "subagent_start", nowIso, payload);
|
|
3172
2755
|
return;
|
|
@@ -3177,7 +2760,7 @@ function handleStart(root, payload, nowIso, sessionId) {
|
|
|
3177
2760
|
const { depth, depth_basis } = resolveDepth(parentRef, readOpenRecords(root));
|
|
3178
2761
|
const rec = {
|
|
3179
2762
|
ref,
|
|
3180
|
-
agent_type:
|
|
2763
|
+
agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
|
|
3181
2764
|
started_at: nowIso,
|
|
3182
2765
|
parent_ref: parentRef,
|
|
3183
2766
|
depth,
|
|
@@ -3204,7 +2787,7 @@ function handleStart(root, payload, nowIso, sessionId) {
|
|
|
3204
2787
|
});
|
|
3205
2788
|
}
|
|
3206
2789
|
function handleStop(root, payload, nowIso, sessionId) {
|
|
3207
|
-
const agentId =
|
|
2790
|
+
const agentId = str(payload, "agent_id", "agentId");
|
|
3208
2791
|
if (agentId === null) {
|
|
3209
2792
|
appendUnidentified(root, "subagent_stop", nowIso, payload);
|
|
3210
2793
|
return;
|
|
@@ -3216,7 +2799,7 @@ function handleStop(root, payload, nowIso, sessionId) {
|
|
|
3216
2799
|
const started = Date.parse(rec.started_at);
|
|
3217
2800
|
if (Number.isFinite(started)) durationMs = Math.max(0, Date.parse(nowIso) - started);
|
|
3218
2801
|
}
|
|
3219
|
-
const parse2 = classifyEnvelope(
|
|
2802
|
+
const parse2 = classifyEnvelope(str(payload, "last_assistant_message", "lastAssistantMessage"));
|
|
3220
2803
|
removeOpenRecord(root, ref);
|
|
3221
2804
|
appendLedgerLine(root, nowIso, {
|
|
3222
2805
|
event: "subagent_stop",
|
|
@@ -3227,7 +2810,7 @@ function handleStop(root, payload, nowIso, sessionId) {
|
|
|
3227
2810
|
// relabel a cross-session stop as belonging to the dispatching session
|
|
3228
2811
|
// and hide exactly the mismatch this field exists to expose.
|
|
3229
2812
|
session_id: sessionId,
|
|
3230
|
-
agent_type: rec?.agent_type ??
|
|
2813
|
+
agent_type: rec?.agent_type ?? str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
|
|
3231
2814
|
depth: rec?.depth ?? null,
|
|
3232
2815
|
depth_basis: rec?.depth_basis ?? null,
|
|
3233
2816
|
// `null` = the matching start was never seen. That is a finding about
|
|
@@ -3247,12 +2830,12 @@ function handleStop(root, payload, nowIso, sessionId) {
|
|
|
3247
2830
|
}
|
|
3248
2831
|
function processEnvelope(envelope, consumerRoot) {
|
|
3249
2832
|
try {
|
|
3250
|
-
if (!
|
|
2833
|
+
if (!isObject(envelope)) return EXIT_ALLOW3;
|
|
3251
2834
|
const event = envelope["event"];
|
|
3252
2835
|
if (event !== "subagent_start" && event !== "subagent_stop") return EXIT_ALLOW3;
|
|
3253
2836
|
const payload = unwrapPayload(envelope);
|
|
3254
2837
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
3255
|
-
const sessionId =
|
|
2838
|
+
const sessionId = str(payload, "session_id", "sessionId") ?? str(envelope, "session_id", "sessionId");
|
|
3256
2839
|
reapStaleOpenRecords(consumerRoot, nowIso);
|
|
3257
2840
|
if (event === "subagent_start") handleStart(consumerRoot, payload, nowIso, sessionId);
|
|
3258
2841
|
else handleStop(consumerRoot, payload, nowIso, sessionId);
|
|
@@ -3262,43 +2845,600 @@ function processEnvelope(envelope, consumerRoot) {
|
|
|
3262
2845
|
return EXIT_ALLOW3;
|
|
3263
2846
|
}
|
|
3264
2847
|
function resolveConsumerRoot(envelope) {
|
|
3265
|
-
if (
|
|
3266
|
-
const pr =
|
|
2848
|
+
if (isObject(envelope)) {
|
|
2849
|
+
const pr = str(envelope, "workspace_root", "project_root");
|
|
3267
2850
|
if (pr) return pr;
|
|
3268
2851
|
const payload = unwrapPayload(envelope);
|
|
3269
|
-
const cwd =
|
|
2852
|
+
const cwd = str(payload, "cwd");
|
|
3270
2853
|
if (cwd) return cwd;
|
|
3271
2854
|
}
|
|
3272
2855
|
return process.cwd();
|
|
3273
2856
|
}
|
|
3274
|
-
function
|
|
2857
|
+
function main4() {
|
|
3275
2858
|
const raw = readHookStdin();
|
|
3276
2859
|
let envelope = {};
|
|
3277
2860
|
try {
|
|
3278
2861
|
envelope = raw.trim() ? JSON.parse(raw) : {};
|
|
3279
2862
|
} catch {
|
|
3280
|
-
return EXIT_ALLOW3;
|
|
2863
|
+
return EXIT_ALLOW3;
|
|
2864
|
+
}
|
|
2865
|
+
return processEnvelope(envelope, resolveConsumerRoot(envelope));
|
|
2866
|
+
}
|
|
2867
|
+
function _isCliEntry4() {
|
|
2868
|
+
if (true) {
|
|
2869
|
+
return false;
|
|
2870
|
+
}
|
|
2871
|
+
if (process.argv[1] === void 0) return false;
|
|
2872
|
+
const argvUrl = pathToFileURL4(path4.resolve(process.argv[1])).href;
|
|
2873
|
+
if (import.meta.url === argvUrl) return true;
|
|
2874
|
+
try {
|
|
2875
|
+
return fs4.realpathSync(fileURLToPath4(import.meta.url)) === fs4.realpathSync(path4.resolve(process.argv[1]));
|
|
2876
|
+
} catch {
|
|
2877
|
+
return false;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
if (_isCliEntry4()) process.exit(main4());
|
|
2881
|
+
|
|
2882
|
+
// src/scripts/_lib/turn_end_transcript.ts
|
|
2883
|
+
import * as fs7 from "node:fs";
|
|
2884
|
+
|
|
2885
|
+
// src/scripts/hooks/end_review_nudge_hook.ts
|
|
2886
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2887
|
+
import * as crypto2 from "node:crypto";
|
|
2888
|
+
import * as fs6 from "node:fs";
|
|
2889
|
+
import * as os from "node:os";
|
|
2890
|
+
import * as path6 from "node:path";
|
|
2891
|
+
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
|
|
2892
|
+
|
|
2893
|
+
// src/scripts/_lib/review_baseline.ts
|
|
2894
|
+
import { spawnSync } from "node:child_process";
|
|
2895
|
+
import * as fs5 from "node:fs";
|
|
2896
|
+
import * as path5 from "node:path";
|
|
2897
|
+
function currentHeadSha(cwd) {
|
|
2898
|
+
try {
|
|
2899
|
+
const r = spawnSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
|
|
2900
|
+
if (r.status !== 0) return null;
|
|
2901
|
+
const out = (r.stdout ?? "").trim();
|
|
2902
|
+
return out === "" ? null : out;
|
|
2903
|
+
} catch {
|
|
2904
|
+
return null;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
var BASELINE_STATE_REL = path5.join("agents", "runtime", "state", "review-baseline");
|
|
2908
|
+
function baselineStateDir(workspaceRoot) {
|
|
2909
|
+
return path5.join(workspaceRoot, BASELINE_STATE_REL);
|
|
2910
|
+
}
|
|
2911
|
+
function baselineStateFile(workspaceRoot, sessionKey) {
|
|
2912
|
+
return path5.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
2913
|
+
}
|
|
2914
|
+
function parseBaseline(raw) {
|
|
2915
|
+
let decoded;
|
|
2916
|
+
try {
|
|
2917
|
+
decoded = JSON.parse(raw);
|
|
2918
|
+
} catch {
|
|
2919
|
+
return null;
|
|
2920
|
+
}
|
|
2921
|
+
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
|
|
2922
|
+
const o = decoded;
|
|
2923
|
+
const lines = o["baseline_lines"];
|
|
2924
|
+
if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
|
|
2925
|
+
const written = o["written_at"];
|
|
2926
|
+
if (typeof written !== "string") return null;
|
|
2927
|
+
const head = o["head_sha"];
|
|
2928
|
+
const measure = o["measure"];
|
|
2929
|
+
return {
|
|
2930
|
+
head_sha: typeof head === "string" && head !== "" ? head : null,
|
|
2931
|
+
baseline_lines: lines,
|
|
2932
|
+
...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
|
|
2933
|
+
written_at: written
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
function readBaseline(workspaceRoot, sessionKey) {
|
|
2937
|
+
const file = baselineStateFile(workspaceRoot, sessionKey);
|
|
2938
|
+
let raw;
|
|
2939
|
+
try {
|
|
2940
|
+
raw = fs5.readFileSync(file, "utf-8");
|
|
2941
|
+
} catch (exc) {
|
|
2942
|
+
const code = exc?.code;
|
|
2943
|
+
return code === "ENOENT" ? "absent" : "unreadable";
|
|
2944
|
+
}
|
|
2945
|
+
return parseBaseline(raw) ?? "unreadable";
|
|
2946
|
+
}
|
|
2947
|
+
function applyBaseline(measuredLines, baseline, headSha, measuredMeasure = "exact") {
|
|
2948
|
+
if (baseline === "absent" || baseline === "unreadable") {
|
|
2949
|
+
return { applied: false, fallback: baseline, lines: measuredLines };
|
|
2950
|
+
}
|
|
2951
|
+
if (baseline.measure !== "exact" || measuredMeasure !== "exact") {
|
|
2952
|
+
return { applied: false, fallback: "mixed-measure", lines: measuredLines };
|
|
2953
|
+
}
|
|
2954
|
+
if (baseline.head_sha !== headSha()) {
|
|
2955
|
+
return { applied: false, fallback: "head-moved", lines: measuredLines };
|
|
2956
|
+
}
|
|
2957
|
+
return {
|
|
2958
|
+
applied: true,
|
|
2959
|
+
baseline,
|
|
2960
|
+
lines: Math.max(0, measuredLines - baseline.baseline_lines)
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
// src/scripts/_lib/audit_field_provenance.ts
|
|
2965
|
+
var MAX_APPLIED_IDS = 32;
|
|
2966
|
+
function appliedIds(values) {
|
|
2967
|
+
const out = [];
|
|
2968
|
+
for (const raw of values ?? []) {
|
|
2969
|
+
const v = String(raw).trim();
|
|
2970
|
+
if (v === "" || out.includes(v)) continue;
|
|
2971
|
+
out.push(v);
|
|
2972
|
+
if (out.length >= MAX_APPLIED_IDS) break;
|
|
2973
|
+
}
|
|
2974
|
+
return out;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
// src/scripts/_lib/review_skipped_record.ts
|
|
2978
|
+
var BASELINE_APPLICATIONS = [
|
|
2979
|
+
"applied",
|
|
2980
|
+
"absent",
|
|
2981
|
+
"unreadable",
|
|
2982
|
+
"head_moved",
|
|
2983
|
+
"mixed_measure"
|
|
2984
|
+
];
|
|
2985
|
+
var PRODUCER_PRIVACY_CLASS = "ids-only";
|
|
2986
|
+
function isNonNegInt(n) {
|
|
2987
|
+
return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
|
|
2988
|
+
}
|
|
2989
|
+
var HIGH_RISK_DIFF_LINES = 200;
|
|
2990
|
+
function buildReviewSkippedLine(input) {
|
|
2991
|
+
const errors = [];
|
|
2992
|
+
if (!isNonNegInt(input.diff_lines)) {
|
|
2993
|
+
errors.push("diff_lines must be a non-negative integer");
|
|
2994
|
+
}
|
|
2995
|
+
if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
|
|
2996
|
+
errors.push('mutation_measure must be "exact" or "capped_approximation"');
|
|
2997
|
+
}
|
|
2998
|
+
if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
|
|
2999
|
+
if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
|
|
3000
|
+
if (input.baseline !== void 0 && !BASELINE_APPLICATIONS.includes(input.baseline)) {
|
|
3001
|
+
errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(", ")}`);
|
|
3002
|
+
}
|
|
3003
|
+
if (errors.length) return { line: null, errors };
|
|
3004
|
+
const line = {
|
|
3005
|
+
schema_version: 1,
|
|
3006
|
+
id: input.id,
|
|
3007
|
+
ts: input.ts,
|
|
3008
|
+
work_id: input.work_id ?? `review-skipped-${input.ts}`,
|
|
3009
|
+
phase: "report",
|
|
3010
|
+
outcome: "skipped",
|
|
3011
|
+
confidence_band: "medium",
|
|
3012
|
+
risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
|
|
3013
|
+
memory: { asks: 0, hits: 0 },
|
|
3014
|
+
verify: { claims: 0, first_try_passes: 0 },
|
|
3015
|
+
// COMPUTED, never a producer constant.
|
|
3016
|
+
rules_applied: appliedIds(input.rules_applied),
|
|
3017
|
+
// MANDATORY on every audit line, same as the orchestration producer.
|
|
3018
|
+
privacy_class: PRODUCER_PRIVACY_CLASS,
|
|
3019
|
+
// `skills_applied` is OMITTED here, deliberately and not by oversight.
|
|
3020
|
+
// audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
|
|
3021
|
+
// ("recorded, none applied"), and this writer observes a review that
|
|
3022
|
+
// did not happen -- it has no skill observation to offer in either
|
|
3023
|
+
// direction. Emitting `[]` would assert "no skills applied" on evidence
|
|
3024
|
+
// this producer never gathered, which is the exact fabrication the
|
|
3025
|
+
// absent/empty split exists to make impossible.
|
|
3026
|
+
persona: null,
|
|
3027
|
+
input_kind: "prompt",
|
|
3028
|
+
type: "note",
|
|
3029
|
+
review_skipped: {
|
|
3030
|
+
diff_lines: input.diff_lines,
|
|
3031
|
+
mutation_measure: input.mutation_measure,
|
|
3032
|
+
// Omitted, not defaulted, when the producer recorded nothing —
|
|
3033
|
+
// audit-log-v1's absent-versus-present split is the same one
|
|
3034
|
+
// `skills_applied` uses above, for the same reason.
|
|
3035
|
+
...input.baseline === void 0 ? {} : { baseline: input.baseline }
|
|
3036
|
+
}
|
|
3037
|
+
};
|
|
3038
|
+
return { line, errors: [] };
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
// src/scripts/hooks/end_review_nudge_hook.ts
|
|
3042
|
+
var MUTATION_LINE_THRESHOLD = 50;
|
|
3043
|
+
var UNTRACKED_FILE_CAP = 20;
|
|
3044
|
+
var TRANSCRIPT_SCAN_MAX_BYTES = 50 * 1024 * 1024;
|
|
3045
|
+
function isSafeTranscriptPath(rawPath, opts = {}) {
|
|
3046
|
+
if (!rawPath || !/\.jsonl$/i.test(rawPath)) return false;
|
|
3047
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
3048
|
+
const maxBytes = opts.maxBytes ?? TRANSCRIPT_SCAN_MAX_BYTES;
|
|
3049
|
+
try {
|
|
3050
|
+
const realHome = fs6.realpathSync(homeDir);
|
|
3051
|
+
const realPath = fs6.realpathSync(rawPath);
|
|
3052
|
+
const relative3 = path6.relative(realHome, realPath);
|
|
3053
|
+
if (relative3 === "" || relative3.startsWith("..") || path6.isAbsolute(relative3)) {
|
|
3054
|
+
return false;
|
|
3055
|
+
}
|
|
3056
|
+
const { size } = fs6.statSync(realPath);
|
|
3057
|
+
return size <= maxBytes;
|
|
3058
|
+
} catch {
|
|
3059
|
+
return false;
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
function parseNumstat(raw) {
|
|
3063
|
+
const rows = [];
|
|
3064
|
+
for (const line of raw.split("\n")) {
|
|
3065
|
+
const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
|
|
3066
|
+
if (!m) continue;
|
|
3067
|
+
const addedStr = m[1];
|
|
3068
|
+
const deletedStr = m[2];
|
|
3069
|
+
rows.push({
|
|
3070
|
+
added: addedStr === "-" ? 0 : Number(addedStr),
|
|
3071
|
+
deleted: deletedStr === "-" ? 0 : Number(deletedStr),
|
|
3072
|
+
path: m[3]
|
|
3073
|
+
});
|
|
3074
|
+
}
|
|
3075
|
+
return rows;
|
|
3076
|
+
}
|
|
3077
|
+
var DOC_PATH_RE = /\.md$/i;
|
|
3078
|
+
function isDocPath(p) {
|
|
3079
|
+
return DOC_PATH_RE.test(p);
|
|
3080
|
+
}
|
|
3081
|
+
function nonDocMutatedLines(rows) {
|
|
3082
|
+
return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
|
|
3083
|
+
}
|
|
3084
|
+
function gitNumstatRows(cwd) {
|
|
3085
|
+
try {
|
|
3086
|
+
const r = spawnSync2("git", ["diff", "--numstat", "HEAD"], {
|
|
3087
|
+
cwd,
|
|
3088
|
+
encoding: "utf8",
|
|
3089
|
+
maxBuffer: 16 * 1024 * 1024
|
|
3090
|
+
});
|
|
3091
|
+
if (r.status !== 0) return [];
|
|
3092
|
+
return parseNumstat(r.stdout ?? "");
|
|
3093
|
+
} catch {
|
|
3094
|
+
return [];
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
function untrackedNonDocFiles(cwd) {
|
|
3098
|
+
try {
|
|
3099
|
+
const r = spawnSync2("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
3100
|
+
cwd,
|
|
3101
|
+
encoding: "utf8",
|
|
3102
|
+
maxBuffer: 16 * 1024 * 1024
|
|
3103
|
+
});
|
|
3104
|
+
if (r.status !== 0) return [];
|
|
3105
|
+
return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
|
|
3106
|
+
} catch {
|
|
3107
|
+
return [];
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
var BINARY_PROBE_BYTES = 8e3;
|
|
3111
|
+
var UNTRACKED_FILE_READ_CAP_BYTES = 8 * 1024 * 1024;
|
|
3112
|
+
function untrackedFileLineCount(cwd, relPath) {
|
|
3113
|
+
try {
|
|
3114
|
+
const abs = path6.resolve(cwd, relPath);
|
|
3115
|
+
const size = fs6.statSync(abs).size;
|
|
3116
|
+
if (size === 0) return 0;
|
|
3117
|
+
if (size > UNTRACKED_FILE_READ_CAP_BYTES) {
|
|
3118
|
+
return MUTATION_LINE_THRESHOLD + 1;
|
|
3119
|
+
}
|
|
3120
|
+
const buf = fs6.readFileSync(abs);
|
|
3121
|
+
if (buf.length === 0) return 0;
|
|
3122
|
+
const probe = buf.subarray(0, Math.min(buf.length, BINARY_PROBE_BYTES));
|
|
3123
|
+
if (probe.includes(0)) return 0;
|
|
3124
|
+
let lines = 0;
|
|
3125
|
+
for (const byte of buf) {
|
|
3126
|
+
if (byte === 10) lines += 1;
|
|
3127
|
+
}
|
|
3128
|
+
if (buf[buf.length - 1] !== 10) lines += 1;
|
|
3129
|
+
return lines;
|
|
3130
|
+
} catch {
|
|
3131
|
+
return 0;
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
function totalNonDocMutatedLinesWithMeasure(cwd) {
|
|
3135
|
+
const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
|
|
3136
|
+
const untracked = untrackedNonDocFiles(cwd);
|
|
3137
|
+
if (untracked.length > UNTRACKED_FILE_CAP) {
|
|
3138
|
+
return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
|
|
3139
|
+
}
|
|
3140
|
+
const untrackedLines = untracked.reduce(
|
|
3141
|
+
(sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
|
|
3142
|
+
0
|
|
3143
|
+
);
|
|
3144
|
+
return { lines: trackedLines + untrackedLines, measure: "exact" };
|
|
3145
|
+
}
|
|
3146
|
+
function isObject2(v) {
|
|
3147
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3148
|
+
}
|
|
3149
|
+
function str2(v) {
|
|
3150
|
+
return typeof v === "string" ? v : "";
|
|
3151
|
+
}
|
|
3152
|
+
var DISPATCH_TOOL_RE = /^(agent|task)$/i;
|
|
3153
|
+
var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
|
|
3154
|
+
var REVIEW_SHAPED_PROMPT_RE = /\b(code\s+review|neutral\s+review|adversarial|judge|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
|
|
3155
|
+
var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
|
|
3156
|
+
function scanTranscriptForReviewer(transcriptText) {
|
|
3157
|
+
for (const raw of transcriptText.split("\n")) {
|
|
3158
|
+
const line = raw.trim();
|
|
3159
|
+
if (!line) continue;
|
|
3160
|
+
let obj;
|
|
3161
|
+
try {
|
|
3162
|
+
obj = JSON.parse(line);
|
|
3163
|
+
} catch {
|
|
3164
|
+
continue;
|
|
3165
|
+
}
|
|
3166
|
+
if (!isObject2(obj) || obj["type"] !== "assistant") continue;
|
|
3167
|
+
const msg = obj["message"];
|
|
3168
|
+
if (!isObject2(msg)) continue;
|
|
3169
|
+
const content = msg["content"];
|
|
3170
|
+
if (!Array.isArray(content)) continue;
|
|
3171
|
+
for (const blk of content) {
|
|
3172
|
+
if (!isObject2(blk) || blk["type"] !== "tool_use") continue;
|
|
3173
|
+
const name = str2(blk["name"]);
|
|
3174
|
+
const input = blk["input"];
|
|
3175
|
+
const inputObj = isObject2(input) ? input : {};
|
|
3176
|
+
if (DISPATCH_TOOL_RE.test(name)) {
|
|
3177
|
+
const label = [
|
|
3178
|
+
str2(inputObj["description"]),
|
|
3179
|
+
str2(inputObj["subagent_type"])
|
|
3180
|
+
].filter(Boolean).join(" ");
|
|
3181
|
+
if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
|
|
3182
|
+
const prompt = str2(inputObj["prompt"]);
|
|
3183
|
+
if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
|
|
3184
|
+
continue;
|
|
3185
|
+
}
|
|
3186
|
+
if (/^skill$/i.test(name)) {
|
|
3187
|
+
const skillName = str2(inputObj["skill"]) || str2(inputObj["name"]) || str2(inputObj["command"]);
|
|
3188
|
+
if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
|
|
3189
|
+
continue;
|
|
3190
|
+
}
|
|
3191
|
+
if (REVIEW_SKILL_NAME_RE.test(name)) return true;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
return false;
|
|
3195
|
+
}
|
|
3196
|
+
function buildAdvisoryLine(diffLines) {
|
|
3197
|
+
return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
|
|
3198
|
+
}
|
|
3199
|
+
function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure, baseline) {
|
|
3200
|
+
if (is_replay_mode()) return;
|
|
3201
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
3202
|
+
const { line, errors } = buildReviewSkippedLine({
|
|
3203
|
+
diff_lines: diffLines,
|
|
3204
|
+
mutation_measure: mutationMeasure,
|
|
3205
|
+
ts,
|
|
3206
|
+
id: crypto2.randomUUID(),
|
|
3207
|
+
baseline
|
|
3208
|
+
});
|
|
3209
|
+
if (errors.length || line === null) return;
|
|
3210
|
+
try {
|
|
3211
|
+
const dir = path6.join(workspaceRoot, "agents", "runtime", "state", "audit");
|
|
3212
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
3213
|
+
const file = path6.join(dir, `${ts.slice(0, 7)}.jsonl`);
|
|
3214
|
+
fs6.appendFileSync(file, `${JSON.stringify(line)}
|
|
3215
|
+
`, "utf8");
|
|
3216
|
+
} catch {
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
function sessionStateDir(workspaceRoot) {
|
|
3220
|
+
return path6.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
|
|
3221
|
+
}
|
|
3222
|
+
function sessionStateFile(workspaceRoot, sessionKey) {
|
|
3223
|
+
return path6.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
3224
|
+
}
|
|
3225
|
+
function deriveSessionKey2(envelope, payload) {
|
|
3226
|
+
const raw = str2(envelope["session_id"]) || str2(payload["transcript_path"] ?? payload["transcriptPath"]);
|
|
3227
|
+
const material = raw || "unknown-session";
|
|
3228
|
+
return crypto2.createHash("sha256").update(material).digest("hex");
|
|
3229
|
+
}
|
|
3230
|
+
function hasFiredThisSession(workspaceRoot, sessionKey) {
|
|
3231
|
+
try {
|
|
3232
|
+
return fs6.existsSync(sessionStateFile(workspaceRoot, sessionKey));
|
|
3233
|
+
} catch {
|
|
3234
|
+
return false;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
function markFiredThisSession(workspaceRoot, sessionKey, ts) {
|
|
3238
|
+
try {
|
|
3239
|
+
atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
|
|
3240
|
+
} catch {
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
function _read_stdin() {
|
|
3244
|
+
return readHookStdin();
|
|
3245
|
+
}
|
|
3246
|
+
var FALLBACK_TO_TELEMETRY = {
|
|
3247
|
+
absent: "absent",
|
|
3248
|
+
unreadable: "unreadable",
|
|
3249
|
+
"head-moved": "head_moved",
|
|
3250
|
+
"mixed-measure": "mixed_measure"
|
|
3251
|
+
};
|
|
3252
|
+
function main5() {
|
|
3253
|
+
const [envelope, payload] = unwrap(_read_stdin(), "claude");
|
|
3254
|
+
const event = String(envelope["event"] ?? "");
|
|
3255
|
+
if (event !== "" && event !== "stop") {
|
|
3256
|
+
return 0;
|
|
3257
|
+
}
|
|
3258
|
+
const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
|
|
3259
|
+
let measuredLines;
|
|
3260
|
+
let mutationMeasure;
|
|
3261
|
+
try {
|
|
3262
|
+
const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
|
|
3263
|
+
measuredLines = measured.lines;
|
|
3264
|
+
mutationMeasure = measured.measure;
|
|
3265
|
+
} catch {
|
|
3266
|
+
return 0;
|
|
3267
|
+
}
|
|
3268
|
+
const sessionKey = deriveSessionKey2(envelope, payload);
|
|
3269
|
+
const baselineOutcome = applyBaseline(
|
|
3270
|
+
measuredLines,
|
|
3271
|
+
readBaseline(workspace_root, sessionKey),
|
|
3272
|
+
// A THUNK, not a value: on the `absent` and `unreadable` branches the
|
|
3273
|
+
// result is discarded, and those two are the steady state on every host
|
|
3274
|
+
// without this nudge and on every session before its first baseline
|
|
3275
|
+
// write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
|
|
3276
|
+
// each of those stops for nothing.
|
|
3277
|
+
() => currentHeadSha(workspace_root),
|
|
3278
|
+
mutationMeasure
|
|
3279
|
+
);
|
|
3280
|
+
const diffLines = baselineOutcome.lines;
|
|
3281
|
+
const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
|
|
3282
|
+
if (diffLines <= MUTATION_LINE_THRESHOLD) {
|
|
3283
|
+
return 0;
|
|
3284
|
+
}
|
|
3285
|
+
if (hasFiredThisSession(workspace_root, sessionKey)) {
|
|
3286
|
+
return 0;
|
|
3287
|
+
}
|
|
3288
|
+
const transcriptPath = str2(
|
|
3289
|
+
payload["transcript_path"] ?? payload["transcriptPath"]
|
|
3290
|
+
).trim();
|
|
3291
|
+
let reviewerRan = false;
|
|
3292
|
+
if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
|
|
3293
|
+
try {
|
|
3294
|
+
reviewerRan = scanTranscriptForReviewer(fs6.readFileSync(transcriptPath, "utf-8"));
|
|
3295
|
+
} catch {
|
|
3296
|
+
reviewerRan = false;
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
if (reviewerRan) {
|
|
3300
|
+
return 0;
|
|
3301
|
+
}
|
|
3302
|
+
markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
3303
|
+
try {
|
|
3304
|
+
appendReviewSkippedTelemetry(
|
|
3305
|
+
workspace_root,
|
|
3306
|
+
diffLines,
|
|
3307
|
+
mutationMeasure,
|
|
3308
|
+
baselineApplication
|
|
3309
|
+
);
|
|
3310
|
+
} catch {
|
|
3311
|
+
}
|
|
3312
|
+
process.stdout.write(
|
|
3313
|
+
`${JSON.stringify({
|
|
3314
|
+
decision: "warn",
|
|
3315
|
+
reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
|
|
3316
|
+
additional_context: buildAdvisoryLine(diffLines)
|
|
3317
|
+
})}
|
|
3318
|
+
`
|
|
3319
|
+
);
|
|
3320
|
+
return EXIT_WARN2;
|
|
3321
|
+
}
|
|
3322
|
+
function _isCliEntry5() {
|
|
3323
|
+
if (true) {
|
|
3324
|
+
return false;
|
|
3325
|
+
}
|
|
3326
|
+
if (process.argv[1] === void 0) {
|
|
3327
|
+
return false;
|
|
3328
|
+
}
|
|
3329
|
+
const argvUrl = pathToFileURL5(path6.resolve(process.argv[1])).href;
|
|
3330
|
+
if (import.meta.url === argvUrl) {
|
|
3331
|
+
return true;
|
|
3332
|
+
}
|
|
3333
|
+
try {
|
|
3334
|
+
const here = fs6.realpathSync(fileURLToPath5(import.meta.url));
|
|
3335
|
+
const argv = fs6.realpathSync(path6.resolve(process.argv[1]));
|
|
3336
|
+
return here === argv;
|
|
3337
|
+
} catch {
|
|
3338
|
+
return false;
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
if (_isCliEntry5()) {
|
|
3342
|
+
process.exit(main5());
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
// src/scripts/_lib/turn_end_transcript.ts
|
|
3346
|
+
function readTranscriptTail(transcriptPath, opts = {}) {
|
|
3347
|
+
const empty = {
|
|
3348
|
+
lastAssistant: "",
|
|
3349
|
+
turnOrdinal: 0,
|
|
3350
|
+
toolCalls: [],
|
|
3351
|
+
assistantTurnTexts: []
|
|
3352
|
+
};
|
|
3353
|
+
if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
|
|
3354
|
+
let lines;
|
|
3355
|
+
try {
|
|
3356
|
+
if (opts.maxBytes !== void 0 && fs7.statSync(transcriptPath).size > opts.maxBytes) {
|
|
3357
|
+
return empty;
|
|
3358
|
+
}
|
|
3359
|
+
lines = fs7.readFileSync(transcriptPath, "utf-8").split("\n");
|
|
3360
|
+
} catch {
|
|
3361
|
+
return empty;
|
|
3281
3362
|
}
|
|
3282
|
-
|
|
3363
|
+
let lastAssistant = "";
|
|
3364
|
+
let turnOrdinal = 0;
|
|
3365
|
+
let toolCalls = [];
|
|
3366
|
+
let assistantTurnTexts = [];
|
|
3367
|
+
for (const rawLine of lines) {
|
|
3368
|
+
const line = rawLine.trim();
|
|
3369
|
+
if (!line) continue;
|
|
3370
|
+
let obj;
|
|
3371
|
+
try {
|
|
3372
|
+
obj = JSON.parse(line);
|
|
3373
|
+
} catch {
|
|
3374
|
+
continue;
|
|
3375
|
+
}
|
|
3376
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
|
|
3377
|
+
const entry = obj;
|
|
3378
|
+
if (entry["isSidechain"] === true) continue;
|
|
3379
|
+
const role = entry["type"];
|
|
3380
|
+
if (role !== "assistant" && role !== "user") continue;
|
|
3381
|
+
const msg = entry["message"];
|
|
3382
|
+
if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
|
|
3383
|
+
const content = msg["content"];
|
|
3384
|
+
if (role === "assistant") {
|
|
3385
|
+
toolCalls.push(...extractToolCalls(content));
|
|
3386
|
+
}
|
|
3387
|
+
const text = _messageText(content);
|
|
3388
|
+
if (text === null) continue;
|
|
3389
|
+
if (role === "assistant") {
|
|
3390
|
+
lastAssistant = text;
|
|
3391
|
+
assistantTurnTexts.push(text);
|
|
3392
|
+
} else if (!isSyntheticPrompt(text)) {
|
|
3393
|
+
turnOrdinal += 1;
|
|
3394
|
+
toolCalls = [];
|
|
3395
|
+
assistantTurnTexts = [];
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
return {
|
|
3399
|
+
lastAssistant: lastAssistant.trim(),
|
|
3400
|
+
turnOrdinal,
|
|
3401
|
+
toolCalls,
|
|
3402
|
+
assistantTurnTexts
|
|
3403
|
+
};
|
|
3283
3404
|
}
|
|
3284
|
-
function
|
|
3285
|
-
if (
|
|
3286
|
-
|
|
3405
|
+
function extractToolCalls(content) {
|
|
3406
|
+
if (!Array.isArray(content)) return [];
|
|
3407
|
+
const out = [];
|
|
3408
|
+
for (const blk of content) {
|
|
3409
|
+
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
3410
|
+
const b = blk;
|
|
3411
|
+
if (b["type"] !== "tool_use") continue;
|
|
3412
|
+
const name = b["name"];
|
|
3413
|
+
if (typeof name !== "string") continue;
|
|
3414
|
+
const input = b["input"];
|
|
3415
|
+
const call = { name };
|
|
3416
|
+
if (typeof input === "object" && input !== null && !Array.isArray(input)) {
|
|
3417
|
+
const inp = input;
|
|
3418
|
+
const cmd = inp["command"];
|
|
3419
|
+
if (typeof cmd === "string") call.command = cmd;
|
|
3420
|
+
const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
|
|
3421
|
+
if (typeof p === "string") call.path = p;
|
|
3422
|
+
}
|
|
3423
|
+
out.push(call);
|
|
3287
3424
|
}
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3425
|
+
return out;
|
|
3426
|
+
}
|
|
3427
|
+
function _messageText(content) {
|
|
3428
|
+
if (typeof content === "string") return content;
|
|
3429
|
+
if (!Array.isArray(content)) return null;
|
|
3430
|
+
const parts = [];
|
|
3431
|
+
for (const blk of content) {
|
|
3432
|
+
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
3433
|
+
const b = blk;
|
|
3434
|
+
if (b["type"] !== "text") continue;
|
|
3435
|
+
const t = b["text"];
|
|
3436
|
+
if (typeof t === "string") parts.push(t);
|
|
3295
3437
|
}
|
|
3438
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
3296
3439
|
}
|
|
3297
|
-
if (_isCliEntry5()) process.exit(main5());
|
|
3298
3440
|
|
|
3299
3441
|
// src/scripts/hooks/turn_end_gate_hook.ts
|
|
3300
|
-
var EXIT_BLOCK = 1;
|
|
3301
|
-
var EXIT_ALLOW4 = 0;
|
|
3302
3442
|
var TRANSCRIPT_READ_MAX_BYTES = 8 * 1024 * 1024;
|
|
3303
3443
|
function stripFencedBlocks(text) {
|
|
3304
3444
|
const out = [];
|
|
@@ -3403,7 +3543,7 @@ function detectPromissory(reply) {
|
|
|
3403
3543
|
function readCiSettled(workspaceRoot, session_id) {
|
|
3404
3544
|
if (!hasStableSessionId(session_id)) return { seen: false, settled: false };
|
|
3405
3545
|
try {
|
|
3406
|
-
const raw =
|
|
3546
|
+
const raw = fs8.readFileSync(path7.join(workspaceRoot, statePathFor(session_id)), "utf-8");
|
|
3407
3547
|
const decoded = JSON.parse(raw);
|
|
3408
3548
|
if (!owns_session_state(decoded, session_id)) return { seen: false, settled: false };
|
|
3409
3549
|
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
@@ -3439,7 +3579,7 @@ function detectCompletionClaim(reply, ci) {
|
|
|
3439
3579
|
function readLanguagePin(workspaceRoot, session_id) {
|
|
3440
3580
|
if (!hasStableSessionId(session_id)) return "und";
|
|
3441
3581
|
try {
|
|
3442
|
-
const raw =
|
|
3582
|
+
const raw = fs8.readFileSync(
|
|
3443
3583
|
path7.join(workspaceRoot, statePathFor2(session_id)),
|
|
3444
3584
|
"utf-8"
|
|
3445
3585
|
);
|
|
@@ -3466,29 +3606,38 @@ function detectLanguage(reply, pinned) {
|
|
|
3466
3606
|
};
|
|
3467
3607
|
}
|
|
3468
3608
|
var _EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
3469
|
-
|
|
3470
|
-
function isVerificationCommand(command) {
|
|
3471
|
-
return _VERIFY_RE.test(command);
|
|
3472
|
-
}
|
|
3473
|
-
function detectUnverifiedEdit(toolCalls) {
|
|
3609
|
+
function detectUnverifiedEdit(toolCalls, runState = null) {
|
|
3474
3610
|
let lastEdit = -1;
|
|
3475
3611
|
for (let i = 0; i < toolCalls.length; i += 1) {
|
|
3476
3612
|
if (_EDIT_TOOLS.has(toolCalls[i].name)) lastEdit = i;
|
|
3477
3613
|
}
|
|
3478
3614
|
if (lastEdit === -1) return null;
|
|
3615
|
+
const edited = toolCalls[lastEdit].path ?? toolCalls[lastEdit].name;
|
|
3616
|
+
if (runState !== null) {
|
|
3617
|
+
const reading = readRunEvidence(runState);
|
|
3618
|
+
if (reading.passed) return null;
|
|
3619
|
+
if (!reading.instrumentGap) {
|
|
3620
|
+
return {
|
|
3621
|
+
detector: "verification",
|
|
3622
|
+
evidence: edited,
|
|
3623
|
+
reason: describeRecordFinding(reading.failed, reading.reasons),
|
|
3624
|
+
mode: "record"
|
|
3625
|
+
};
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3479
3628
|
for (let i = lastEdit + 1; i < toolCalls.length; i += 1) {
|
|
3480
3629
|
const c = toolCalls[i];
|
|
3481
3630
|
if (c.name === "Bash" && c.command !== void 0 && isVerificationCommand(c.command)) {
|
|
3482
3631
|
return null;
|
|
3483
3632
|
}
|
|
3484
3633
|
}
|
|
3485
|
-
const edited = toolCalls[lastEdit].path;
|
|
3486
3634
|
return {
|
|
3487
3635
|
detector: "verification",
|
|
3488
3636
|
// The path, never the diff: the evidence span is quoted into a refusal,
|
|
3489
3637
|
// and `ToolCall` is shaped so a file body cannot reach it.
|
|
3490
|
-
evidence: edited
|
|
3491
|
-
reason: "this turn changed a file and then ran no verification command \u2014 no test, type-check, lint or build call follows the last edit (verify-before-complete: a claim without a fresh run is unverified)"
|
|
3638
|
+
evidence: edited,
|
|
3639
|
+
reason: "this turn changed a file and then ran no verification command \u2014 no test, type-check, lint or build call follows the last edit (verify-before-complete: a claim without a fresh run is unverified)",
|
|
3640
|
+
mode: "transcript"
|
|
3492
3641
|
};
|
|
3493
3642
|
}
|
|
3494
3643
|
var _SOURCE_RE = /\.(?:ts|tsx|js|jsx|mjs|cjs|vue|svelte|php|py|rb|go|rs|java|kt|swift|cs|scala|ex|exs)$/i;
|
|
@@ -3500,18 +3649,37 @@ function _isTestPath(p) {
|
|
|
3500
3649
|
function _isProductionSource(p) {
|
|
3501
3650
|
return _SOURCE_RE.test(p) && !_isTestPath(p);
|
|
3502
3651
|
}
|
|
3503
|
-
function detectUntestedChange(reply, toolCalls) {
|
|
3652
|
+
function detectUntestedChange(reply, toolCalls, runState = null) {
|
|
3504
3653
|
const edited = toolCalls.filter((c) => _EDIT_TOOLS.has(c.name) && c.path !== void 0).map((c) => c.path);
|
|
3505
3654
|
const source = edited.filter(_isProductionSource);
|
|
3506
3655
|
if (source.length === 0) return null;
|
|
3507
|
-
|
|
3656
|
+
const wroteNewTest = toolCalls.some(
|
|
3657
|
+
(c) => c.name === "Write" && c.path !== void 0 && _isTestPath(c.path)
|
|
3658
|
+
);
|
|
3659
|
+
let noRedEvidence = false;
|
|
3660
|
+
if (edited.some(_isTestPath)) {
|
|
3661
|
+
if (!wroteNewTest) return null;
|
|
3662
|
+
if (runState === null) return null;
|
|
3663
|
+
if (hasRedThenGreen(runState)) return null;
|
|
3664
|
+
if (readRunEvidence(runState).instrumentGap) return null;
|
|
3665
|
+
noRedEvidence = true;
|
|
3666
|
+
}
|
|
3508
3667
|
const prose = visibleProse(reply);
|
|
3509
3668
|
const m = _COMPLETION_RE.exec(prose);
|
|
3510
3669
|
if (!m) return null;
|
|
3511
3670
|
if (_NEGATED_CLAIM_RE.test(_lineAround(prose, m.index))) return null;
|
|
3512
3671
|
const shown = source.slice(0, 3).join(", ");
|
|
3672
|
+
if (noRedEvidence) {
|
|
3673
|
+
return {
|
|
3674
|
+
detector: "untested",
|
|
3675
|
+
evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
|
|
3676
|
+
mode: "record",
|
|
3677
|
+
reason: NO_RED_EVIDENCE_REASON
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3513
3680
|
return {
|
|
3514
3681
|
detector: "untested",
|
|
3682
|
+
mode: "transcript",
|
|
3515
3683
|
evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
|
|
3516
3684
|
reason: "a completion claim over production code this turn changed, with NO test file touched anywhere in the turn \u2014 running a linter is not evidence the change works. Write the test that would have caught the failure, and enumerate the states the change actually has (every view, every CRUD path, the empty and error states) rather than the happy one. If a test genuinely does not belong here, say which file carries the coverage instead"
|
|
3517
3685
|
};
|
|
@@ -3521,7 +3689,7 @@ function sessionStateFile2(workspaceRoot, sessionKey) {
|
|
|
3521
3689
|
}
|
|
3522
3690
|
function alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal) {
|
|
3523
3691
|
try {
|
|
3524
|
-
const raw =
|
|
3692
|
+
const raw = fs8.readFileSync(sessionStateFile2(workspaceRoot, sessionKey), "utf-8");
|
|
3525
3693
|
const decoded = JSON.parse(raw);
|
|
3526
3694
|
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
3527
3695
|
return decoded["refused_turn"] === turnOrdinal;
|
|
@@ -3535,7 +3703,7 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
|
|
|
3535
3703
|
const file = sessionStateFile2(workspaceRoot, sessionKey);
|
|
3536
3704
|
let prev = null;
|
|
3537
3705
|
try {
|
|
3538
|
-
prev = parseRecord(
|
|
3706
|
+
prev = parseRecord(fs8.readFileSync(file, "utf-8"));
|
|
3539
3707
|
} catch {
|
|
3540
3708
|
}
|
|
3541
3709
|
let version;
|
|
@@ -3558,112 +3726,10 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
|
|
|
3558
3726
|
} catch {
|
|
3559
3727
|
}
|
|
3560
3728
|
}
|
|
3561
|
-
function readTranscriptTail(transcriptPath, opts = {}) {
|
|
3562
|
-
const empty = {
|
|
3563
|
-
lastAssistant: "",
|
|
3564
|
-
turnOrdinal: 0,
|
|
3565
|
-
toolCalls: [],
|
|
3566
|
-
assistantTurnTexts: []
|
|
3567
|
-
};
|
|
3568
|
-
if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
|
|
3569
|
-
let lines;
|
|
3570
|
-
try {
|
|
3571
|
-
if (opts.maxBytes !== void 0 && fs7.statSync(transcriptPath).size > opts.maxBytes) {
|
|
3572
|
-
return empty;
|
|
3573
|
-
}
|
|
3574
|
-
lines = fs7.readFileSync(transcriptPath, "utf-8").split("\n");
|
|
3575
|
-
} catch {
|
|
3576
|
-
return empty;
|
|
3577
|
-
}
|
|
3578
|
-
let lastAssistant = "";
|
|
3579
|
-
let turnOrdinal = 0;
|
|
3580
|
-
let toolCalls = [];
|
|
3581
|
-
let assistantTurnTexts = [];
|
|
3582
|
-
for (const rawLine of lines) {
|
|
3583
|
-
const line = rawLine.trim();
|
|
3584
|
-
if (!line) continue;
|
|
3585
|
-
let obj;
|
|
3586
|
-
try {
|
|
3587
|
-
obj = JSON.parse(line);
|
|
3588
|
-
} catch {
|
|
3589
|
-
continue;
|
|
3590
|
-
}
|
|
3591
|
-
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
|
|
3592
|
-
const entry = obj;
|
|
3593
|
-
if (entry["isSidechain"] === true) continue;
|
|
3594
|
-
const role = entry["type"];
|
|
3595
|
-
if (role !== "assistant" && role !== "user") continue;
|
|
3596
|
-
const msg = entry["message"];
|
|
3597
|
-
if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
|
|
3598
|
-
const content = msg["content"];
|
|
3599
|
-
if (role === "assistant") {
|
|
3600
|
-
toolCalls.push(...extractToolCalls(content));
|
|
3601
|
-
}
|
|
3602
|
-
const text = _messageText(content);
|
|
3603
|
-
if (text === null) continue;
|
|
3604
|
-
if (role === "assistant") {
|
|
3605
|
-
lastAssistant = text;
|
|
3606
|
-
assistantTurnTexts.push(text);
|
|
3607
|
-
} else if (!isSyntheticPrompt(text)) {
|
|
3608
|
-
turnOrdinal += 1;
|
|
3609
|
-
toolCalls = [];
|
|
3610
|
-
assistantTurnTexts = [];
|
|
3611
|
-
}
|
|
3612
|
-
}
|
|
3613
|
-
return {
|
|
3614
|
-
lastAssistant: lastAssistant.trim(),
|
|
3615
|
-
turnOrdinal,
|
|
3616
|
-
toolCalls,
|
|
3617
|
-
assistantTurnTexts
|
|
3618
|
-
};
|
|
3619
|
-
}
|
|
3620
|
-
function extractToolCalls(content) {
|
|
3621
|
-
if (!Array.isArray(content)) return [];
|
|
3622
|
-
const out = [];
|
|
3623
|
-
for (const blk of content) {
|
|
3624
|
-
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
3625
|
-
const b = blk;
|
|
3626
|
-
if (b["type"] !== "tool_use") continue;
|
|
3627
|
-
const name = b["name"];
|
|
3628
|
-
if (typeof name !== "string") continue;
|
|
3629
|
-
const input = b["input"];
|
|
3630
|
-
const call = { name };
|
|
3631
|
-
if (typeof input === "object" && input !== null && !Array.isArray(input)) {
|
|
3632
|
-
const inp = input;
|
|
3633
|
-
const cmd = inp["command"];
|
|
3634
|
-
if (typeof cmd === "string") call.command = cmd;
|
|
3635
|
-
const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
|
|
3636
|
-
if (typeof p === "string") call.path = p;
|
|
3637
|
-
}
|
|
3638
|
-
out.push(call);
|
|
3639
|
-
}
|
|
3640
|
-
return out;
|
|
3641
|
-
}
|
|
3642
|
-
function _messageText(content) {
|
|
3643
|
-
if (typeof content === "string") return content;
|
|
3644
|
-
if (!Array.isArray(content)) return null;
|
|
3645
|
-
const parts = [];
|
|
3646
|
-
for (const blk of content) {
|
|
3647
|
-
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
3648
|
-
const b = blk;
|
|
3649
|
-
if (b["type"] !== "text") continue;
|
|
3650
|
-
const t = b["text"];
|
|
3651
|
-
if (typeof t === "string") parts.push(t);
|
|
3652
|
-
}
|
|
3653
|
-
return parts.length > 0 ? parts.join("\n") : null;
|
|
3654
|
-
}
|
|
3655
3729
|
function str3(v) {
|
|
3656
3730
|
return typeof v === "string" ? v : "";
|
|
3657
3731
|
}
|
|
3658
|
-
function
|
|
3659
|
-
let envelope;
|
|
3660
|
-
let payload;
|
|
3661
|
-
try {
|
|
3662
|
-
[envelope, payload] = unwrap(readHookStdin(), "claude");
|
|
3663
|
-
} catch {
|
|
3664
|
-
return EXIT_ALLOW4;
|
|
3665
|
-
}
|
|
3666
|
-
if (payload["stop_hook_active"] === true) return EXIT_ALLOW4;
|
|
3732
|
+
function assembleDetectorInputs(envelope, payload) {
|
|
3667
3733
|
const workspaceRoot = str3(envelope["workspace_root"]) || process.cwd();
|
|
3668
3734
|
let dispatchOpen = false;
|
|
3669
3735
|
try {
|
|
@@ -3677,18 +3743,32 @@ function main6() {
|
|
|
3677
3743
|
transcriptPath,
|
|
3678
3744
|
{ maxBytes: TRANSCRIPT_READ_MAX_BYTES }
|
|
3679
3745
|
);
|
|
3680
|
-
if (!lastAssistant) return
|
|
3746
|
+
if (!lastAssistant) return null;
|
|
3681
3747
|
const payloadClosing = str3(
|
|
3682
3748
|
payload["last_assistant_message"] ?? payload["lastAssistantMessage"]
|
|
3683
3749
|
);
|
|
3684
3750
|
const closingFromPayload = payloadClosing !== "" && !assistantTurnTexts.includes(payloadClosing);
|
|
3685
3751
|
const turnTexts = closingFromPayload ? [...assistantTurnTexts, payloadClosing] : assistantTurnTexts;
|
|
3686
|
-
const promptId = str3(
|
|
3687
|
-
payload["prompt_id"] ?? payload["promptId"]
|
|
3688
|
-
);
|
|
3752
|
+
const promptId = str3(payload["prompt_id"] ?? payload["promptId"]);
|
|
3689
3753
|
const rawSessionId = str3(envelope["session_id"]) || "";
|
|
3690
3754
|
const sessionKey = deriveSessionKey(rawSessionId || "unknown-session");
|
|
3691
|
-
|
|
3755
|
+
const runState = readTurnRunState(workspaceRoot, rawSessionId);
|
|
3756
|
+
return {
|
|
3757
|
+
workspaceRoot,
|
|
3758
|
+
rawSessionId,
|
|
3759
|
+
sessionKey,
|
|
3760
|
+
turnOrdinal,
|
|
3761
|
+
promptId,
|
|
3762
|
+
lastAssistant,
|
|
3763
|
+
toolCalls,
|
|
3764
|
+
turnTexts,
|
|
3765
|
+
closingFromPayload,
|
|
3766
|
+
dispatchOpen,
|
|
3767
|
+
runState
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
function runDetectors(inputs) {
|
|
3771
|
+
const { dispatchOpen, lastAssistant, workspaceRoot, rawSessionId, toolCalls, runState } = inputs;
|
|
3692
3772
|
const findings = [];
|
|
3693
3773
|
for (const f of [
|
|
3694
3774
|
// A and D are the completion-adjacent pair a pending dispatch excuses
|
|
@@ -3696,17 +3776,14 @@ function main6() {
|
|
|
3696
3776
|
// excused by anything about a dispatch and run unchanged.
|
|
3697
3777
|
dispatchOpen ? null : detectPromissory(lastAssistant),
|
|
3698
3778
|
detectLanguage(lastAssistant, readLanguagePin(workspaceRoot, rawSessionId)),
|
|
3699
|
-
detectUnverifiedEdit(toolCalls),
|
|
3700
|
-
// Round 7 § Phase 1 — detector D. It is NOT unconditional
|
|
3701
|
-
//
|
|
3702
|
-
//
|
|
3703
|
-
//
|
|
3704
|
-
//
|
|
3705
|
-
//
|
|
3706
|
-
//
|
|
3707
|
-
// gating is where the comment above says gating belongs — inside the
|
|
3708
|
-
// detector: no CI observed, or a settled read, or no completion claim
|
|
3709
|
-
// ⇒ no finding.
|
|
3779
|
+
detectUnverifiedEdit(toolCalls, runState),
|
|
3780
|
+
// Round 7 § Phase 1 — detector D. It is NOT unconditional: A and D are
|
|
3781
|
+
// both excused by an open dispatch, per the note above. It shipped with
|
|
3782
|
+
// its own settings flag one commit earlier and that flag is gone:
|
|
3783
|
+
// `hooks.turn_end_gate.*` was deleted on 2026-08-12 because a
|
|
3784
|
+
// default-off safety gate is an absent one. Its gating is where the
|
|
3785
|
+
// note above says gating belongs — inside the detector: no CI observed,
|
|
3786
|
+
// or a settled read, or no completion claim ⇒ no finding.
|
|
3710
3787
|
dispatchOpen ? null : detectCompletionClaim(lastAssistant, readCiSettled(workspaceRoot, rawSessionId)),
|
|
3711
3788
|
// Detector E is UNCONDITIONAL, and that is the opposite choice from A
|
|
3712
3789
|
// and D one line above rather than an oversight. An open dispatch
|
|
@@ -3716,24 +3793,71 @@ function main6() {
|
|
|
3716
3793
|
// that produced the measured failure — IS the continuation that drops
|
|
3717
3794
|
// the decision, so narrowing E the same way would silence it in exactly
|
|
3718
3795
|
// the case it exists for.
|
|
3719
|
-
detectDroppedDecision(turnTexts, closingFromPayload),
|
|
3796
|
+
detectDroppedDecision(inputs.turnTexts, inputs.closingFromPayload),
|
|
3720
3797
|
// Detector F takes the OTHER side of that same question, and the two
|
|
3721
3798
|
// sitting adjacent is the clearest place to say why. E fires on a
|
|
3722
3799
|
// question already asked, which an open dispatch cannot excuse. F fires
|
|
3723
3800
|
// on a completion CLAIM, and a turn waiting on a subagent has not
|
|
3724
3801
|
// finished — so its closing is not the claim F is about. Same slot,
|
|
3725
3802
|
// opposite trigger, opposite treatment of `dispatchOpen`.
|
|
3726
|
-
dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls)
|
|
3803
|
+
dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls, runState)
|
|
3727
3804
|
]) {
|
|
3728
3805
|
if (f) findings.push(f);
|
|
3729
3806
|
}
|
|
3730
|
-
|
|
3807
|
+
return findings;
|
|
3808
|
+
}
|
|
3809
|
+
function recordShadow(inputs, layer) {
|
|
3810
|
+
if (is_replay_mode()) return;
|
|
3811
|
+
try {
|
|
3812
|
+
const findings = runDetectors(inputs);
|
|
3813
|
+
const file = sessionShadowFile(inputs.workspaceRoot, inputs.sessionKey);
|
|
3814
|
+
let prev = null;
|
|
3815
|
+
try {
|
|
3816
|
+
prev = parseShadowRecord(fs8.readFileSync(file, "utf-8"));
|
|
3817
|
+
} catch {
|
|
3818
|
+
}
|
|
3819
|
+
atomic_write_json(
|
|
3820
|
+
file,
|
|
3821
|
+
foldShadow(prev, {
|
|
3822
|
+
detectors: findings.map((f) => f.detector),
|
|
3823
|
+
turnOrdinal: inputs.turnOrdinal,
|
|
3824
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3825
|
+
layer
|
|
3826
|
+
})
|
|
3827
|
+
);
|
|
3828
|
+
} catch {
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
function main6() {
|
|
3832
|
+
let envelope;
|
|
3833
|
+
let payload;
|
|
3834
|
+
try {
|
|
3835
|
+
[envelope, payload] = unwrap(readHookStdin(), "claude");
|
|
3836
|
+
} catch {
|
|
3837
|
+
return EXIT_ALLOW3;
|
|
3838
|
+
}
|
|
3839
|
+
if (payload["stop_hook_active"] === true) {
|
|
3840
|
+
try {
|
|
3841
|
+
const retryInputs = assembleDetectorInputs(envelope, payload);
|
|
3842
|
+
if (retryInputs !== null) recordShadow(retryInputs, "stop_hook_active");
|
|
3843
|
+
} catch {
|
|
3844
|
+
}
|
|
3845
|
+
return EXIT_ALLOW3;
|
|
3846
|
+
}
|
|
3847
|
+
const inputs = assembleDetectorInputs(envelope, payload);
|
|
3848
|
+
if (inputs === null) return EXIT_ALLOW3;
|
|
3849
|
+
if (alreadyRefusedTurn(inputs.workspaceRoot, inputs.sessionKey, inputs.turnOrdinal)) {
|
|
3850
|
+
recordShadow(inputs, "refused_turn");
|
|
3851
|
+
return EXIT_ALLOW3;
|
|
3852
|
+
}
|
|
3853
|
+
const findings = runDetectors(inputs);
|
|
3854
|
+
if (findings.length === 0) return EXIT_ALLOW3;
|
|
3731
3855
|
markRefusedTurn(
|
|
3732
|
-
workspaceRoot,
|
|
3733
|
-
sessionKey,
|
|
3734
|
-
turnOrdinal,
|
|
3856
|
+
inputs.workspaceRoot,
|
|
3857
|
+
inputs.sessionKey,
|
|
3858
|
+
inputs.turnOrdinal,
|
|
3735
3859
|
findings.map((f) => f.detector),
|
|
3736
|
-
promptId
|
|
3860
|
+
inputs.promptId
|
|
3737
3861
|
);
|
|
3738
3862
|
const lines = findings.map(
|
|
3739
3863
|
(f) => ` \xB7 ${f.detector}: ${f.reason}
|
|
@@ -3760,8 +3884,8 @@ function _isCliEntry6() {
|
|
|
3760
3884
|
return true;
|
|
3761
3885
|
}
|
|
3762
3886
|
try {
|
|
3763
|
-
const here =
|
|
3764
|
-
const argv =
|
|
3887
|
+
const here = fs8.realpathSync(fileURLToPath6(import.meta.url));
|
|
3888
|
+
const argv = fs8.realpathSync(path7.resolve(process.argv[1]));
|
|
3765
3889
|
return here === argv;
|
|
3766
3890
|
} catch {
|
|
3767
3891
|
return false;
|
|
@@ -3774,7 +3898,7 @@ if (_isCliEntry6()) {
|
|
|
3774
3898
|
// src/scripts/hooks/evidence_independence.ts
|
|
3775
3899
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
3776
3900
|
import crypto3 from "node:crypto";
|
|
3777
|
-
import
|
|
3901
|
+
import fs9 from "node:fs";
|
|
3778
3902
|
import path8 from "node:path";
|
|
3779
3903
|
import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL7 } from "node:url";
|
|
3780
3904
|
|
|
@@ -3794,8 +3918,6 @@ function isTestPath(p) {
|
|
|
3794
3918
|
}
|
|
3795
3919
|
|
|
3796
3920
|
// src/scripts/hooks/evidence_independence.ts
|
|
3797
|
-
var EXIT_ALLOW5 = 0;
|
|
3798
|
-
var EXIT_BLOCK2 = 1;
|
|
3799
3921
|
var STATE_FILE3 = path8.join("agents", "state", "evidence-dispatch.json");
|
|
3800
3922
|
function _isObject5(v) {
|
|
3801
3923
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -3864,7 +3986,7 @@ function extractDispatch(envelope) {
|
|
|
3864
3986
|
}
|
|
3865
3987
|
function _ledgerStamp(consumer_root) {
|
|
3866
3988
|
try {
|
|
3867
|
-
const raw =
|
|
3989
|
+
const raw = fs9.readFileSync(
|
|
3868
3990
|
path8.join(consumer_root, "agents", "state", "git-authorization.json"),
|
|
3869
3991
|
"utf8"
|
|
3870
3992
|
);
|
|
@@ -3876,7 +3998,7 @@ function _ledgerStamp(consumer_root) {
|
|
|
3876
3998
|
}
|
|
3877
3999
|
function _load(target) {
|
|
3878
4000
|
try {
|
|
3879
|
-
const decoded = JSON.parse(
|
|
4001
|
+
const decoded = JSON.parse(fs9.readFileSync(target, "utf8"));
|
|
3880
4002
|
if (_isObject5(decoded) && Array.isArray(decoded["evaluations"])) {
|
|
3881
4003
|
return decoded;
|
|
3882
4004
|
}
|
|
@@ -3887,12 +4009,12 @@ function _load(target) {
|
|
|
3887
4009
|
var WARN_TEXT = "First evaluation dispatch this turn. Two shapes fabricated a binding honest-null in the audited sessions \u2014 check your prompt for both before it goes out: (1) a verdict pre-loaded into the prompt, and (2) a scope narrowed to files you chose. A review you commissioned on your own work is admissible as gate evidence only when the prompt is recorded alongside the verdict.";
|
|
3888
4010
|
function decide(tool, prompt, priorEvaluations) {
|
|
3889
4011
|
if (!isDispatchTool(tool) || !isEvaluationPrompt(prompt)) {
|
|
3890
|
-
return { exit:
|
|
4012
|
+
return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
|
|
3891
4013
|
}
|
|
3892
4014
|
const preloaded = preloadedVerdict(prompt);
|
|
3893
4015
|
if (preloaded !== null) {
|
|
3894
4016
|
return {
|
|
3895
|
-
exit:
|
|
4017
|
+
exit: EXIT_BLOCK,
|
|
3896
4018
|
stdout: "",
|
|
3897
4019
|
stderr: `Blocked: this evaluation prompt pre-loads its verdict ("${preloaded}"). That is the construct that produced a fabricated NO-FINDINGS committed as binding gate evidence, over a delta an unsteered pass then found a live critical in. Remove the expectation from the prompt and let the evaluator reach its own verdict.
|
|
3898
4020
|
`,
|
|
@@ -3900,11 +4022,11 @@ function decide(tool, prompt, priorEvaluations) {
|
|
|
3900
4022
|
};
|
|
3901
4023
|
}
|
|
3902
4024
|
if (!isSelfScoped(prompt)) {
|
|
3903
|
-
return { exit:
|
|
4025
|
+
return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
|
|
3904
4026
|
}
|
|
3905
4027
|
if (priorEvaluations >= 1) {
|
|
3906
4028
|
return {
|
|
3907
|
-
exit:
|
|
4029
|
+
exit: EXIT_ALLOW3,
|
|
3908
4030
|
stdout: `${JSON.stringify({
|
|
3909
4031
|
decision: "warn",
|
|
3910
4032
|
reason: `Second evaluation dispatch of your own work in this turn. If both passes judge the SAME subject, that is verdict shopping \u2014 commissioning another with a different prompt or scope selects the answer instead of measuring it. Report what the first pass returned, then re-plan. If this is an implementation fan-out rather than a second review, carry on: this check reads prose and cannot tell the two apart with certainty.`
|
|
@@ -3915,7 +4037,7 @@ function decide(tool, prompt, priorEvaluations) {
|
|
|
3915
4037
|
};
|
|
3916
4038
|
}
|
|
3917
4039
|
return {
|
|
3918
|
-
exit:
|
|
4040
|
+
exit: EXIT_ALLOW3,
|
|
3919
4041
|
stdout: `${JSON.stringify({ decision: "warn", reason: WARN_TEXT })}
|
|
3920
4042
|
`,
|
|
3921
4043
|
stderr: "",
|
|
@@ -3957,7 +4079,7 @@ function run3(stdin_text, options) {
|
|
|
3957
4079
|
envelope = decoded;
|
|
3958
4080
|
}
|
|
3959
4081
|
} catch {
|
|
3960
|
-
return
|
|
4082
|
+
return EXIT_ALLOW3;
|
|
3961
4083
|
}
|
|
3962
4084
|
}
|
|
3963
4085
|
const target = path8.join(options.consumer_root, STATE_FILE3);
|
|
@@ -3976,7 +4098,7 @@ function run3(stdin_text, options) {
|
|
|
3976
4098
|
if (staged !== null && touchesTestAndCode(staged)) {
|
|
3977
4099
|
process.stdout.write(`${JSON.stringify({ decision: "warn", reason: flag })}
|
|
3978
4100
|
`);
|
|
3979
|
-
return
|
|
4101
|
+
return EXIT_ALLOW3;
|
|
3980
4102
|
}
|
|
3981
4103
|
}
|
|
3982
4104
|
const decision = decide(tool, prompt, state.evaluations.length);
|
|
@@ -4033,8 +4155,8 @@ function _isCliEntry7() {
|
|
|
4033
4155
|
return true;
|
|
4034
4156
|
}
|
|
4035
4157
|
try {
|
|
4036
|
-
const here =
|
|
4037
|
-
const argv =
|
|
4158
|
+
const here = fs9.realpathSync(fileURLToPath7(import.meta.url));
|
|
4159
|
+
const argv = fs9.realpathSync(path8.resolve(process.argv[1]));
|
|
4038
4160
|
return here === argv;
|
|
4039
4161
|
} catch {
|
|
4040
4162
|
return false;
|
|
@@ -4045,7 +4167,7 @@ if (_isCliEntry7()) {
|
|
|
4045
4167
|
}
|
|
4046
4168
|
|
|
4047
4169
|
// src/scripts/hooks/delegation_nudge_hook.ts
|
|
4048
|
-
import
|
|
4170
|
+
import fs10 from "node:fs";
|
|
4049
4171
|
import path9 from "node:path";
|
|
4050
4172
|
import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL8 } from "node:url";
|
|
4051
4173
|
|
|
@@ -4327,8 +4449,6 @@ function resolveSubagentRouting(inp) {
|
|
|
4327
4449
|
}
|
|
4328
4450
|
|
|
4329
4451
|
// src/scripts/hooks/delegation_nudge_hook.ts
|
|
4330
|
-
var EXIT_ALLOW6 = 0;
|
|
4331
|
-
var EXIT_WARN3 = 2;
|
|
4332
4452
|
function _isObject6(v) {
|
|
4333
4453
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4334
4454
|
}
|
|
@@ -4508,28 +4628,28 @@ function main8() {
|
|
|
4508
4628
|
const raw = readHookStdin();
|
|
4509
4629
|
envelope = raw.trim() ? JSON.parse(raw) : {};
|
|
4510
4630
|
} catch {
|
|
4511
|
-
return
|
|
4631
|
+
return EXIT_ALLOW3;
|
|
4512
4632
|
}
|
|
4513
4633
|
const env = _isObject6(envelope) ? envelope : {};
|
|
4514
4634
|
const event = env["event"];
|
|
4515
4635
|
const slot = typeof event === "string" && event !== "" ? event : "user_prompt_submit";
|
|
4516
4636
|
if (slot !== "user_prompt_submit") {
|
|
4517
|
-
return
|
|
4637
|
+
return EXIT_ALLOW3;
|
|
4518
4638
|
}
|
|
4519
4639
|
const payload = _isObject6(env["payload"]) ? env["payload"] : env;
|
|
4520
4640
|
const prompt = _extractPrompt2(payload);
|
|
4521
4641
|
if (!prompt) {
|
|
4522
|
-
return
|
|
4642
|
+
return EXIT_ALLOW3;
|
|
4523
4643
|
}
|
|
4524
4644
|
if (isSyntheticPrompt(prompt)) {
|
|
4525
|
-
return
|
|
4645
|
+
return EXIT_ALLOW3;
|
|
4526
4646
|
}
|
|
4527
4647
|
const root = _workspaceRoot(env);
|
|
4528
4648
|
const platform = env["platform"];
|
|
4529
4649
|
const hostId = typeof platform === "string" && platform ? platform : null;
|
|
4530
4650
|
const result = classifyPrompt(prompt, root, hostId);
|
|
4531
4651
|
if (!result) {
|
|
4532
|
-
return
|
|
4652
|
+
return EXIT_ALLOW3;
|
|
4533
4653
|
}
|
|
4534
4654
|
process.stdout.write(
|
|
4535
4655
|
`${JSON.stringify({
|
|
@@ -4539,7 +4659,7 @@ function main8() {
|
|
|
4539
4659
|
})}
|
|
4540
4660
|
`
|
|
4541
4661
|
);
|
|
4542
|
-
return
|
|
4662
|
+
return EXIT_WARN2;
|
|
4543
4663
|
}
|
|
4544
4664
|
function _isCliEntry8() {
|
|
4545
4665
|
if (true) {
|
|
@@ -4553,8 +4673,8 @@ function _isCliEntry8() {
|
|
|
4553
4673
|
return true;
|
|
4554
4674
|
}
|
|
4555
4675
|
try {
|
|
4556
|
-
const here =
|
|
4557
|
-
const argv =
|
|
4676
|
+
const here = fs10.realpathSync(fileURLToPath8(import.meta.url));
|
|
4677
|
+
const argv = fs10.realpathSync(path9.resolve(process.argv[1]));
|
|
4558
4678
|
return here === argv;
|
|
4559
4679
|
} catch {
|
|
4560
4680
|
return false;
|
|
@@ -4951,13 +5071,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
|
|
|
4951
5071
|
return {
|
|
4952
5072
|
project: {
|
|
4953
5073
|
dir: projectRulesDir,
|
|
4954
|
-
present:
|
|
5074
|
+
present: fs11.existsSync(projectRulesDir),
|
|
4955
5075
|
files: p.files,
|
|
4956
5076
|
tokens: _tokens(p.chars)
|
|
4957
5077
|
},
|
|
4958
5078
|
global: {
|
|
4959
5079
|
dir: globalRulesDir,
|
|
4960
|
-
present:
|
|
5080
|
+
present: fs11.existsSync(globalRulesDir),
|
|
4961
5081
|
files: g.files,
|
|
4962
5082
|
tokens: _tokens(g.chars)
|
|
4963
5083
|
},
|
|
@@ -4968,10 +5088,10 @@ function storeKey(store) {
|
|
|
4968
5088
|
return createHash3("sha256").update(path10.resolve(store)).digest("hex").slice(0, 12);
|
|
4969
5089
|
}
|
|
4970
5090
|
function scanStore(store, limit) {
|
|
4971
|
-
const files =
|
|
5091
|
+
const files = fs11.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs11.statSync(path10.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
|
|
4972
5092
|
const per_session = [];
|
|
4973
5093
|
for (const f of files) {
|
|
4974
|
-
const lines =
|
|
5094
|
+
const lines = fs11.readFileSync(path10.join(store, f), "utf8").split("\n");
|
|
4975
5095
|
const r = scanSession(f.replace(/\.jsonl$/, "").slice(0, 8), lines);
|
|
4976
5096
|
if (r.assistant_turns > 0) {
|
|
4977
5097
|
per_session.push(r);
|
|
@@ -5032,8 +5152,8 @@ function scanStore(store, limit) {
|
|
|
5032
5152
|
};
|
|
5033
5153
|
}
|
|
5034
5154
|
function recordRate(file, scanned_at, rate) {
|
|
5035
|
-
|
|
5036
|
-
|
|
5155
|
+
fs11.mkdirSync(path10.dirname(file), { recursive: true });
|
|
5156
|
+
fs11.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
|
|
5037
5157
|
`, "utf8");
|
|
5038
5158
|
}
|
|
5039
5159
|
function render(report) {
|
|
@@ -5164,14 +5284,14 @@ function main9(argv) {
|
|
|
5164
5284
|
return 2;
|
|
5165
5285
|
}
|
|
5166
5286
|
const resolved = store ?? defaultStore(process.cwd());
|
|
5167
|
-
if (!
|
|
5287
|
+
if (!fs11.existsSync(resolved)) {
|
|
5168
5288
|
process.stderr.write(`conformance:behavior: no transcript store at ${resolved}
|
|
5169
5289
|
`);
|
|
5170
5290
|
return 1;
|
|
5171
5291
|
}
|
|
5172
5292
|
const report = scanStore(resolved, Number.isFinite(limit) && limit > 0 ? limit : 30);
|
|
5173
5293
|
if (out) {
|
|
5174
|
-
|
|
5294
|
+
fs11.writeFileSync(out, `${JSON.stringify(report, null, 2)}
|
|
5175
5295
|
`, "utf8");
|
|
5176
5296
|
}
|
|
5177
5297
|
if (record) {
|
|
@@ -5179,7 +5299,7 @@ function main9(argv) {
|
|
|
5179
5299
|
}
|
|
5180
5300
|
if (why !== null) {
|
|
5181
5301
|
const id = why;
|
|
5182
|
-
|
|
5302
|
+
fs11.writeSync(
|
|
5183
5303
|
1,
|
|
5184
5304
|
json ? `${JSON.stringify(
|
|
5185
5305
|
{
|
|
@@ -5199,7 +5319,7 @@ function main9(argv) {
|
|
|
5199
5319
|
);
|
|
5200
5320
|
return 0;
|
|
5201
5321
|
}
|
|
5202
|
-
|
|
5322
|
+
fs11.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
|
|
5203
5323
|
` : `${render(report)}
|
|
5204
5324
|
`);
|
|
5205
5325
|
return 0;
|
|
@@ -5216,8 +5336,8 @@ function _isCliEntry9() {
|
|
|
5216
5336
|
return true;
|
|
5217
5337
|
}
|
|
5218
5338
|
try {
|
|
5219
|
-
const here =
|
|
5220
|
-
const argv =
|
|
5339
|
+
const here = fs11.realpathSync(fileURLToPath9(import.meta.url));
|
|
5340
|
+
const argv = fs11.realpathSync(path10.resolve(process.argv[1]));
|
|
5221
5341
|
return here === argv;
|
|
5222
5342
|
} catch {
|
|
5223
5343
|
return false;
|
|
@@ -5228,7 +5348,7 @@ if (_isCliEntry9()) {
|
|
|
5228
5348
|
}
|
|
5229
5349
|
|
|
5230
5350
|
// src/scripts/report_carrier_divergence.ts
|
|
5231
|
-
import * as
|
|
5351
|
+
import * as fs12 from "node:fs";
|
|
5232
5352
|
import * as os3 from "node:os";
|
|
5233
5353
|
import * as path11 from "node:path";
|
|
5234
5354
|
import process2 from "node:process";
|
|
@@ -5240,7 +5360,7 @@ var PROJECTION_SOURCE = path11.join("dist", "agent-src", "rules");
|
|
|
5240
5360
|
var GLOBAL_RULES = path11.join(".claude", "rules");
|
|
5241
5361
|
function _listRules(dir) {
|
|
5242
5362
|
try {
|
|
5243
|
-
return
|
|
5363
|
+
return fs12.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
5244
5364
|
} catch {
|
|
5245
5365
|
return [];
|
|
5246
5366
|
}
|
|
@@ -5248,7 +5368,7 @@ function _listRules(dir) {
|
|
|
5248
5368
|
function _ruleType(file) {
|
|
5249
5369
|
let text;
|
|
5250
5370
|
try {
|
|
5251
|
-
text =
|
|
5371
|
+
text = fs12.readFileSync(file, "utf-8");
|
|
5252
5372
|
} catch {
|
|
5253
5373
|
return "";
|
|
5254
5374
|
}
|
|
@@ -5259,8 +5379,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
5259
5379
|
const out = {
|
|
5260
5380
|
projectDir,
|
|
5261
5381
|
globalDir,
|
|
5262
|
-
globalPresent:
|
|
5263
|
-
projectPresent:
|
|
5382
|
+
globalPresent: fs12.existsSync(globalDir),
|
|
5383
|
+
projectPresent: fs12.existsSync(projectDir),
|
|
5264
5384
|
shared: 0,
|
|
5265
5385
|
identical: [],
|
|
5266
5386
|
provenanceOnly: [],
|
|
@@ -5293,8 +5413,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
5293
5413
|
let projectCopy;
|
|
5294
5414
|
let globalCopy;
|
|
5295
5415
|
try {
|
|
5296
|
-
projectCopy =
|
|
5297
|
-
globalCopy =
|
|
5416
|
+
projectCopy = fs12.readFileSync(path11.join(projectDir, rule));
|
|
5417
|
+
globalCopy = fs12.readFileSync(path11.join(globalDir, rule));
|
|
5298
5418
|
verdict = comparePair(projectCopy, globalCopy);
|
|
5299
5419
|
} catch {
|
|
5300
5420
|
out.shared -= 1;
|
|
@@ -5481,7 +5601,7 @@ function _isCliEntry10() {
|
|
|
5481
5601
|
if (process2.argv[1] === void 0) return false;
|
|
5482
5602
|
if (import.meta.url === pathToFileURL10(path11.resolve(process2.argv[1])).href) return true;
|
|
5483
5603
|
try {
|
|
5484
|
-
return
|
|
5604
|
+
return fs12.realpathSync(_HERE2) === fs12.realpathSync(path11.resolve(process2.argv[1]));
|
|
5485
5605
|
} catch {
|
|
5486
5606
|
return false;
|
|
5487
5607
|
}
|
|
@@ -5491,7 +5611,7 @@ if (_isCliEntry10()) {
|
|
|
5491
5611
|
}
|
|
5492
5612
|
|
|
5493
5613
|
// src/scripts/report_skill_activation.ts
|
|
5494
|
-
import * as
|
|
5614
|
+
import * as fs13 from "node:fs";
|
|
5495
5615
|
import * as os4 from "node:os";
|
|
5496
5616
|
import * as path12 from "node:path";
|
|
5497
5617
|
import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL11 } from "node:url";
|
|
@@ -5507,25 +5627,25 @@ function censusSkills(root) {
|
|
|
5507
5627
|
withTriggerCorpus: [],
|
|
5508
5628
|
withDeterministicObligation: []
|
|
5509
5629
|
};
|
|
5510
|
-
if (!
|
|
5630
|
+
if (!fs13.existsSync(root)) {
|
|
5511
5631
|
return out;
|
|
5512
5632
|
}
|
|
5513
|
-
for (const entry of
|
|
5633
|
+
for (const entry of fs13.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
5514
5634
|
if (!entry.isDirectory()) {
|
|
5515
5635
|
continue;
|
|
5516
5636
|
}
|
|
5517
5637
|
const file = path12.join(root, entry.name, "SKILL.md");
|
|
5518
|
-
if (!
|
|
5638
|
+
if (!fs13.existsSync(file)) {
|
|
5519
5639
|
continue;
|
|
5520
5640
|
}
|
|
5521
5641
|
out.total += 1;
|
|
5522
|
-
const text =
|
|
5642
|
+
const text = fs13.readFileSync(file, "utf8");
|
|
5523
5643
|
const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
|
|
5524
5644
|
const fm = fmEnd === -1 ? "" : text.slice(0, fmEnd);
|
|
5525
5645
|
if (TRIGGER_KEYS.some((k) => new RegExp(`^${k}`, "m").test(fm))) {
|
|
5526
5646
|
out.withTriggerKey.push(entry.name);
|
|
5527
5647
|
}
|
|
5528
|
-
if (
|
|
5648
|
+
if (fs13.existsSync(path12.join(root, entry.name, "evals", "triggers.json"))) {
|
|
5529
5649
|
out.withTriggerCorpus.push(entry.name);
|
|
5530
5650
|
}
|
|
5531
5651
|
const body = fmEnd === -1 ? text : text.slice(fmEnd + 4);
|
|
@@ -5540,13 +5660,13 @@ function defaultStore2(cwd) {
|
|
|
5540
5660
|
}
|
|
5541
5661
|
function measureUsage(store, limit) {
|
|
5542
5662
|
const rep = { store, sessions: 0, assistantTurns: 0, invocations: 0, bySkill: {} };
|
|
5543
|
-
if (!
|
|
5663
|
+
if (!fs13.existsSync(store)) {
|
|
5544
5664
|
return rep;
|
|
5545
5665
|
}
|
|
5546
|
-
const files =
|
|
5666
|
+
const files = fs13.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path12.join(store, f)).sort((a, b) => fs13.statSync(b).mtimeMs - fs13.statSync(a).mtimeMs).slice(0, limit);
|
|
5547
5667
|
rep.sessions = files.length;
|
|
5548
5668
|
for (const file of files) {
|
|
5549
|
-
for (const line of
|
|
5669
|
+
for (const line of fs13.readFileSync(file, "utf8").split("\n")) {
|
|
5550
5670
|
if (!line.trim()) {
|
|
5551
5671
|
continue;
|
|
5552
5672
|
}
|
|
@@ -5624,10 +5744,10 @@ function buildRecord(census, usage, today) {
|
|
|
5624
5744
|
}
|
|
5625
5745
|
function readRecord(repoRoot) {
|
|
5626
5746
|
const file = path12.join(repoRoot, RECORD_PATH);
|
|
5627
|
-
if (!
|
|
5747
|
+
if (!fs13.existsSync(file)) {
|
|
5628
5748
|
return null;
|
|
5629
5749
|
}
|
|
5630
|
-
return JSON.parse(
|
|
5750
|
+
return JSON.parse(fs13.readFileSync(file, "utf8"));
|
|
5631
5751
|
}
|
|
5632
5752
|
function render3(census, usage, published = null) {
|
|
5633
5753
|
const lines = [];
|
|
@@ -5788,8 +5908,8 @@ function main11(argv) {
|
|
|
5788
5908
|
}
|
|
5789
5909
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5790
5910
|
const file = path12.join(root, RECORD_PATH);
|
|
5791
|
-
|
|
5792
|
-
|
|
5911
|
+
fs13.mkdirSync(path12.dirname(file), { recursive: true });
|
|
5912
|
+
fs13.writeFileSync(file, `${JSON.stringify(buildRecord(census, usage, today), null, 2)}
|
|
5793
5913
|
`);
|
|
5794
5914
|
process.stdout.write(`
|
|
5795
5915
|
wrote ${RECORD_PATH} (measured ${today})
|
|
@@ -5801,7 +5921,7 @@ if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL11(path12.res
|
|
|
5801
5921
|
}
|
|
5802
5922
|
|
|
5803
5923
|
// src/scripts/report_skill_obligation_violations.ts
|
|
5804
|
-
import * as
|
|
5924
|
+
import * as fs14 from "node:fs";
|
|
5805
5925
|
import * as os5 from "node:os";
|
|
5806
5926
|
import * as path13 from "node:path";
|
|
5807
5927
|
import process3 from "node:process";
|
|
@@ -5859,7 +5979,7 @@ function extractObligations(repoRoot) {
|
|
|
5859
5979
|
for (const skill of census.withDeterministicObligation) {
|
|
5860
5980
|
let text;
|
|
5861
5981
|
try {
|
|
5862
|
-
text =
|
|
5982
|
+
text = fs14.readFileSync(path13.join(skillsRoot, skill, "SKILL.md"), "utf-8");
|
|
5863
5983
|
} catch {
|
|
5864
5984
|
continue;
|
|
5865
5985
|
}
|
|
@@ -5988,13 +6108,13 @@ function scanStore2(repoRoot, store, limit) {
|
|
|
5988
6108
|
};
|
|
5989
6109
|
let files;
|
|
5990
6110
|
try {
|
|
5991
|
-
files =
|
|
6111
|
+
files = fs14.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path13.join(store, f), m: fs14.statSync(path13.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
|
|
5992
6112
|
} catch {
|
|
5993
6113
|
return report;
|
|
5994
6114
|
}
|
|
5995
6115
|
report.sessions = files.length;
|
|
5996
6116
|
for (const file of files) {
|
|
5997
|
-
const lines =
|
|
6117
|
+
const lines = fs14.readFileSync(file, "utf-8").split("\n");
|
|
5998
6118
|
const id = path13.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
|
|
5999
6119
|
const entries = _parseSession(lines);
|
|
6000
6120
|
if (loadedSkillsFrom(entries).size > 0) report.sessionsWithASkill += 1;
|
|
@@ -6122,7 +6242,7 @@ function _isCliEntry11() {
|
|
|
6122
6242
|
if (process3.argv[1] === void 0) return false;
|
|
6123
6243
|
if (import.meta.url === pathToFileURL12(path13.resolve(process3.argv[1])).href) return true;
|
|
6124
6244
|
try {
|
|
6125
|
-
return
|
|
6245
|
+
return fs14.realpathSync(_HERE4) === fs14.realpathSync(path13.resolve(process3.argv[1]));
|
|
6126
6246
|
} catch {
|
|
6127
6247
|
return false;
|
|
6128
6248
|
}
|
|
@@ -6146,7 +6266,7 @@ function buildFunnel(opts = {}) {
|
|
|
6146
6266
|
divergence: compareCarriers(projectRulesDir, globalRulesDir, sourceDir),
|
|
6147
6267
|
skills: censusSkills(path14.join(repoRoot, SKILLS_ROOT)),
|
|
6148
6268
|
usage: measureUsage(store, limit),
|
|
6149
|
-
storePresent:
|
|
6269
|
+
storePresent: fs15.existsSync(store),
|
|
6150
6270
|
sk2: scanStore2(repoRoot, store, limit)
|
|
6151
6271
|
};
|
|
6152
6272
|
}
|
|
@@ -6321,7 +6441,7 @@ function _isCliEntry12() {
|
|
|
6321
6441
|
if (process4.argv[1] === void 0) return false;
|
|
6322
6442
|
if (import.meta.url === pathToFileURL13(path14.resolve(process4.argv[1])).href) return true;
|
|
6323
6443
|
try {
|
|
6324
|
-
return
|
|
6444
|
+
return fs15.realpathSync(_HERE5) === fs15.realpathSync(path14.resolve(process4.argv[1]));
|
|
6325
6445
|
} catch {
|
|
6326
6446
|
return false;
|
|
6327
6447
|
}
|
|
@@ -6368,10 +6488,10 @@ function installLogPath() {
|
|
|
6368
6488
|
return path15.join(os7.homedir(), ".event4u", "agent-config", "install-log.jsonl");
|
|
6369
6489
|
}
|
|
6370
6490
|
function _installLogExpected(projectRoot) {
|
|
6371
|
-
return
|
|
6491
|
+
return fs16.existsSync(manifest_path(projectRoot));
|
|
6372
6492
|
}
|
|
6373
6493
|
function _check_txlog_clean(logPath = installLogPath(), projectRoot = null) {
|
|
6374
|
-
const entries =
|
|
6494
|
+
const entries = fs16.existsSync(logPath) ? readRecentEntries(logPath) : [];
|
|
6375
6495
|
if (entries.length === 0) {
|
|
6376
6496
|
if (projectRoot !== null && _installLogExpected(projectRoot)) {
|
|
6377
6497
|
return {
|
|
@@ -6409,7 +6529,7 @@ function routeTargetPaths(target) {
|
|
|
6409
6529
|
}
|
|
6410
6530
|
function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
6411
6531
|
const routerPath = path15.join(packageRoot, "dist", "router.json");
|
|
6412
|
-
if (!
|
|
6532
|
+
if (!fs16.existsSync(routerPath)) {
|
|
6413
6533
|
return {
|
|
6414
6534
|
id: "router-pointers",
|
|
6415
6535
|
status: "fail",
|
|
@@ -6419,7 +6539,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6419
6539
|
}
|
|
6420
6540
|
let router;
|
|
6421
6541
|
try {
|
|
6422
|
-
router = JSON.parse(
|
|
6542
|
+
router = JSON.parse(fs16.readFileSync(routerPath, "utf8"));
|
|
6423
6543
|
} catch (exc) {
|
|
6424
6544
|
return {
|
|
6425
6545
|
id: "router-pointers",
|
|
@@ -6434,7 +6554,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6434
6554
|
let targets = 0;
|
|
6435
6555
|
for (const kernelId of router["kernel"] ?? []) {
|
|
6436
6556
|
ids += 1;
|
|
6437
|
-
if (!
|
|
6557
|
+
if (!fs16.existsSync(path15.join(rulesDir, `${kernelId}.md`))) {
|
|
6438
6558
|
broken.push(`kernel:${kernelId} \u2192 rules/${kernelId}.md missing`);
|
|
6439
6559
|
}
|
|
6440
6560
|
}
|
|
@@ -6442,7 +6562,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6442
6562
|
for (const entry of router[tier] ?? []) {
|
|
6443
6563
|
const ruleId = String(entry["id"] ?? "");
|
|
6444
6564
|
ids += 1;
|
|
6445
|
-
if (!
|
|
6565
|
+
if (!fs16.existsSync(path15.join(rulesDir, `${ruleId}.md`))) {
|
|
6446
6566
|
broken.push(`${tier}:${ruleId} \u2192 rules/${ruleId}.md missing`);
|
|
6447
6567
|
}
|
|
6448
6568
|
for (const target of entry["routes_to"] ?? []) {
|
|
@@ -6452,7 +6572,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6452
6572
|
broken.push(`${ruleId} \u2192 unparseable routes_to ${JSON.stringify(target)}`);
|
|
6453
6573
|
continue;
|
|
6454
6574
|
}
|
|
6455
|
-
if (!candidates.some((rel) =>
|
|
6575
|
+
if (!candidates.some((rel) => fs16.existsSync(path15.join(packageRoot, rel)))) {
|
|
6456
6576
|
broken.push(`${ruleId} \u2192 ${target} (${candidates[0]} missing)`);
|
|
6457
6577
|
}
|
|
6458
6578
|
}
|
|
@@ -6486,7 +6606,7 @@ function detectSmokePlatform(projectRoot) {
|
|
|
6486
6606
|
function dispatcherRunner(packageRoot) {
|
|
6487
6607
|
const localTsx = path15.join(packageRoot, "node_modules", ".bin", "tsx");
|
|
6488
6608
|
const script = path15.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
|
|
6489
|
-
if (
|
|
6609
|
+
if (fs16.existsSync(localTsx)) {
|
|
6490
6610
|
return [localTsx, script];
|
|
6491
6611
|
}
|
|
6492
6612
|
return ["npx", "tsx", script];
|
|
@@ -6537,11 +6657,11 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
|
|
|
6537
6657
|
}
|
|
6538
6658
|
function readSettingsMode(projectRoot) {
|
|
6539
6659
|
const settingsPath = path15.join(projectRoot, ".agent-settings.yml");
|
|
6540
|
-
if (!
|
|
6660
|
+
if (!fs16.existsSync(settingsPath)) {
|
|
6541
6661
|
return "eager-all";
|
|
6542
6662
|
}
|
|
6543
6663
|
try {
|
|
6544
|
-
const parsed = YAML.parse(
|
|
6664
|
+
const parsed = YAML.parse(fs16.readFileSync(settingsPath, "utf8"));
|
|
6545
6665
|
const lean = parsed?.["lean_projection"] ?? null;
|
|
6546
6666
|
const mode = lean?.["mode"];
|
|
6547
6667
|
return typeof mode === "string" && mode.length > 0 ? mode : "eager-all";
|
|
@@ -6552,7 +6672,7 @@ function readSettingsMode(projectRoot) {
|
|
|
6552
6672
|
function kernelIdsFromRouter(packageRoot) {
|
|
6553
6673
|
try {
|
|
6554
6674
|
const router = JSON.parse(
|
|
6555
|
-
|
|
6675
|
+
fs16.readFileSync(path15.join(packageRoot, "dist", "router.json"), "utf8")
|
|
6556
6676
|
);
|
|
6557
6677
|
return new Set((router["kernel"] ?? []).map(String));
|
|
6558
6678
|
} catch {
|
|
@@ -6570,7 +6690,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
6570
6690
|
};
|
|
6571
6691
|
}
|
|
6572
6692
|
const rulesDir = projectedRulesDir ?? path15.join(projectRoot, ".augment", "rules");
|
|
6573
|
-
if (!
|
|
6693
|
+
if (!fs16.existsSync(rulesDir)) {
|
|
6574
6694
|
return {
|
|
6575
6695
|
id: "lean-projection",
|
|
6576
6696
|
status: "skipped",
|
|
@@ -6579,7 +6699,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
6579
6699
|
};
|
|
6580
6700
|
}
|
|
6581
6701
|
const kernel = kernelIdsFromRouter(packageRoot);
|
|
6582
|
-
const files =
|
|
6702
|
+
const files = fs16.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
|
|
6583
6703
|
const nonKernel = files.filter((f) => !kernel.has(f.replace(/\.md$/, "")));
|
|
6584
6704
|
if (nonKernel.length === 0) {
|
|
6585
6705
|
return {
|
|
@@ -6593,7 +6713,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
6593
6713
|
for (const f of nonKernel) {
|
|
6594
6714
|
let text = "";
|
|
6595
6715
|
try {
|
|
6596
|
-
text =
|
|
6716
|
+
text = fs16.readFileSync(path15.join(rulesDir, f), "utf8");
|
|
6597
6717
|
} catch {
|
|
6598
6718
|
continue;
|
|
6599
6719
|
}
|
|
@@ -6625,7 +6745,7 @@ function _check_host_manifest(projectRoot) {
|
|
|
6625
6745
|
const detected = Object.entries(detectToolPresence(projectRoot)).filter(([, v]) => v === true).map(([k]) => k);
|
|
6626
6746
|
const detectedNote = detected.length > 0 ? `detected surfaces: ${detected.join(", ")}` : "no surfaces detected";
|
|
6627
6747
|
const settingsPath = path15.join(projectRoot, ".agent-settings.yml");
|
|
6628
|
-
if (!
|
|
6748
|
+
if (!fs16.existsSync(settingsPath)) {
|
|
6629
6749
|
return {
|
|
6630
6750
|
id: "host-manifest",
|
|
6631
6751
|
status: "ok",
|
|
@@ -6635,7 +6755,7 @@ function _check_host_manifest(projectRoot) {
|
|
|
6635
6755
|
}
|
|
6636
6756
|
let parsed = null;
|
|
6637
6757
|
try {
|
|
6638
|
-
parsed = YAML.parse(
|
|
6758
|
+
parsed = YAML.parse(fs16.readFileSync(settingsPath, "utf8"));
|
|
6639
6759
|
} catch (exc) {
|
|
6640
6760
|
return {
|
|
6641
6761
|
id: "host-manifest",
|
|
@@ -6814,8 +6934,8 @@ function _isCliEntry13() {
|
|
|
6814
6934
|
return true;
|
|
6815
6935
|
}
|
|
6816
6936
|
try {
|
|
6817
|
-
const here =
|
|
6818
|
-
const argv =
|
|
6937
|
+
const here = fs16.realpathSync(fileURLToPath14(import.meta.url));
|
|
6938
|
+
const argv = fs16.realpathSync(path15.resolve(process5.argv[1]));
|
|
6819
6939
|
return here === argv;
|
|
6820
6940
|
} catch {
|
|
6821
6941
|
return false;
|