@event4u/agent-config 9.36.0 → 10.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +117 -2
- package/agents/templates/.ai-council.yml.example +31 -51
- package/dist/agent-src/commands/analyze/conformance.md +15 -4
- package/dist/agent-src/rules/session-canary.md +28 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +17 -0
- package/dist/agent-src/skills/adr-create/SKILL.md +5 -0
- package/dist/agent-src/skills/check-refs/SKILL.md +5 -0
- package/dist/agent-src/skills/lint-skills/SKILL.md +5 -0
- package/dist/agent-src/skills/md-language-check/SKILL.md +5 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/{chunk-SFGARMAB.js → chunk-2H5SE7DU.js} +24 -4
- package/dist/cli-delegate/{chunk-U7CMAIOC.js → chunk-3JUTT26I.js} +1 -1
- package/dist/cli-delegate/{chunk-JP53LNVT.js → chunk-HJSKTMIF.js} +6 -14
- package/dist/cli-delegate/{chunk-QEEB4LHO.js → chunk-LRXJK6GN.js} +1 -1
- package/dist/cli-delegate/{chunk-NR7HSN3P.js → chunk-NX5AXSLD.js} +3 -3
- package/dist/cli-delegate/{chunk-AG47LAGQ.js → chunk-TVGXH7VA.js} +1 -1
- package/dist/cli-delegate/{chunk-55RI5UXK.js → chunk-XT2YTJCZ.js} +22 -18
- package/dist/cli-delegate/{chunk-JEJABGKB.js → chunk-XTC5ARB6.js} +16 -12
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +1037 -196
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +3 -3
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +1 -1
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +8 -8
- 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 +125 -89
- package/dist/install/install.mjs +22 -26
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1579 -616
- package/dist/scripts/_lib/agent_settings.js +39 -25
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/scripts/condense.js +15 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/routes/wizard.js +23 -3
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/server/schemas/settings.js +6 -6
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/contracts/settings-classes.md +14 -9
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +17 -0
- package/src/config/agent-settings.template.yml +6 -36
- package/src/config/ci-local-parity.yml +44 -0
- package/src/scripts/_lib/agent_settings.ts +39 -25
- package/src/scripts/_lib/environment_detector.ts +52 -1
- package/src/scripts/_lib/untrusted_content.test.ts +175 -0
- package/src/scripts/_lib/untrusted_content.ts +184 -0
- package/src/scripts/ai_council/config.ts +62 -24
- package/src/scripts/before_complete_hook.ts +39 -0
- package/src/scripts/check_bridge_derivation.ts +89 -3
- package/src/scripts/check_ci_local_parity.ts +74 -0
- package/src/scripts/check_references.ts +15 -1
- package/src/scripts/condense.ts +16 -2
- package/src/scripts/conformance_scan.ts +200 -9
- package/src/scripts/council_cli.ts +95 -3
- package/src/scripts/hook_manifest.yaml +12 -6
- package/src/scripts/hooks/block_no_verify.ts +82 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +138 -105
- package/src/scripts/probe_promissory_closing.ts +180 -0
- package/src/scripts/probe_session_canary.ts +180 -0
- package/src/scripts/schemas/skill.schema.json +42 -0
- package/src/scripts/skill_linter.ts +57 -2
- package/src/server/schemas/settings.ts +6 -14
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
frontmatterPaths,
|
|
20
20
|
proseEqual,
|
|
21
21
|
splitFrontmatter
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-2H5SE7DU.js";
|
|
23
23
|
import "./chunk-FTAISGDQ.js";
|
|
24
24
|
import {
|
|
25
25
|
atomic_write_json,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
is_replay_mode
|
|
28
28
|
} from "./chunk-EJITJBJX.js";
|
|
29
29
|
import "./chunk-Q26HT4QT.js";
|
|
30
|
-
import "./chunk-
|
|
30
|
+
import "./chunk-XT2YTJCZ.js";
|
|
31
31
|
import "./chunk-PZWRPRWP.js";
|
|
32
32
|
import "./chunk-M2DPGFZ4.js";
|
|
33
33
|
import {
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
} from "./chunk-63WSP6YZ.js";
|
|
38
38
|
import "./chunk-GRRPEPRN.js";
|
|
39
39
|
import "./chunk-NXFWFAYV.js";
|
|
40
|
-
import "./chunk-
|
|
40
|
+
import "./chunk-XTC5ARB6.js";
|
|
41
41
|
import "./chunk-NDJLNC6Q.js";
|
|
42
42
|
import {
|
|
43
43
|
detectToolPresence
|
|
@@ -54,12 +54,12 @@ import {
|
|
|
54
54
|
|
|
55
55
|
// src/scripts/_cli/cmd_conformance.ts
|
|
56
56
|
var YAML = __toESM(require_dist(), 1);
|
|
57
|
-
import * as
|
|
58
|
-
import * as
|
|
59
|
-
import * as
|
|
57
|
+
import * as fs13 from "node:fs";
|
|
58
|
+
import * as os7 from "node:os";
|
|
59
|
+
import * as path13 from "node:path";
|
|
60
60
|
import process5 from "node:process";
|
|
61
|
-
import { spawnSync } from "node:child_process";
|
|
62
|
-
import { fileURLToPath as
|
|
61
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
62
|
+
import { fileURLToPath as fileURLToPath13, pathToFileURL as pathToFileURL13 } from "node:url";
|
|
63
63
|
|
|
64
64
|
// src/install/txlog.ts
|
|
65
65
|
import { createReadStream, createWriteStream, existsSync, readFileSync, renameSync, statSync, unlinkSync } from "node:fs";
|
|
@@ -120,18 +120,18 @@ function routeTargetPathsPosix(target) {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// src/scripts/report_conformance_funnel.ts
|
|
123
|
-
import * as
|
|
124
|
-
import * as
|
|
125
|
-
import * as
|
|
123
|
+
import * as fs12 from "node:fs";
|
|
124
|
+
import * as os6 from "node:os";
|
|
125
|
+
import * as path12 from "node:path";
|
|
126
126
|
import process4 from "node:process";
|
|
127
|
-
import { fileURLToPath as
|
|
127
|
+
import { fileURLToPath as fileURLToPath12, pathToFileURL as pathToFileURL12 } from "node:url";
|
|
128
128
|
|
|
129
129
|
// src/scripts/conformance_scan.ts
|
|
130
|
-
import { createHash } from "node:crypto";
|
|
131
|
-
import
|
|
132
|
-
import
|
|
133
|
-
import
|
|
134
|
-
import { fileURLToPath as
|
|
130
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
131
|
+
import fs8 from "node:fs";
|
|
132
|
+
import os2 from "node:os";
|
|
133
|
+
import path8 from "node:path";
|
|
134
|
+
import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL8 } from "node:url";
|
|
135
135
|
|
|
136
136
|
// src/scripts/_lib/transcript_entry.ts
|
|
137
137
|
function _isObject(v) {
|
|
@@ -1037,6 +1037,14 @@ function _empty_state() {
|
|
|
1037
1037
|
verified_this_turn: false,
|
|
1038
1038
|
ci_saw_pending: false,
|
|
1039
1039
|
nonevidence_this_turn: 0,
|
|
1040
|
+
// Round 7 § Phase 1 — the last CI read, SESSION-scoped on purpose. Every
|
|
1041
|
+
// other counter here is turn-scoped because a stale POSITIVE would wrongly
|
|
1042
|
+
// vouch ("I verified"). This one is a NEGATIVE ("CI was not settled"), and a
|
|
1043
|
+
// stale negative only ever refuses more often — so surviving the turn
|
|
1044
|
+
// boundary preserves the freshness invariant rather than bypassing it. It
|
|
1045
|
+
// has to survive it: the measured failure is a completion claim in a LATER
|
|
1046
|
+
// turn than the poll it rests on.
|
|
1047
|
+
ci_last: null,
|
|
1040
1048
|
checked_at: _now()
|
|
1041
1049
|
};
|
|
1042
1050
|
}
|
|
@@ -1164,6 +1172,7 @@ function _update(state, event, envelope) {
|
|
|
1164
1172
|
if (session_id && session_id !== state["session_id"]) {
|
|
1165
1173
|
state["session_id"] = session_id;
|
|
1166
1174
|
state["verifications_this_session"] = 0;
|
|
1175
|
+
state["ci_last"] = null;
|
|
1167
1176
|
state = _reset_turn(state, session_id);
|
|
1168
1177
|
}
|
|
1169
1178
|
let payload = envelope["payload"];
|
|
@@ -1189,6 +1198,12 @@ function _update(state, event, envelope) {
|
|
|
1189
1198
|
} else {
|
|
1190
1199
|
counts = false;
|
|
1191
1200
|
}
|
|
1201
|
+
state["ci_last"] = {
|
|
1202
|
+
at: _now(),
|
|
1203
|
+
command: cmd.slice(0, 512),
|
|
1204
|
+
pending,
|
|
1205
|
+
settled: pending === 0 && counts === true
|
|
1206
|
+
};
|
|
1192
1207
|
}
|
|
1193
1208
|
state["last_verification"] = {
|
|
1194
1209
|
command: cmd.slice(0, 512),
|
|
@@ -1302,15 +1317,758 @@ if (isCliEntry) {
|
|
|
1302
1317
|
process.exit(main4(process.argv.slice(2)));
|
|
1303
1318
|
}
|
|
1304
1319
|
|
|
1305
|
-
// src/scripts/hooks/
|
|
1306
|
-
import
|
|
1307
|
-
import
|
|
1308
|
-
import
|
|
1320
|
+
// src/scripts/hooks/turn_end_gate_hook.ts
|
|
1321
|
+
import * as crypto2 from "node:crypto";
|
|
1322
|
+
import * as fs6 from "node:fs";
|
|
1323
|
+
import * as path6 from "node:path";
|
|
1324
|
+
import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
|
|
1325
|
+
|
|
1326
|
+
// src/scripts/hooks/end_review_nudge_hook.ts
|
|
1327
|
+
import { spawnSync } from "node:child_process";
|
|
1328
|
+
import * as crypto from "node:crypto";
|
|
1329
|
+
import * as fs5 from "node:fs";
|
|
1330
|
+
import * as os from "node:os";
|
|
1331
|
+
import * as path5 from "node:path";
|
|
1309
1332
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
|
|
1310
|
-
|
|
1333
|
+
|
|
1334
|
+
// src/scripts/_lib/review_skipped_record.ts
|
|
1335
|
+
function isNonNegInt(n) {
|
|
1336
|
+
return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
|
|
1337
|
+
}
|
|
1338
|
+
var HIGH_RISK_DIFF_LINES = 200;
|
|
1339
|
+
function buildReviewSkippedLine(input) {
|
|
1340
|
+
const errors = [];
|
|
1341
|
+
if (!isNonNegInt(input.diff_lines)) {
|
|
1342
|
+
errors.push("diff_lines must be a non-negative integer");
|
|
1343
|
+
}
|
|
1344
|
+
if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
|
|
1345
|
+
errors.push('mutation_measure must be "exact" or "capped_approximation"');
|
|
1346
|
+
}
|
|
1347
|
+
if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
|
|
1348
|
+
if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
|
|
1349
|
+
if (errors.length) return { line: null, errors };
|
|
1350
|
+
const line = {
|
|
1351
|
+
schema_version: 1,
|
|
1352
|
+
id: input.id,
|
|
1353
|
+
ts: input.ts,
|
|
1354
|
+
work_id: input.work_id ?? `review-skipped-${input.ts}`,
|
|
1355
|
+
phase: "report",
|
|
1356
|
+
outcome: "skipped",
|
|
1357
|
+
confidence_band: "medium",
|
|
1358
|
+
risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
|
|
1359
|
+
memory: { asks: 0, hits: 0 },
|
|
1360
|
+
verify: { claims: 0, first_try_passes: 0 },
|
|
1361
|
+
rules_applied: ["delegation-policy"],
|
|
1362
|
+
persona: null,
|
|
1363
|
+
input_kind: "prompt",
|
|
1364
|
+
type: "note",
|
|
1365
|
+
review_skipped: { diff_lines: input.diff_lines, mutation_measure: input.mutation_measure }
|
|
1366
|
+
};
|
|
1367
|
+
return { line, errors: [] };
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// src/scripts/hooks/envelope.ts
|
|
1371
|
+
var ENVELOPE_KEYS = [
|
|
1372
|
+
"schema_version",
|
|
1373
|
+
"platform",
|
|
1374
|
+
"event",
|
|
1375
|
+
"payload"
|
|
1376
|
+
];
|
|
1377
|
+
function _isObject5(obj) {
|
|
1378
|
+
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
|
|
1379
|
+
}
|
|
1380
|
+
function looks_like_envelope(obj) {
|
|
1381
|
+
if (!_isObject5(obj)) {
|
|
1382
|
+
return false;
|
|
1383
|
+
}
|
|
1384
|
+
return ENVELOPE_KEYS.every((key) => key in obj);
|
|
1385
|
+
}
|
|
1386
|
+
function unwrap(stdin_text, default_platform = "generic") {
|
|
1387
|
+
const text = (stdin_text ?? "").trim();
|
|
1388
|
+
if (!text) {
|
|
1389
|
+
return [{}, {}, default_platform];
|
|
1390
|
+
}
|
|
1391
|
+
let decoded;
|
|
1392
|
+
try {
|
|
1393
|
+
decoded = JSON.parse(text);
|
|
1394
|
+
} catch {
|
|
1395
|
+
return [{}, {}, default_platform];
|
|
1396
|
+
}
|
|
1397
|
+
if (looks_like_envelope(decoded)) {
|
|
1398
|
+
const env = decoded;
|
|
1399
|
+
let payload2 = env["payload"];
|
|
1400
|
+
if (!payload2 || !_isObject5(payload2)) {
|
|
1401
|
+
payload2 = {};
|
|
1402
|
+
}
|
|
1403
|
+
const platRaw = env["platform"];
|
|
1404
|
+
const platform = String(platRaw || default_platform);
|
|
1405
|
+
return [env, payload2, platform];
|
|
1406
|
+
}
|
|
1407
|
+
const payload = _isObject5(decoded) ? decoded : {};
|
|
1408
|
+
return [
|
|
1409
|
+
{
|
|
1410
|
+
schema_version: 1,
|
|
1411
|
+
platform: default_platform,
|
|
1412
|
+
event: "",
|
|
1413
|
+
native_event: "",
|
|
1414
|
+
session_id: "",
|
|
1415
|
+
workspace_root: "",
|
|
1416
|
+
payload,
|
|
1417
|
+
settings: {}
|
|
1418
|
+
},
|
|
1419
|
+
payload,
|
|
1420
|
+
default_platform
|
|
1421
|
+
];
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// src/scripts/hooks/end_review_nudge_hook.ts
|
|
1425
|
+
var MUTATION_LINE_THRESHOLD = 50;
|
|
1426
|
+
var UNTRACKED_FILE_CAP = 20;
|
|
1427
|
+
var TRANSCRIPT_SCAN_MAX_BYTES = 50 * 1024 * 1024;
|
|
1428
|
+
function isSafeTranscriptPath(rawPath, opts = {}) {
|
|
1429
|
+
if (!rawPath || !/\.jsonl$/i.test(rawPath)) return false;
|
|
1430
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
1431
|
+
const maxBytes = opts.maxBytes ?? TRANSCRIPT_SCAN_MAX_BYTES;
|
|
1432
|
+
try {
|
|
1433
|
+
const realHome = fs5.realpathSync(homeDir);
|
|
1434
|
+
const realPath = fs5.realpathSync(rawPath);
|
|
1435
|
+
const relative2 = path5.relative(realHome, realPath);
|
|
1436
|
+
if (relative2 === "" || relative2.startsWith("..") || path5.isAbsolute(relative2)) {
|
|
1437
|
+
return false;
|
|
1438
|
+
}
|
|
1439
|
+
const { size } = fs5.statSync(realPath);
|
|
1440
|
+
return size <= maxBytes;
|
|
1441
|
+
} catch {
|
|
1442
|
+
return false;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
var EXIT_WARN2 = 2;
|
|
1446
|
+
function parseNumstat(raw) {
|
|
1447
|
+
const rows = [];
|
|
1448
|
+
for (const line of raw.split("\n")) {
|
|
1449
|
+
const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
|
|
1450
|
+
if (!m) continue;
|
|
1451
|
+
const addedStr = m[1];
|
|
1452
|
+
const deletedStr = m[2];
|
|
1453
|
+
rows.push({
|
|
1454
|
+
added: addedStr === "-" ? 0 : Number(addedStr),
|
|
1455
|
+
deleted: deletedStr === "-" ? 0 : Number(deletedStr),
|
|
1456
|
+
path: m[3]
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
return rows;
|
|
1460
|
+
}
|
|
1461
|
+
var DOC_PATH_RE = /\.md$/i;
|
|
1462
|
+
function isDocPath(p) {
|
|
1463
|
+
return DOC_PATH_RE.test(p);
|
|
1464
|
+
}
|
|
1465
|
+
function nonDocMutatedLines(rows) {
|
|
1466
|
+
return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
|
|
1467
|
+
}
|
|
1468
|
+
function gitNumstatRows(cwd) {
|
|
1469
|
+
try {
|
|
1470
|
+
const r = spawnSync("git", ["diff", "--numstat", "HEAD"], {
|
|
1471
|
+
cwd,
|
|
1472
|
+
encoding: "utf8",
|
|
1473
|
+
maxBuffer: 16 * 1024 * 1024
|
|
1474
|
+
});
|
|
1475
|
+
if (r.status !== 0) return [];
|
|
1476
|
+
return parseNumstat(r.stdout ?? "");
|
|
1477
|
+
} catch {
|
|
1478
|
+
return [];
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
function untrackedNonDocFiles(cwd) {
|
|
1482
|
+
try {
|
|
1483
|
+
const r = spawnSync("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
1484
|
+
cwd,
|
|
1485
|
+
encoding: "utf8",
|
|
1486
|
+
maxBuffer: 16 * 1024 * 1024
|
|
1487
|
+
});
|
|
1488
|
+
if (r.status !== 0) return [];
|
|
1489
|
+
return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
|
|
1490
|
+
} catch {
|
|
1491
|
+
return [];
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
function untrackedFileLineCount(cwd, relPath) {
|
|
1495
|
+
try {
|
|
1496
|
+
const r = spawnSync("git", ["diff", "--numstat", "--no-index", "/dev/null", relPath], {
|
|
1497
|
+
cwd,
|
|
1498
|
+
encoding: "utf8",
|
|
1499
|
+
maxBuffer: 16 * 1024 * 1024
|
|
1500
|
+
});
|
|
1501
|
+
return parseNumstat(r.stdout ?? "").reduce((sum, row) => sum + row.added + row.deleted, 0);
|
|
1502
|
+
} catch {
|
|
1503
|
+
return 0;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
function totalNonDocMutatedLinesWithMeasure(cwd) {
|
|
1507
|
+
const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
|
|
1508
|
+
const untracked = untrackedNonDocFiles(cwd);
|
|
1509
|
+
if (untracked.length > UNTRACKED_FILE_CAP) {
|
|
1510
|
+
return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
|
|
1511
|
+
}
|
|
1512
|
+
const untrackedLines = untracked.reduce(
|
|
1513
|
+
(sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
|
|
1514
|
+
0
|
|
1515
|
+
);
|
|
1516
|
+
return { lines: trackedLines + untrackedLines, measure: "exact" };
|
|
1517
|
+
}
|
|
1518
|
+
function isObject(v) {
|
|
1519
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1520
|
+
}
|
|
1521
|
+
function str(v) {
|
|
1522
|
+
return typeof v === "string" ? v : "";
|
|
1523
|
+
}
|
|
1524
|
+
var DISPATCH_TOOL_RE = /^(agent|task)$/i;
|
|
1525
|
+
var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
|
|
1526
|
+
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;
|
|
1527
|
+
var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
|
|
1528
|
+
function scanTranscriptForReviewer(transcriptText) {
|
|
1529
|
+
for (const raw of transcriptText.split("\n")) {
|
|
1530
|
+
const line = raw.trim();
|
|
1531
|
+
if (!line) continue;
|
|
1532
|
+
let obj;
|
|
1533
|
+
try {
|
|
1534
|
+
obj = JSON.parse(line);
|
|
1535
|
+
} catch {
|
|
1536
|
+
continue;
|
|
1537
|
+
}
|
|
1538
|
+
if (!isObject(obj) || obj["type"] !== "assistant") continue;
|
|
1539
|
+
const msg = obj["message"];
|
|
1540
|
+
if (!isObject(msg)) continue;
|
|
1541
|
+
const content = msg["content"];
|
|
1542
|
+
if (!Array.isArray(content)) continue;
|
|
1543
|
+
for (const blk of content) {
|
|
1544
|
+
if (!isObject(blk) || blk["type"] !== "tool_use") continue;
|
|
1545
|
+
const name = str(blk["name"]);
|
|
1546
|
+
const input = blk["input"];
|
|
1547
|
+
const inputObj = isObject(input) ? input : {};
|
|
1548
|
+
if (DISPATCH_TOOL_RE.test(name)) {
|
|
1549
|
+
const label = [
|
|
1550
|
+
str(inputObj["description"]),
|
|
1551
|
+
str(inputObj["subagent_type"])
|
|
1552
|
+
].filter(Boolean).join(" ");
|
|
1553
|
+
if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
|
|
1554
|
+
const prompt = str(inputObj["prompt"]);
|
|
1555
|
+
if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
if (/^skill$/i.test(name)) {
|
|
1559
|
+
const skillName = str(inputObj["skill"]) || str(inputObj["name"]) || str(inputObj["command"]);
|
|
1560
|
+
if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1563
|
+
if (REVIEW_SKILL_NAME_RE.test(name)) return true;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
return false;
|
|
1567
|
+
}
|
|
1568
|
+
function buildAdvisoryLine(diffLines) {
|
|
1569
|
+
return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
|
|
1570
|
+
}
|
|
1571
|
+
function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure) {
|
|
1572
|
+
if (is_replay_mode()) return;
|
|
1573
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1574
|
+
const { line, errors } = buildReviewSkippedLine({
|
|
1575
|
+
diff_lines: diffLines,
|
|
1576
|
+
mutation_measure: mutationMeasure,
|
|
1577
|
+
ts,
|
|
1578
|
+
id: crypto.randomUUID()
|
|
1579
|
+
});
|
|
1580
|
+
if (errors.length || line === null) return;
|
|
1581
|
+
try {
|
|
1582
|
+
const dir = path5.join(workspaceRoot, "agents", "runtime", "state", "audit");
|
|
1583
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1584
|
+
const file = path5.join(dir, `${ts.slice(0, 7)}.jsonl`);
|
|
1585
|
+
fs5.appendFileSync(file, `${JSON.stringify(line)}
|
|
1586
|
+
`, "utf8");
|
|
1587
|
+
} catch {
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
function sessionStateDir(workspaceRoot) {
|
|
1591
|
+
return path5.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
|
|
1592
|
+
}
|
|
1593
|
+
function sessionStateFile(workspaceRoot, sessionKey) {
|
|
1594
|
+
return path5.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
1595
|
+
}
|
|
1596
|
+
function deriveSessionKey(envelope, payload) {
|
|
1597
|
+
const raw = str(envelope["session_id"]) || str(payload["transcript_path"] ?? payload["transcriptPath"]);
|
|
1598
|
+
const material = raw || "unknown-session";
|
|
1599
|
+
return crypto.createHash("sha256").update(material).digest("hex");
|
|
1600
|
+
}
|
|
1601
|
+
function hasFiredThisSession(workspaceRoot, sessionKey) {
|
|
1602
|
+
try {
|
|
1603
|
+
return fs5.existsSync(sessionStateFile(workspaceRoot, sessionKey));
|
|
1604
|
+
} catch {
|
|
1605
|
+
return false;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
function markFiredThisSession(workspaceRoot, sessionKey, ts) {
|
|
1609
|
+
try {
|
|
1610
|
+
atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
|
|
1611
|
+
} catch {
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
function _read_stdin() {
|
|
1615
|
+
return readHookStdin();
|
|
1616
|
+
}
|
|
1617
|
+
function main5() {
|
|
1618
|
+
const [envelope, payload] = unwrap(_read_stdin(), "claude");
|
|
1619
|
+
const event = String(envelope["event"] ?? "");
|
|
1620
|
+
if (event !== "" && event !== "stop") {
|
|
1621
|
+
return 0;
|
|
1622
|
+
}
|
|
1623
|
+
const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
|
|
1624
|
+
let diffLines;
|
|
1625
|
+
let mutationMeasure;
|
|
1626
|
+
try {
|
|
1627
|
+
const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
|
|
1628
|
+
diffLines = measured.lines;
|
|
1629
|
+
mutationMeasure = measured.measure;
|
|
1630
|
+
} catch {
|
|
1631
|
+
return 0;
|
|
1632
|
+
}
|
|
1633
|
+
if (diffLines <= MUTATION_LINE_THRESHOLD) {
|
|
1634
|
+
return 0;
|
|
1635
|
+
}
|
|
1636
|
+
const sessionKey = deriveSessionKey(envelope, payload);
|
|
1637
|
+
if (hasFiredThisSession(workspace_root, sessionKey)) {
|
|
1638
|
+
return 0;
|
|
1639
|
+
}
|
|
1640
|
+
const transcriptPath = str(
|
|
1641
|
+
payload["transcript_path"] ?? payload["transcriptPath"]
|
|
1642
|
+
).trim();
|
|
1643
|
+
let reviewerRan = false;
|
|
1644
|
+
if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
|
|
1645
|
+
try {
|
|
1646
|
+
reviewerRan = scanTranscriptForReviewer(fs5.readFileSync(transcriptPath, "utf-8"));
|
|
1647
|
+
} catch {
|
|
1648
|
+
reviewerRan = false;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
if (reviewerRan) {
|
|
1652
|
+
return 0;
|
|
1653
|
+
}
|
|
1654
|
+
markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
1655
|
+
try {
|
|
1656
|
+
appendReviewSkippedTelemetry(workspace_root, diffLines, mutationMeasure);
|
|
1657
|
+
} catch {
|
|
1658
|
+
}
|
|
1659
|
+
process.stdout.write(
|
|
1660
|
+
`${JSON.stringify({
|
|
1661
|
+
decision: "warn",
|
|
1662
|
+
reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
|
|
1663
|
+
additional_context: buildAdvisoryLine(diffLines)
|
|
1664
|
+
})}
|
|
1665
|
+
`
|
|
1666
|
+
);
|
|
1667
|
+
return EXIT_WARN2;
|
|
1668
|
+
}
|
|
1669
|
+
function _isCliEntry5() {
|
|
1670
|
+
if (true) {
|
|
1671
|
+
return false;
|
|
1672
|
+
}
|
|
1673
|
+
if (process.argv[1] === void 0) {
|
|
1674
|
+
return false;
|
|
1675
|
+
}
|
|
1676
|
+
const argvUrl = pathToFileURL5(path5.resolve(process.argv[1])).href;
|
|
1677
|
+
if (import.meta.url === argvUrl) {
|
|
1678
|
+
return true;
|
|
1679
|
+
}
|
|
1680
|
+
try {
|
|
1681
|
+
const here = fs5.realpathSync(fileURLToPath5(import.meta.url));
|
|
1682
|
+
const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
|
|
1683
|
+
return here === argv;
|
|
1684
|
+
} catch {
|
|
1685
|
+
return false;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
if (_isCliEntry5()) {
|
|
1689
|
+
process.exit(main5());
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
// src/scripts/hooks/turn_end_gate_hook.ts
|
|
1311
1693
|
var EXIT_BLOCK2 = 1;
|
|
1312
|
-
var
|
|
1313
|
-
|
|
1694
|
+
var EXIT_ALLOW4 = 0;
|
|
1695
|
+
var TRANSCRIPT_READ_MAX_BYTES = 8 * 1024 * 1024;
|
|
1696
|
+
function stripFencedBlocks(text) {
|
|
1697
|
+
const out = [];
|
|
1698
|
+
let open = null;
|
|
1699
|
+
for (const line of text.split("\n")) {
|
|
1700
|
+
const m = /^[ \t]{0,3}([`~]{3,})(.*)$/.exec(line);
|
|
1701
|
+
if (open === null) {
|
|
1702
|
+
if (m) {
|
|
1703
|
+
open = { ch: m[1][0], len: m[1].length };
|
|
1704
|
+
out.push(" ");
|
|
1705
|
+
continue;
|
|
1706
|
+
}
|
|
1707
|
+
out.push(line);
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1710
|
+
if (m && m[1][0] === open.ch && m[1].length >= open.len && (m[2] ?? "").trim() === "") {
|
|
1711
|
+
open = null;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
return out.join("\n");
|
|
1715
|
+
}
|
|
1716
|
+
function visibleProse(reply) {
|
|
1717
|
+
let text = stripFencedBlocks(reply);
|
|
1718
|
+
text = text.replace(/`[^`\n]*`/g, " ");
|
|
1719
|
+
text = text.replace(/^[ \t]*>.*$/gm, " ");
|
|
1720
|
+
text = text.replace(/^[ \t]*\|.*\|[ \t]*$/gm, " ");
|
|
1721
|
+
text = text.replace(/\bhttps?:\/\/\S+/g, " ");
|
|
1722
|
+
text = text.replace(/\S*\/\S+/g, " ");
|
|
1723
|
+
text = text.replace(/\b[\w.-]+\.(ts|js|md|json|ya?ml|py|php|txt|sh)\b/g, " ");
|
|
1724
|
+
return text;
|
|
1725
|
+
}
|
|
1726
|
+
function finalParagraph(reply) {
|
|
1727
|
+
const prose = visibleProse(reply).trim();
|
|
1728
|
+
if (!prose) return "";
|
|
1729
|
+
const paragraphs = prose.split(/\n\s*\n/).map((p) => p.trim()).filter((p) => p.length > 0);
|
|
1730
|
+
return paragraphs.length === 0 ? "" : paragraphs[paragraphs.length - 1];
|
|
1731
|
+
}
|
|
1732
|
+
var PROMISSORY = [
|
|
1733
|
+
/\bich melde mich\b/i,
|
|
1734
|
+
/\bmelde ich (mich|dir|Dir)\b/i,
|
|
1735
|
+
/\bich melde\b/i,
|
|
1736
|
+
/\bich berichte\b/i,
|
|
1737
|
+
/\bich (sage|gebe) (dir |Dir )?Bescheid\b/i,
|
|
1738
|
+
/\bals n(ä|ae)chstes (werde|mache|baue|pr(ü|ue)fe) ich\b/i,
|
|
1739
|
+
// German puts the infinitive at the END of the clause ("ich werde jetzt
|
|
1740
|
+
// die Tests schreiben"), so the verb cannot be matched adjacent to
|
|
1741
|
+
// "werde" — it has to be looked for across the rest of the sentence.
|
|
1742
|
+
//
|
|
1743
|
+
// The lookahead excludes a stated REFUSAL to act: declining to do
|
|
1744
|
+
// something is not a promise to do it. R2 finding 4 reproduced four live
|
|
1745
|
+
// false refusals against the old `\bnicht\b`-only version — "Ich werde
|
|
1746
|
+
// nichts anfassen" (no word boundary after `nicht`), "keine Tests
|
|
1747
|
+
// schreiben", "niemals raten", and the passive non-promise "Ich werde
|
|
1748
|
+
// gefragt, ob …". Each was a false refusal on a BLOCKING guard, which is
|
|
1749
|
+
// the precision failure the council warning is about.
|
|
1750
|
+
// `\p{L}` with the `u` flag, NOT `\w`: R2 round 2, finding 15 verified that
|
|
1751
|
+
// `\b\w{3,}en\b` cannot reach an infinitive whose post-umlaut fragment is
|
|
1752
|
+
// short, because `ü`/`ö`/`ä` are not `\w` and create a word boundary — so
|
|
1753
|
+
// "prüfen" and "lösen" were silent while the comment above claimed the
|
|
1754
|
+
// verb-final construction was handled.
|
|
1755
|
+
//
|
|
1756
|
+
// Honest about what this proxy matches: any word of 3+ letters ending in
|
|
1757
|
+
// "en" within the clause, which is the infinitive in practice but also hits
|
|
1758
|
+
// a plural noun ("die Zeilen zählen" matches on "Zeilen"). That is
|
|
1759
|
+
// acceptable here — the clause already requires "ich werde" and excludes
|
|
1760
|
+
// negations and passives, so a forward commitment is what remains — but it
|
|
1761
|
+
// is a proxy for the infinitive, not a parse of one.
|
|
1762
|
+
new RegExp("\\bich werde\\b(?![^.!?\\n]*\\b(?:nicht|nichts|kein(?:e|en|em|er|es)?|niemals|nie)\\b)(?![^.!?\\n]*\\b(?:gefragt|gebeten|informiert|benachrichtigt)\\b)[^.!?\\n]*(?<!\\p{L})\\p{L}{3,}en(?!\\p{L})", "iu"),
|
|
1763
|
+
/\bI(?:'| wi)ll (report|let you know|update you|follow up)\b/i,
|
|
1764
|
+
/\bI(?:'| wi)ll (now |then )?\w+ (it|that|this|next)\b/i,
|
|
1765
|
+
/\bnext,? I(?:'| wi)ll\b/i,
|
|
1766
|
+
/\bI am going to\b/i
|
|
1767
|
+
];
|
|
1768
|
+
var HANDBACK = [
|
|
1769
|
+
/\bdas entscheidest (du|Du)\b/i,
|
|
1770
|
+
/\bdeine Entscheidung\b/i,
|
|
1771
|
+
/\bich fasse .{0,40}nicht ungefragt an\b/i,
|
|
1772
|
+
/\bsag (Bescheid|ein Wort)\b/i,
|
|
1773
|
+
/\bwarte auf (deine|Deine|dein|Dein)\b/i,
|
|
1774
|
+
/\byour call\b/i,
|
|
1775
|
+
/\byou decide\b/i,
|
|
1776
|
+
/\blet me know (which|if|whether)\b/i,
|
|
1777
|
+
/\bwaiting for your\b/i
|
|
1778
|
+
];
|
|
1779
|
+
function detectPromissory(reply) {
|
|
1780
|
+
const tail = finalParagraph(reply);
|
|
1781
|
+
if (!tail) return null;
|
|
1782
|
+
if (/\?["'’)\]]*\s*$/.test(tail)) return null;
|
|
1783
|
+
if (HANDBACK.some((re) => re.test(tail))) return null;
|
|
1784
|
+
for (const re of PROMISSORY) {
|
|
1785
|
+
const m = tail.match(re);
|
|
1786
|
+
if (m) {
|
|
1787
|
+
return {
|
|
1788
|
+
detector: "promissory",
|
|
1789
|
+
evidence: m[0],
|
|
1790
|
+
reason: "the closing paragraph promises work that has not been performed, and the turn asks the user nothing \u2014 so nothing ends this turn but the promise itself"
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
return null;
|
|
1795
|
+
}
|
|
1796
|
+
function readCiSettled(workspaceRoot) {
|
|
1797
|
+
try {
|
|
1798
|
+
const raw = fs6.readFileSync(path6.join(workspaceRoot, STATE_FILE3), "utf-8");
|
|
1799
|
+
const decoded = JSON.parse(raw);
|
|
1800
|
+
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
1801
|
+
const ci = decoded["ci_last"];
|
|
1802
|
+
if (typeof ci === "object" && ci !== null && !Array.isArray(ci)) {
|
|
1803
|
+
return { seen: true, settled: ci["settled"] === true };
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
} catch {
|
|
1807
|
+
}
|
|
1808
|
+
return { seen: false, settled: false };
|
|
1809
|
+
}
|
|
1810
|
+
var _COMPLETION_RE = /(^|\n)\s*(?:\*\*)?(?:fertig\b|damit ist alles erledigt|aufgabe erledigt|alles erledigt\b|komplett(?:er)? (?:auftrag|abgearbeitet)|der komplette auftrag ist durch|done[.!]|all done\b|task complete)/i;
|
|
1811
|
+
function detectCompletionClaim(reply, ci) {
|
|
1812
|
+
if (!ci.seen || ci.settled) return null;
|
|
1813
|
+
const prose = visibleProse(reply);
|
|
1814
|
+
const m = _COMPLETION_RE.exec(prose);
|
|
1815
|
+
if (!m) return null;
|
|
1816
|
+
const at = m.index;
|
|
1817
|
+
return {
|
|
1818
|
+
detector: "completion",
|
|
1819
|
+
evidence: prose.slice(at, at + 120).trim(),
|
|
1820
|
+
reason: "a completion claim while the last CI read was not settled \u2014 read the verdict, then claim it (verify-before-complete)"
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function readLanguagePin(workspaceRoot) {
|
|
1824
|
+
try {
|
|
1825
|
+
const raw = fs6.readFileSync(path6.join(workspaceRoot, STATE_FILE), "utf-8");
|
|
1826
|
+
const decoded = JSON.parse(raw);
|
|
1827
|
+
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
1828
|
+
const lang = decoded["language"];
|
|
1829
|
+
if (lang === "de" || lang === "en") return lang;
|
|
1830
|
+
}
|
|
1831
|
+
} catch {
|
|
1832
|
+
}
|
|
1833
|
+
return "und";
|
|
1834
|
+
}
|
|
1835
|
+
function detectLanguage(reply, pinned) {
|
|
1836
|
+
if (pinned !== "de" && pinned !== "en") return null;
|
|
1837
|
+
const prose = visibleProse(reply);
|
|
1838
|
+
const verdict = classify(prose);
|
|
1839
|
+
if (verdict.language === "und") return null;
|
|
1840
|
+
if (verdict.language === pinned) return null;
|
|
1841
|
+
return {
|
|
1842
|
+
detector: "language",
|
|
1843
|
+
evidence: prose.trim().slice(0, 120),
|
|
1844
|
+
reason: `the pinned reply language is "${pinned}" but the reply classifies as "${verdict.language}" (${verdict.de_markers} de / ${verdict.en_markers} en markers; code, quotes, tables, URLs and paths excluded, then scored lead-first by the same classifier that sets the pin)`
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
var _EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
1848
|
+
var _VERIFY_RE = /\b(test|tests|vitest|jest|pytest|phpunit|pest|tsc|eslint|ruff|mypy|pyright|clippy|typecheck|lint|build|ci|preflight|smoke[-_:a-z]*|check[-_:a-z]*|validate[-_:a-z]*)\b|\b(task|npm|pnpm|yarn|composer|cargo|go|make|php artisan|bun)\s+(run\s+)?\S*(test|check|lint|build|ci|typecheck)/i;
|
|
1849
|
+
function isVerificationCommand(command) {
|
|
1850
|
+
return _VERIFY_RE.test(command);
|
|
1851
|
+
}
|
|
1852
|
+
function detectUnverifiedEdit(toolCalls) {
|
|
1853
|
+
let lastEdit = -1;
|
|
1854
|
+
for (let i = 0; i < toolCalls.length; i += 1) {
|
|
1855
|
+
if (_EDIT_TOOLS.has(toolCalls[i].name)) lastEdit = i;
|
|
1856
|
+
}
|
|
1857
|
+
if (lastEdit === -1) return null;
|
|
1858
|
+
for (let i = lastEdit + 1; i < toolCalls.length; i += 1) {
|
|
1859
|
+
const c = toolCalls[i];
|
|
1860
|
+
if (c.name === "Bash" && c.command !== void 0 && isVerificationCommand(c.command)) {
|
|
1861
|
+
return null;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
const edited = toolCalls[lastEdit].path;
|
|
1865
|
+
return {
|
|
1866
|
+
detector: "verification",
|
|
1867
|
+
// The path, never the diff: the evidence span is quoted into a refusal,
|
|
1868
|
+
// and `ToolCall` is shaped so a file body cannot reach it.
|
|
1869
|
+
evidence: edited ?? toolCalls[lastEdit].name,
|
|
1870
|
+
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)"
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
function stateDir(workspaceRoot) {
|
|
1874
|
+
return path6.join(workspaceRoot, "agents", "runtime", "state", "turn-end-gate");
|
|
1875
|
+
}
|
|
1876
|
+
function sessionStateFile2(workspaceRoot, sessionKey) {
|
|
1877
|
+
return path6.join(stateDir(workspaceRoot), `${sessionKey}.json`);
|
|
1878
|
+
}
|
|
1879
|
+
function deriveSessionKey2(sessionId) {
|
|
1880
|
+
return crypto2.createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
1881
|
+
}
|
|
1882
|
+
function alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal) {
|
|
1883
|
+
try {
|
|
1884
|
+
const raw = fs6.readFileSync(sessionStateFile2(workspaceRoot, sessionKey), "utf-8");
|
|
1885
|
+
const decoded = JSON.parse(raw);
|
|
1886
|
+
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
1887
|
+
return decoded["refused_turn"] === turnOrdinal;
|
|
1888
|
+
}
|
|
1889
|
+
} catch {
|
|
1890
|
+
}
|
|
1891
|
+
return false;
|
|
1892
|
+
}
|
|
1893
|
+
function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detector) {
|
|
1894
|
+
if (is_replay_mode()) return;
|
|
1895
|
+
try {
|
|
1896
|
+
atomic_write_json(sessionStateFile2(workspaceRoot, sessionKey), {
|
|
1897
|
+
refused_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1898
|
+
refused_turn: turnOrdinal,
|
|
1899
|
+
detector
|
|
1900
|
+
});
|
|
1901
|
+
} catch {
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
function readTranscriptTail(transcriptPath, opts = {}) {
|
|
1905
|
+
const empty = { lastAssistant: "", turnOrdinal: 0, toolCalls: [] };
|
|
1906
|
+
if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
|
|
1907
|
+
let lines;
|
|
1908
|
+
try {
|
|
1909
|
+
if (opts.maxBytes !== void 0 && fs6.statSync(transcriptPath).size > opts.maxBytes) {
|
|
1910
|
+
return empty;
|
|
1911
|
+
}
|
|
1912
|
+
lines = fs6.readFileSync(transcriptPath, "utf-8").split("\n");
|
|
1913
|
+
} catch {
|
|
1914
|
+
return empty;
|
|
1915
|
+
}
|
|
1916
|
+
let lastAssistant = "";
|
|
1917
|
+
let turnOrdinal = 0;
|
|
1918
|
+
let toolCalls = [];
|
|
1919
|
+
for (const rawLine of lines) {
|
|
1920
|
+
const line = rawLine.trim();
|
|
1921
|
+
if (!line) continue;
|
|
1922
|
+
let obj;
|
|
1923
|
+
try {
|
|
1924
|
+
obj = JSON.parse(line);
|
|
1925
|
+
} catch {
|
|
1926
|
+
continue;
|
|
1927
|
+
}
|
|
1928
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
|
|
1929
|
+
const entry = obj;
|
|
1930
|
+
if (entry["isSidechain"] === true) continue;
|
|
1931
|
+
const role = entry["type"];
|
|
1932
|
+
if (role !== "assistant" && role !== "user") continue;
|
|
1933
|
+
const msg = entry["message"];
|
|
1934
|
+
if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
|
|
1935
|
+
const content = msg["content"];
|
|
1936
|
+
if (role === "assistant") {
|
|
1937
|
+
toolCalls.push(..._toolCalls(content));
|
|
1938
|
+
}
|
|
1939
|
+
const text = _messageText(content);
|
|
1940
|
+
if (text === null) continue;
|
|
1941
|
+
if (role === "assistant") {
|
|
1942
|
+
lastAssistant = text;
|
|
1943
|
+
} else if (!isSyntheticPrompt(text)) {
|
|
1944
|
+
turnOrdinal += 1;
|
|
1945
|
+
toolCalls = [];
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
return { lastAssistant: lastAssistant.trim(), turnOrdinal, toolCalls };
|
|
1949
|
+
}
|
|
1950
|
+
function _toolCalls(content) {
|
|
1951
|
+
if (!Array.isArray(content)) return [];
|
|
1952
|
+
const out = [];
|
|
1953
|
+
for (const blk of content) {
|
|
1954
|
+
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
1955
|
+
const b = blk;
|
|
1956
|
+
if (b["type"] !== "tool_use") continue;
|
|
1957
|
+
const name = b["name"];
|
|
1958
|
+
if (typeof name !== "string") continue;
|
|
1959
|
+
const input = b["input"];
|
|
1960
|
+
const call = { name };
|
|
1961
|
+
if (typeof input === "object" && input !== null && !Array.isArray(input)) {
|
|
1962
|
+
const inp = input;
|
|
1963
|
+
const cmd = inp["command"];
|
|
1964
|
+
if (typeof cmd === "string") call.command = cmd;
|
|
1965
|
+
const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
|
|
1966
|
+
if (typeof p === "string") call.path = p;
|
|
1967
|
+
}
|
|
1968
|
+
out.push(call);
|
|
1969
|
+
}
|
|
1970
|
+
return out;
|
|
1971
|
+
}
|
|
1972
|
+
function _messageText(content) {
|
|
1973
|
+
if (typeof content === "string") return content;
|
|
1974
|
+
if (!Array.isArray(content)) return null;
|
|
1975
|
+
const parts = [];
|
|
1976
|
+
for (const blk of content) {
|
|
1977
|
+
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
1978
|
+
const b = blk;
|
|
1979
|
+
if (b["type"] !== "text") continue;
|
|
1980
|
+
const t = b["text"];
|
|
1981
|
+
if (typeof t === "string") parts.push(t);
|
|
1982
|
+
}
|
|
1983
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
1984
|
+
}
|
|
1985
|
+
function str2(v) {
|
|
1986
|
+
return typeof v === "string" ? v : "";
|
|
1987
|
+
}
|
|
1988
|
+
function main6() {
|
|
1989
|
+
let envelope;
|
|
1990
|
+
let payload;
|
|
1991
|
+
try {
|
|
1992
|
+
[envelope, payload] = unwrap(readHookStdin(), "claude");
|
|
1993
|
+
} catch {
|
|
1994
|
+
return EXIT_ALLOW4;
|
|
1995
|
+
}
|
|
1996
|
+
if (payload["stop_hook_active"] === true) return EXIT_ALLOW4;
|
|
1997
|
+
const workspaceRoot = str2(envelope["workspace_root"]) || process.cwd();
|
|
1998
|
+
const transcriptPath = str2(
|
|
1999
|
+
payload["transcript_path"] ?? payload["transcriptPath"]
|
|
2000
|
+
);
|
|
2001
|
+
const { lastAssistant, turnOrdinal, toolCalls } = readTranscriptTail(transcriptPath, {
|
|
2002
|
+
maxBytes: TRANSCRIPT_READ_MAX_BYTES
|
|
2003
|
+
});
|
|
2004
|
+
if (!lastAssistant) return EXIT_ALLOW4;
|
|
2005
|
+
const sessionKey = deriveSessionKey2(
|
|
2006
|
+
str2(envelope["session_id"]) || "unknown-session"
|
|
2007
|
+
);
|
|
2008
|
+
if (alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal)) return EXIT_ALLOW4;
|
|
2009
|
+
const findings = [];
|
|
2010
|
+
for (const f of [
|
|
2011
|
+
detectPromissory(lastAssistant),
|
|
2012
|
+
detectLanguage(lastAssistant, readLanguagePin(workspaceRoot)),
|
|
2013
|
+
detectUnverifiedEdit(toolCalls),
|
|
2014
|
+
// Round 7 § Phase 1 — detector D, in the same unconditional list as the
|
|
2015
|
+
// other three. It shipped with its own settings flag one commit earlier
|
|
2016
|
+
// and that flag is gone: `hooks.turn_end_gate.*` was deleted on
|
|
2017
|
+
// 2026-08-12 because a default-off safety gate is an absent one. Its
|
|
2018
|
+
// gating is where the comment above says gating belongs — inside the
|
|
2019
|
+
// detector: no CI observed, or a settled read, or no completion claim
|
|
2020
|
+
// ⇒ no finding.
|
|
2021
|
+
detectCompletionClaim(lastAssistant, readCiSettled(workspaceRoot))
|
|
2022
|
+
]) {
|
|
2023
|
+
if (f) findings.push(f);
|
|
2024
|
+
}
|
|
2025
|
+
if (findings.length === 0) return EXIT_ALLOW4;
|
|
2026
|
+
markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, findings[0].detector);
|
|
2027
|
+
const lines = findings.map(
|
|
2028
|
+
(f) => ` \xB7 ${f.detector}: ${f.reason}
|
|
2029
|
+
evidence: ${JSON.stringify(f.evidence)}`
|
|
2030
|
+
);
|
|
2031
|
+
process.stderr.write(
|
|
2032
|
+
`turn-end-gate: REFUSED \u2014 this turn is not finished.
|
|
2033
|
+
${lines.join("\n")}
|
|
2034
|
+
Do the promised work now, correct the language, run the verification the edit needs, or read the CI verdict before claiming it \u2014 then end the turn.
|
|
2035
|
+
This turn will not be refused a second time.
|
|
2036
|
+
`
|
|
2037
|
+
);
|
|
2038
|
+
return EXIT_BLOCK2;
|
|
2039
|
+
}
|
|
2040
|
+
function _isCliEntry6() {
|
|
2041
|
+
if (true) {
|
|
2042
|
+
return false;
|
|
2043
|
+
}
|
|
2044
|
+
if (process.argv[1] === void 0) {
|
|
2045
|
+
return false;
|
|
2046
|
+
}
|
|
2047
|
+
const argvUrl = pathToFileURL6(path6.resolve(process.argv[1])).href;
|
|
2048
|
+
if (import.meta.url === argvUrl) {
|
|
2049
|
+
return true;
|
|
2050
|
+
}
|
|
2051
|
+
try {
|
|
2052
|
+
const here = fs6.realpathSync(fileURLToPath6(import.meta.url));
|
|
2053
|
+
const argv = fs6.realpathSync(path6.resolve(process.argv[1]));
|
|
2054
|
+
return here === argv;
|
|
2055
|
+
} catch {
|
|
2056
|
+
return false;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
if (_isCliEntry6()) {
|
|
2060
|
+
process.exit(main6());
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// src/scripts/hooks/evidence_independence.ts
|
|
2064
|
+
import crypto3 from "node:crypto";
|
|
2065
|
+
import fs7 from "node:fs";
|
|
2066
|
+
import path7 from "node:path";
|
|
2067
|
+
import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL7 } from "node:url";
|
|
2068
|
+
var EXIT_ALLOW5 = 0;
|
|
2069
|
+
var EXIT_BLOCK3 = 1;
|
|
2070
|
+
var STATE_FILE4 = path7.join("agents", "state", "evidence-dispatch.json");
|
|
2071
|
+
function _isObject6(v) {
|
|
1314
2072
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1315
2073
|
}
|
|
1316
2074
|
var DISPATCH_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -1363,10 +2121,10 @@ function preloadedVerdict(prompt) {
|
|
|
1363
2121
|
return null;
|
|
1364
2122
|
}
|
|
1365
2123
|
function extractDispatch(envelope) {
|
|
1366
|
-
const payload =
|
|
2124
|
+
const payload = _isObject6(envelope["payload"]) ? envelope["payload"] : envelope;
|
|
1367
2125
|
const toolRaw = payload["tool_name"] ?? payload["toolName"] ?? payload["tool"];
|
|
1368
2126
|
const tool = typeof toolRaw === "string" ? toolRaw : null;
|
|
1369
|
-
const input =
|
|
2127
|
+
const input = _isObject6(payload["tool_input"]) ? payload["tool_input"] : _isObject6(payload["toolInput"]) ? payload["toolInput"] : payload;
|
|
1370
2128
|
for (const key of ["prompt", "instructions", "task", "description"]) {
|
|
1371
2129
|
const v = input[key];
|
|
1372
2130
|
if (typeof v === "string" && v.trim()) {
|
|
@@ -1377,8 +2135,8 @@ function extractDispatch(envelope) {
|
|
|
1377
2135
|
}
|
|
1378
2136
|
function _ledgerStamp(consumer_root) {
|
|
1379
2137
|
try {
|
|
1380
|
-
const raw =
|
|
1381
|
-
|
|
2138
|
+
const raw = fs7.readFileSync(
|
|
2139
|
+
path7.join(consumer_root, "agents", "state", "git-authorization.json"),
|
|
1382
2140
|
"utf8"
|
|
1383
2141
|
);
|
|
1384
2142
|
const d = JSON.parse(raw);
|
|
@@ -1389,8 +2147,8 @@ function _ledgerStamp(consumer_root) {
|
|
|
1389
2147
|
}
|
|
1390
2148
|
function _load(target) {
|
|
1391
2149
|
try {
|
|
1392
|
-
const decoded = JSON.parse(
|
|
1393
|
-
if (
|
|
2150
|
+
const decoded = JSON.parse(fs7.readFileSync(target, "utf8"));
|
|
2151
|
+
if (_isObject6(decoded) && Array.isArray(decoded["evaluations"])) {
|
|
1394
2152
|
return decoded;
|
|
1395
2153
|
}
|
|
1396
2154
|
} catch {
|
|
@@ -1400,12 +2158,12 @@ function _load(target) {
|
|
|
1400
2158
|
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.";
|
|
1401
2159
|
function decide2(tool, prompt, priorEvaluations) {
|
|
1402
2160
|
if (!isDispatchTool(tool) || !isEvaluationPrompt(prompt)) {
|
|
1403
|
-
return { exit:
|
|
2161
|
+
return { exit: EXIT_ALLOW5, stdout: "", stderr: "", evaluations: priorEvaluations };
|
|
1404
2162
|
}
|
|
1405
2163
|
const preloaded = preloadedVerdict(prompt);
|
|
1406
2164
|
if (preloaded !== null) {
|
|
1407
2165
|
return {
|
|
1408
|
-
exit:
|
|
2166
|
+
exit: EXIT_BLOCK3,
|
|
1409
2167
|
stdout: "",
|
|
1410
2168
|
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.
|
|
1411
2169
|
`,
|
|
@@ -1413,11 +2171,11 @@ function decide2(tool, prompt, priorEvaluations) {
|
|
|
1413
2171
|
};
|
|
1414
2172
|
}
|
|
1415
2173
|
if (!isSelfScoped(prompt)) {
|
|
1416
|
-
return { exit:
|
|
2174
|
+
return { exit: EXIT_ALLOW5, stdout: "", stderr: "", evaluations: priorEvaluations };
|
|
1417
2175
|
}
|
|
1418
2176
|
if (priorEvaluations >= 1) {
|
|
1419
2177
|
return {
|
|
1420
|
-
exit:
|
|
2178
|
+
exit: EXIT_BLOCK3,
|
|
1421
2179
|
stdout: "",
|
|
1422
2180
|
stderr: `Blocked: second evaluation dispatch of your own work in this turn (verdict shopping). One evaluation has already run; commissioning another with a different prompt or scope selects the answer instead of measuring it. Report what the first pass returned, then re-plan.
|
|
1423
2181
|
`,
|
|
@@ -1425,7 +2183,7 @@ function decide2(tool, prompt, priorEvaluations) {
|
|
|
1425
2183
|
};
|
|
1426
2184
|
}
|
|
1427
2185
|
return {
|
|
1428
|
-
exit:
|
|
2186
|
+
exit: EXIT_ALLOW5,
|
|
1429
2187
|
stdout: `${JSON.stringify({ decision: "warn", reason: WARN_TEXT })}
|
|
1430
2188
|
`,
|
|
1431
2189
|
stderr: "",
|
|
@@ -1437,14 +2195,14 @@ function run5(stdin_text, options) {
|
|
|
1437
2195
|
if (stdin_text.trim()) {
|
|
1438
2196
|
try {
|
|
1439
2197
|
const decoded = JSON.parse(stdin_text);
|
|
1440
|
-
if (
|
|
2198
|
+
if (_isObject6(decoded)) {
|
|
1441
2199
|
envelope = decoded;
|
|
1442
2200
|
}
|
|
1443
2201
|
} catch {
|
|
1444
|
-
return
|
|
2202
|
+
return EXIT_ALLOW5;
|
|
1445
2203
|
}
|
|
1446
2204
|
}
|
|
1447
|
-
const target =
|
|
2205
|
+
const target = path7.join(options.consumer_root, STATE_FILE4);
|
|
1448
2206
|
const state = _load(target);
|
|
1449
2207
|
const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
|
|
1450
2208
|
const turnMarker = `${session_id}:${_ledgerStamp(options.consumer_root)}`;
|
|
@@ -1458,7 +2216,7 @@ function run5(stdin_text, options) {
|
|
|
1458
2216
|
if (decision.evaluations > state.evaluations.length) {
|
|
1459
2217
|
state.evaluations.push({
|
|
1460
2218
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1461
|
-
digest:
|
|
2219
|
+
digest: crypto3.createHash("sha256").update(prompt).digest("hex").slice(0, 16),
|
|
1462
2220
|
prompt_chars: prompt.length
|
|
1463
2221
|
});
|
|
1464
2222
|
try {
|
|
@@ -1474,7 +2232,7 @@ function run5(stdin_text, options) {
|
|
|
1474
2232
|
}
|
|
1475
2233
|
return decision.exit;
|
|
1476
2234
|
}
|
|
1477
|
-
function
|
|
2235
|
+
function main7(argv) {
|
|
1478
2236
|
const args = argv ?? process.argv.slice(2);
|
|
1479
2237
|
let consumer_root = process.cwd();
|
|
1480
2238
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -1488,34 +2246,54 @@ function main5(argv) {
|
|
|
1488
2246
|
}
|
|
1489
2247
|
return run5(readHookStdin(), { consumer_root });
|
|
1490
2248
|
}
|
|
1491
|
-
function
|
|
2249
|
+
function _isCliEntry7() {
|
|
1492
2250
|
if (true) {
|
|
1493
2251
|
return false;
|
|
1494
2252
|
}
|
|
1495
2253
|
if (process.argv[1] === void 0) {
|
|
1496
2254
|
return false;
|
|
1497
2255
|
}
|
|
1498
|
-
const argvUrl =
|
|
2256
|
+
const argvUrl = pathToFileURL7(path7.resolve(process.argv[1])).href;
|
|
1499
2257
|
if (import.meta.url === argvUrl) {
|
|
1500
2258
|
return true;
|
|
1501
2259
|
}
|
|
1502
2260
|
try {
|
|
1503
|
-
const here =
|
|
1504
|
-
const argv =
|
|
2261
|
+
const here = fs7.realpathSync(fileURLToPath7(import.meta.url));
|
|
2262
|
+
const argv = fs7.realpathSync(path7.resolve(process.argv[1]));
|
|
1505
2263
|
return here === argv;
|
|
1506
2264
|
} catch {
|
|
1507
2265
|
return false;
|
|
1508
2266
|
}
|
|
1509
2267
|
}
|
|
1510
|
-
if (
|
|
1511
|
-
process.exit(
|
|
2268
|
+
if (_isCliEntry7()) {
|
|
2269
|
+
process.exit(main7());
|
|
1512
2270
|
}
|
|
1513
2271
|
|
|
1514
2272
|
// src/scripts/conformance_scan.ts
|
|
1515
|
-
var REPO_ROOT =
|
|
2273
|
+
var REPO_ROOT = path8.resolve(path8.dirname(fileURLToPath8(import.meta.url)), "..", "..");
|
|
1516
2274
|
var OBSERVED_BAND = { low: 9.1, high: 39.2 };
|
|
1517
2275
|
var BAND_MIN_TURNS = 1978;
|
|
1518
|
-
|
|
2276
|
+
var CARRIER_CHANGES = [
|
|
2277
|
+
{ at: "2026-08-07T00:00:00Z", what: "language-pin bound on user_prompt_submit (round 5)" }
|
|
2278
|
+
];
|
|
2279
|
+
function spansCarrierChange(firstAts) {
|
|
2280
|
+
const stamps = firstAts.filter((s) => s !== "").sort();
|
|
2281
|
+
if (stamps.length === 0) {
|
|
2282
|
+
return { spans: false, what: "" };
|
|
2283
|
+
}
|
|
2284
|
+
const lo = stamps[0];
|
|
2285
|
+
const hi = stamps[stamps.length - 1];
|
|
2286
|
+
for (const c of CARRIER_CHANGES) {
|
|
2287
|
+
if (lo < c.at && hi >= c.at) {
|
|
2288
|
+
return { spans: true, what: c.what };
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
return { spans: false, what: "" };
|
|
2292
|
+
}
|
|
2293
|
+
function bandVerdict(rate_pct, assistant_turns, opts = {}) {
|
|
2294
|
+
if (opts.spansCarrier === true) {
|
|
2295
|
+
return "era-spanning";
|
|
2296
|
+
}
|
|
1519
2297
|
if (assistant_turns < BAND_MIN_TURNS) {
|
|
1520
2298
|
return "corpus-too-small";
|
|
1521
2299
|
}
|
|
@@ -1525,13 +2303,15 @@ var CHECK_IDS = [
|
|
|
1525
2303
|
"language-pin",
|
|
1526
2304
|
"git-authorization",
|
|
1527
2305
|
"vacuous-evidence",
|
|
1528
|
-
"evidence-steering"
|
|
2306
|
+
"evidence-steering",
|
|
2307
|
+
"completion-claim"
|
|
1529
2308
|
];
|
|
1530
2309
|
var CHECK_MEANINGS = {
|
|
1531
2310
|
"language-pin": "An assistant turn whose user-visible prose is not in the language that turn's own pin named. Carries provenance (turns_since_prompt, compaction_since_prompt) so a pin that was ABSENT because compaction removed it stays distinguishable from one that was present and ignored.",
|
|
1532
2311
|
"git-authorization": "A gated git operation performed in a session where no authorization for that operation was observed. Authorization is per-operation and never carries from one to the next.",
|
|
1533
2312
|
"vacuous-evidence": "A CI poll read as settled without ever observing an in-flight run \u2014 the poll gap that reports a green settle over a window in which nothing was pending yet.",
|
|
1534
|
-
"evidence-steering": "A self-commissioned evaluation whose prompt pre-loaded its own verdict, or a second self-scoped evaluation of the same subject within one turn."
|
|
2313
|
+
"evidence-steering": "A self-commissioned evaluation whose prompt pre-loaded its own verdict, or a second self-scoped evaluation of the same subject within one turn.",
|
|
2314
|
+
"completion-claim": "An assistant turn claiming the work is complete while the last CI read in that session was NOT settled. Round 7 measured 14 instances by reading, each followed by the user handing the work back; this is the same predicate `turn-end-gate`'s completion detector refuses on, so the rate and the gate cannot disagree. A session that never polled CI can never hit."
|
|
1535
2315
|
};
|
|
1536
2316
|
function renderWhy(report, id) {
|
|
1537
2317
|
const hits = report.per_session.flatMap((s) => s.violations.filter((v) => v.check === id));
|
|
@@ -1561,7 +2341,7 @@ function renderWhy(report, id) {
|
|
|
1561
2341
|
}
|
|
1562
2342
|
return lines.join("\n");
|
|
1563
2343
|
}
|
|
1564
|
-
function
|
|
2344
|
+
function _isObject7(v) {
|
|
1565
2345
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1566
2346
|
}
|
|
1567
2347
|
function userText(entry) {
|
|
@@ -1585,15 +2365,15 @@ function assistantText(entry) {
|
|
|
1585
2365
|
}
|
|
1586
2366
|
function toolUses2(entry) {
|
|
1587
2367
|
const msg = entry["message"];
|
|
1588
|
-
const content =
|
|
2368
|
+
const content = _isObject7(msg) ? msg["content"] : void 0;
|
|
1589
2369
|
if (!Array.isArray(content)) {
|
|
1590
2370
|
return [];
|
|
1591
2371
|
}
|
|
1592
|
-
return content.filter((b) =>
|
|
2372
|
+
return content.filter((b) => _isObject7(b) && b["type"] === "tool_use").map((b) => {
|
|
1593
2373
|
const o = b;
|
|
1594
2374
|
return {
|
|
1595
2375
|
name: String(o["name"] ?? ""),
|
|
1596
|
-
input:
|
|
2376
|
+
input: _isObject7(o["input"]) ? o["input"] : {}
|
|
1597
2377
|
};
|
|
1598
2378
|
});
|
|
1599
2379
|
}
|
|
@@ -1610,7 +2390,9 @@ function scanSession(sessionId, lines) {
|
|
|
1610
2390
|
session: sessionId,
|
|
1611
2391
|
user_turns: 0,
|
|
1612
2392
|
assistant_turns: 0,
|
|
1613
|
-
violations: []
|
|
2393
|
+
violations: [],
|
|
2394
|
+
de_pin_turns: 0,
|
|
2395
|
+
first_at: ""
|
|
1614
2396
|
};
|
|
1615
2397
|
let pinned = null;
|
|
1616
2398
|
let pendingPoll = null;
|
|
@@ -1619,6 +2401,8 @@ function scanSession(sessionId, lines) {
|
|
|
1619
2401
|
let evaluationsThisTurn = 0;
|
|
1620
2402
|
let turnsSincePrompt = 0;
|
|
1621
2403
|
let compactionSincePrompt = false;
|
|
2404
|
+
let ciSeen = false;
|
|
2405
|
+
let ciSettled = false;
|
|
1622
2406
|
for (const line of lines) {
|
|
1623
2407
|
if (!line.trim()) {
|
|
1624
2408
|
continue;
|
|
@@ -1629,6 +2413,9 @@ function scanSession(sessionId, lines) {
|
|
|
1629
2413
|
} catch {
|
|
1630
2414
|
continue;
|
|
1631
2415
|
}
|
|
2416
|
+
if (report.first_at === "" && typeof entry["timestamp"] === "string") {
|
|
2417
|
+
report.first_at = entry["timestamp"];
|
|
2418
|
+
}
|
|
1632
2419
|
if (entry["type"] === "user" && entry["toolUseResult"] !== void 0 && pendingPoll !== null) {
|
|
1633
2420
|
const raw = entry["toolUseResult"];
|
|
1634
2421
|
const out = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
@@ -1643,6 +2430,8 @@ function scanSession(sessionId, lines) {
|
|
|
1643
2430
|
detail: `CI poll read as settled without an in-flight observation: ${out.slice(0, 90)}`
|
|
1644
2431
|
});
|
|
1645
2432
|
}
|
|
2433
|
+
ciSeen = true;
|
|
2434
|
+
ciSettled = pending === 0 && sawPending && !isVacuousOutput(out);
|
|
1646
2435
|
pendingPoll = null;
|
|
1647
2436
|
continue;
|
|
1648
2437
|
}
|
|
@@ -1682,6 +2471,20 @@ function scanSession(sessionId, lines) {
|
|
|
1682
2471
|
if (prose !== null) {
|
|
1683
2472
|
report.assistant_turns += 1;
|
|
1684
2473
|
turnsSincePrompt += 1;
|
|
2474
|
+
if (pinned === "de") {
|
|
2475
|
+
report.de_pin_turns += 1;
|
|
2476
|
+
}
|
|
2477
|
+
if (!HARNESS_TEXT.test((prose.split("\n").find((l) => l.trim()) ?? "").trim())) {
|
|
2478
|
+
const cc = detectCompletionClaim(prose, { seen: ciSeen, settled: ciSettled });
|
|
2479
|
+
if (cc !== null) {
|
|
2480
|
+
report.violations.push({
|
|
2481
|
+
check: "completion-claim",
|
|
2482
|
+
session: sessionId,
|
|
2483
|
+
at,
|
|
2484
|
+
detail: `completion claimed over an unsettled CI read: "${cc.evidence.slice(0, 110)}"`
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
1685
2488
|
if (pinned === "de") {
|
|
1686
2489
|
const first = prose.split("\n").find((l) => l.trim()) ?? "";
|
|
1687
2490
|
if (HARNESS_TEXT.test(first.trim())) {
|
|
@@ -1742,7 +2545,7 @@ function scanSession(sessionId, lines) {
|
|
|
1742
2545
|
}
|
|
1743
2546
|
return report;
|
|
1744
2547
|
}
|
|
1745
|
-
var DEFAULT_RATE_SERIES =
|
|
2548
|
+
var DEFAULT_RATE_SERIES = path8.join(
|
|
1746
2549
|
REPO_ROOT,
|
|
1747
2550
|
"agents",
|
|
1748
2551
|
"runtime",
|
|
@@ -1751,7 +2554,7 @@ var DEFAULT_RATE_SERIES = path6.join(
|
|
|
1751
2554
|
);
|
|
1752
2555
|
function defaultStore(projectDir) {
|
|
1753
2556
|
const slug = projectDir.replace(/[/.]/g, "-");
|
|
1754
|
-
return
|
|
2557
|
+
return path8.join(process.env["HOME"] ?? "", ".claude", "projects", slug);
|
|
1755
2558
|
}
|
|
1756
2559
|
function _tokens(chars) {
|
|
1757
2560
|
return Math.round(chars / 4);
|
|
@@ -1762,13 +2565,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
|
|
|
1762
2565
|
return {
|
|
1763
2566
|
project: {
|
|
1764
2567
|
dir: projectRulesDir,
|
|
1765
|
-
present:
|
|
2568
|
+
present: fs8.existsSync(projectRulesDir),
|
|
1766
2569
|
files: p.files,
|
|
1767
2570
|
tokens: _tokens(p.chars)
|
|
1768
2571
|
},
|
|
1769
2572
|
global: {
|
|
1770
2573
|
dir: globalRulesDir,
|
|
1771
|
-
present:
|
|
2574
|
+
present: fs8.existsSync(globalRulesDir),
|
|
1772
2575
|
files: g.files,
|
|
1773
2576
|
tokens: _tokens(g.chars)
|
|
1774
2577
|
},
|
|
@@ -1776,13 +2579,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
|
|
|
1776
2579
|
};
|
|
1777
2580
|
}
|
|
1778
2581
|
function storeKey(store) {
|
|
1779
|
-
return
|
|
2582
|
+
return createHash3("sha256").update(path8.resolve(store)).digest("hex").slice(0, 12);
|
|
1780
2583
|
}
|
|
1781
2584
|
function scanStore(store, limit) {
|
|
1782
|
-
const files =
|
|
2585
|
+
const files = fs8.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs8.statSync(path8.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
|
|
1783
2586
|
const per_session = [];
|
|
1784
2587
|
for (const f of files) {
|
|
1785
|
-
const lines =
|
|
2588
|
+
const lines = fs8.readFileSync(path8.join(store, f), "utf8").split("\n");
|
|
1786
2589
|
const r = scanSession(f.replace(/\.jsonl$/, "").slice(0, 8), lines);
|
|
1787
2590
|
if (r.assistant_turns > 0) {
|
|
1788
2591
|
per_session.push(r);
|
|
@@ -1792,7 +2595,8 @@ function scanStore(store, limit) {
|
|
|
1792
2595
|
"language-pin": 0,
|
|
1793
2596
|
"git-authorization": 0,
|
|
1794
2597
|
"vacuous-evidence": 0,
|
|
1795
|
-
"evidence-steering": 0
|
|
2598
|
+
"evidence-steering": 0,
|
|
2599
|
+
"completion-claim": 0
|
|
1796
2600
|
};
|
|
1797
2601
|
for (const s of per_session) {
|
|
1798
2602
|
for (const v of s.violations) {
|
|
@@ -1802,11 +2606,14 @@ function scanStore(store, limit) {
|
|
|
1802
2606
|
const assistant_turns = per_session.reduce((n, s) => n + s.assistant_turns, 0);
|
|
1803
2607
|
const language_pin = totals["language-pin"] ?? 0;
|
|
1804
2608
|
const rate_pct = assistant_turns === 0 ? 0 : Number((100 * language_pin / assistant_turns).toFixed(1));
|
|
2609
|
+
const de_pin_turns = per_session.reduce((n, s) => n + s.de_pin_turns, 0);
|
|
2610
|
+
const rate_pct_de_pin = de_pin_turns === 0 ? 0 : Number((100 * language_pin / de_pin_turns).toFixed(1));
|
|
2611
|
+
const era = spansCarrierChange(per_session.map((s) => s.first_at));
|
|
1805
2612
|
const delivered = measureDelivered(
|
|
1806
|
-
|
|
1807
|
-
|
|
2613
|
+
path8.join(REPO_ROOT, ".claude", "rules"),
|
|
2614
|
+
path8.join(process.env["HOME"] ?? os2.homedir(), ".claude", "rules")
|
|
1808
2615
|
);
|
|
1809
|
-
const ownStore =
|
|
2616
|
+
const ownStore = path8.resolve(store) === path8.resolve(defaultStore(REPO_ROOT));
|
|
1810
2617
|
return {
|
|
1811
2618
|
scanned_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1812
2619
|
store,
|
|
@@ -1820,7 +2627,11 @@ function scanStore(store, limit) {
|
|
|
1820
2627
|
assistant_turns,
|
|
1821
2628
|
language_pin,
|
|
1822
2629
|
rate_pct,
|
|
1823
|
-
|
|
2630
|
+
de_pin_turns,
|
|
2631
|
+
rate_pct_de_pin,
|
|
2632
|
+
era_spanning: era.spans,
|
|
2633
|
+
era_reason: era.what,
|
|
2634
|
+
band: bandVerdict(rate_pct, assistant_turns, { spansCarrier: era.spans }),
|
|
1824
2635
|
// Only attach the carrier figures when the rate and the carriers describe
|
|
1825
2636
|
// the same project.
|
|
1826
2637
|
delivered_project_tokens: ownStore ? delivered.project.tokens : null,
|
|
@@ -1829,8 +2640,8 @@ function scanStore(store, limit) {
|
|
|
1829
2640
|
};
|
|
1830
2641
|
}
|
|
1831
2642
|
function recordRate(file, scanned_at, rate) {
|
|
1832
|
-
|
|
1833
|
-
|
|
2643
|
+
fs8.mkdirSync(path8.dirname(file), { recursive: true });
|
|
2644
|
+
fs8.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
|
|
1834
2645
|
`, "utf8");
|
|
1835
2646
|
}
|
|
1836
2647
|
function render(report) {
|
|
@@ -1861,7 +2672,26 @@ function render(report) {
|
|
|
1861
2672
|
lines.push(
|
|
1862
2673
|
` language-pin rate ${report.rate.rate_pct.toFixed(1)}% (${report.rate.language_pin} of ${report.rate.assistant_turns} assistant turns)`
|
|
1863
2674
|
);
|
|
1864
|
-
|
|
2675
|
+
const dePinTurns = report.rate.de_pin_turns;
|
|
2676
|
+
const dePinRate = report.rate.rate_pct_de_pin;
|
|
2677
|
+
if (typeof dePinTurns === "number" && typeof dePinRate === "number") {
|
|
2678
|
+
lines.push(
|
|
2679
|
+
` ${dePinRate.toFixed(1)}% (${report.rate.language_pin} of ${dePinTurns} turns under a GERMAN pin \u2014 the only turns this check can fire on)`
|
|
2680
|
+
);
|
|
2681
|
+
lines.push(
|
|
2682
|
+
" The band (M5, n=3) was computed on the ALL-turns denominator, so the first figure"
|
|
2683
|
+
);
|
|
2684
|
+
lines.push(" is the comparable one and the second is the honest one. Both are printed.");
|
|
2685
|
+
}
|
|
2686
|
+
if (report.rate.band === "era-spanning") {
|
|
2687
|
+
lines.push(
|
|
2688
|
+
` Not compared: the corpus SPANS a carrier landing \u2014 ${report.rate.era_reason || "see CARRIER_CHANGES"}.`
|
|
2689
|
+
);
|
|
2690
|
+
lines.push(" A rate pooled across it is a weighted average of two different systems, not");
|
|
2691
|
+
lines.push(" a reading about behaviour. Round 7 measured 23.1% before and 0.0% after that");
|
|
2692
|
+
lines.push(" exact carrier; the pooled 6.5% was announced as the falsifier firing.");
|
|
2693
|
+
lines.push(" Split the window at the carrier date and read each era on its own.");
|
|
2694
|
+
} else if (report.rate.band === "corpus-too-small") {
|
|
1865
2695
|
lines.push(
|
|
1866
2696
|
` Not compared: ${report.rate.assistant_turns} turns is below the ${BAND_MIN_TURNS} of the`
|
|
1867
2697
|
);
|
|
@@ -1883,11 +2713,22 @@ function render(report) {
|
|
|
1883
2713
|
lines.push(" as new. Store novelty is not project novelty.");
|
|
1884
2714
|
}
|
|
1885
2715
|
lines.push("");
|
|
1886
|
-
lines.push(" Scanned checks are exactly the mechanised ones.
|
|
1887
|
-
lines.push("
|
|
2716
|
+
lines.push(" Scanned checks are exactly the mechanised ones. What is NOT in the counts above:");
|
|
2717
|
+
lines.push(" ask-shape (trailing free-text offer) \u2014 measured NOWHERE. ~11 findings in round 7's");
|
|
2718
|
+
lines.push(" reading half; the discriminator needs judgement, so it stays advisory on purpose.");
|
|
2719
|
+
lines.push(" session-canary (opening greeting) \u2014 not here, but PROBEABLE:");
|
|
2720
|
+
lines.push(" `./scripts-run src/scripts/probe_session_canary`. Round 7: 25/28 sessions,");
|
|
2721
|
+
lines.push(" 24/25 post-carrier. The per-TASK instance is undecidable \u2014 no task boundary");
|
|
2722
|
+
lines.push(" is recorded in a transcript.");
|
|
2723
|
+
lines.push(" promissory closings \u2014 not here, but ALREADY GATED: `turn-end-gate`");
|
|
2724
|
+
lines.push(" ships a blocking `detectPromissory`. Round 7 probe: 1 of 120 hand-back turns");
|
|
2725
|
+
lines.push(" (the 163 this line first printed was the retracted denominator \u2014 it counted");
|
|
2726
|
+
lines.push(" synthetic user turns as hand-back closers).");
|
|
2727
|
+
lines.push(" `./scripts-run src/scripts/probe_promissory_closing`.");
|
|
2728
|
+
lines.push(" checkbox batching \u2014 measured NOWHERE. ~5 low-severity findings.");
|
|
1888
2729
|
return lines.join("\n");
|
|
1889
2730
|
}
|
|
1890
|
-
function
|
|
2731
|
+
function main8(argv) {
|
|
1891
2732
|
const args = argv ?? process.argv.slice(2);
|
|
1892
2733
|
let store = null;
|
|
1893
2734
|
let limit = 30;
|
|
@@ -1919,14 +2760,14 @@ function main6(argv) {
|
|
|
1919
2760
|
return 2;
|
|
1920
2761
|
}
|
|
1921
2762
|
const resolved = store ?? defaultStore(process.cwd());
|
|
1922
|
-
if (!
|
|
2763
|
+
if (!fs8.existsSync(resolved)) {
|
|
1923
2764
|
process.stderr.write(`conformance:behavior: no transcript store at ${resolved}
|
|
1924
2765
|
`);
|
|
1925
2766
|
return 1;
|
|
1926
2767
|
}
|
|
1927
2768
|
const report = scanStore(resolved, Number.isFinite(limit) && limit > 0 ? limit : 30);
|
|
1928
2769
|
if (out) {
|
|
1929
|
-
|
|
2770
|
+
fs8.writeFileSync(out, `${JSON.stringify(report, null, 2)}
|
|
1930
2771
|
`, "utf8");
|
|
1931
2772
|
}
|
|
1932
2773
|
if (record) {
|
|
@@ -1934,7 +2775,7 @@ function main6(argv) {
|
|
|
1934
2775
|
}
|
|
1935
2776
|
if (why !== null) {
|
|
1936
2777
|
const id = why;
|
|
1937
|
-
|
|
2778
|
+
fs8.writeSync(
|
|
1938
2779
|
1,
|
|
1939
2780
|
json ? `${JSON.stringify(
|
|
1940
2781
|
{
|
|
@@ -1954,48 +2795,48 @@ function main6(argv) {
|
|
|
1954
2795
|
);
|
|
1955
2796
|
return 0;
|
|
1956
2797
|
}
|
|
1957
|
-
|
|
2798
|
+
fs8.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
|
|
1958
2799
|
` : `${render(report)}
|
|
1959
2800
|
`);
|
|
1960
2801
|
return 0;
|
|
1961
2802
|
}
|
|
1962
|
-
function
|
|
2803
|
+
function _isCliEntry8() {
|
|
1963
2804
|
if (true) {
|
|
1964
2805
|
return false;
|
|
1965
2806
|
}
|
|
1966
2807
|
if (process.argv[1] === void 0) {
|
|
1967
2808
|
return false;
|
|
1968
2809
|
}
|
|
1969
|
-
const argvUrl =
|
|
2810
|
+
const argvUrl = pathToFileURL8(path8.resolve(process.argv[1])).href;
|
|
1970
2811
|
if (import.meta.url === argvUrl) {
|
|
1971
2812
|
return true;
|
|
1972
2813
|
}
|
|
1973
2814
|
try {
|
|
1974
|
-
const here =
|
|
1975
|
-
const argv =
|
|
2815
|
+
const here = fs8.realpathSync(fileURLToPath8(import.meta.url));
|
|
2816
|
+
const argv = fs8.realpathSync(path8.resolve(process.argv[1]));
|
|
1976
2817
|
return here === argv;
|
|
1977
2818
|
} catch {
|
|
1978
2819
|
return false;
|
|
1979
2820
|
}
|
|
1980
2821
|
}
|
|
1981
|
-
if (
|
|
1982
|
-
process.exit(
|
|
2822
|
+
if (_isCliEntry8()) {
|
|
2823
|
+
process.exit(main8());
|
|
1983
2824
|
}
|
|
1984
2825
|
|
|
1985
2826
|
// src/scripts/report_carrier_divergence.ts
|
|
1986
|
-
import * as
|
|
1987
|
-
import * as
|
|
1988
|
-
import * as
|
|
2827
|
+
import * as fs9 from "node:fs";
|
|
2828
|
+
import * as os3 from "node:os";
|
|
2829
|
+
import * as path9 from "node:path";
|
|
1989
2830
|
import process2 from "node:process";
|
|
1990
|
-
import { fileURLToPath as
|
|
1991
|
-
var _HERE =
|
|
1992
|
-
var REPO_ROOT2 =
|
|
1993
|
-
var PROJECT_RULES =
|
|
1994
|
-
var PROJECTION_SOURCE =
|
|
1995
|
-
var GLOBAL_RULES =
|
|
2831
|
+
import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL9 } from "node:url";
|
|
2832
|
+
var _HERE = fileURLToPath9(import.meta.url);
|
|
2833
|
+
var REPO_ROOT2 = path9.resolve(path9.dirname(_HERE), "..", "..");
|
|
2834
|
+
var PROJECT_RULES = path9.join(".claude", "rules");
|
|
2835
|
+
var PROJECTION_SOURCE = path9.join("dist", "agent-src", "rules");
|
|
2836
|
+
var GLOBAL_RULES = path9.join(".claude", "rules");
|
|
1996
2837
|
function _listRules(dir) {
|
|
1997
2838
|
try {
|
|
1998
|
-
return
|
|
2839
|
+
return fs9.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
1999
2840
|
} catch {
|
|
2000
2841
|
return [];
|
|
2001
2842
|
}
|
|
@@ -2003,7 +2844,7 @@ function _listRules(dir) {
|
|
|
2003
2844
|
function _ruleType(file) {
|
|
2004
2845
|
let text;
|
|
2005
2846
|
try {
|
|
2006
|
-
text =
|
|
2847
|
+
text = fs9.readFileSync(file, "utf-8");
|
|
2007
2848
|
} catch {
|
|
2008
2849
|
return "";
|
|
2009
2850
|
}
|
|
@@ -2014,8 +2855,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
2014
2855
|
const out = {
|
|
2015
2856
|
projectDir,
|
|
2016
2857
|
globalDir,
|
|
2017
|
-
globalPresent:
|
|
2018
|
-
projectPresent:
|
|
2858
|
+
globalPresent: fs9.existsSync(globalDir),
|
|
2859
|
+
projectPresent: fs9.existsSync(projectDir),
|
|
2019
2860
|
shared: 0,
|
|
2020
2861
|
identical: [],
|
|
2021
2862
|
provenanceOnly: [],
|
|
@@ -2038,7 +2879,7 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
2038
2879
|
}
|
|
2039
2880
|
if (!inP && inG) {
|
|
2040
2881
|
out.globalOnly.push(rule);
|
|
2041
|
-
if (sourceDir !== void 0 && _ruleType(
|
|
2882
|
+
if (sourceDir !== void 0 && _ruleType(path9.join(sourceDir, rule)) === "manual") {
|
|
2042
2883
|
out.manualOnlyGlobal.push(rule);
|
|
2043
2884
|
}
|
|
2044
2885
|
continue;
|
|
@@ -2048,8 +2889,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
2048
2889
|
let projectCopy;
|
|
2049
2890
|
let globalCopy;
|
|
2050
2891
|
try {
|
|
2051
|
-
projectCopy =
|
|
2052
|
-
globalCopy =
|
|
2892
|
+
projectCopy = fs9.readFileSync(path9.join(projectDir, rule));
|
|
2893
|
+
globalCopy = fs9.readFileSync(path9.join(globalDir, rule));
|
|
2053
2894
|
verdict = comparePair(projectCopy, globalCopy);
|
|
2054
2895
|
} catch {
|
|
2055
2896
|
out.shared -= 1;
|
|
@@ -2194,11 +3035,11 @@ function render2(d) {
|
|
|
2194
3035
|
}
|
|
2195
3036
|
return lines.join("\n");
|
|
2196
3037
|
}
|
|
2197
|
-
function
|
|
3038
|
+
function main9(argv) {
|
|
2198
3039
|
const args = argv ?? process2.argv.slice(2);
|
|
2199
|
-
let projectDir =
|
|
2200
|
-
let globalDir =
|
|
2201
|
-
let sourceDir =
|
|
3040
|
+
let projectDir = path9.join(REPO_ROOT2, PROJECT_RULES);
|
|
3041
|
+
let globalDir = path9.join(os3.homedir(), GLOBAL_RULES);
|
|
3042
|
+
let sourceDir = path9.join(REPO_ROOT2, PROJECTION_SOURCE);
|
|
2202
3043
|
const value = (i) => {
|
|
2203
3044
|
const v = args[i + 1];
|
|
2204
3045
|
return v === void 0 || v.startsWith("-") ? null : v;
|
|
@@ -2212,7 +3053,7 @@ function main7(argv) {
|
|
|
2212
3053
|
`);
|
|
2213
3054
|
return 1;
|
|
2214
3055
|
}
|
|
2215
|
-
const resolved =
|
|
3056
|
+
const resolved = path9.resolve(v);
|
|
2216
3057
|
if (a === "--project") projectDir = resolved;
|
|
2217
3058
|
else if (a === "--global") globalDir = resolved;
|
|
2218
3059
|
else sourceDir = resolved;
|
|
@@ -2232,44 +3073,44 @@ function main7(argv) {
|
|
|
2232
3073
|
`);
|
|
2233
3074
|
return 0;
|
|
2234
3075
|
}
|
|
2235
|
-
function
|
|
3076
|
+
function _isCliEntry9() {
|
|
2236
3077
|
if (process2.argv[1] === void 0) return false;
|
|
2237
|
-
if (import.meta.url ===
|
|
3078
|
+
if (import.meta.url === pathToFileURL9(path9.resolve(process2.argv[1])).href) return true;
|
|
2238
3079
|
try {
|
|
2239
|
-
return
|
|
3080
|
+
return fs9.realpathSync(_HERE) === fs9.realpathSync(path9.resolve(process2.argv[1]));
|
|
2240
3081
|
} catch {
|
|
2241
3082
|
return false;
|
|
2242
3083
|
}
|
|
2243
3084
|
}
|
|
2244
|
-
if (
|
|
2245
|
-
process2.exit(
|
|
3085
|
+
if (_isCliEntry9()) {
|
|
3086
|
+
process2.exit(main9());
|
|
2246
3087
|
}
|
|
2247
3088
|
|
|
2248
3089
|
// src/scripts/report_skill_activation.ts
|
|
2249
|
-
import * as
|
|
2250
|
-
import * as
|
|
2251
|
-
import * as
|
|
2252
|
-
import { fileURLToPath as
|
|
2253
|
-
var _HERE2 =
|
|
2254
|
-
var REPO_ROOT3 =
|
|
3090
|
+
import * as fs10 from "node:fs";
|
|
3091
|
+
import * as os4 from "node:os";
|
|
3092
|
+
import * as path10 from "node:path";
|
|
3093
|
+
import { fileURLToPath as fileURLToPath10, pathToFileURL as pathToFileURL10 } from "node:url";
|
|
3094
|
+
var _HERE2 = fileURLToPath10(import.meta.url);
|
|
3095
|
+
var REPO_ROOT3 = path10.resolve(path10.dirname(_HERE2), "..", "..");
|
|
2255
3096
|
var SKILLS_ROOT = "src/skills";
|
|
2256
3097
|
var TRIGGER_KEYS = ["triggers:", "trigger_description:", "file_pattern:", "path_prefix:"];
|
|
2257
3098
|
var DETERMINISTIC_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?(MUST|NEVER|ALWAYS)\b/m;
|
|
2258
3099
|
function censusSkills(root) {
|
|
2259
3100
|
const out = { total: 0, withTriggerKey: [], withDeterministicObligation: [] };
|
|
2260
|
-
if (!
|
|
3101
|
+
if (!fs10.existsSync(root)) {
|
|
2261
3102
|
return out;
|
|
2262
3103
|
}
|
|
2263
|
-
for (const entry of
|
|
3104
|
+
for (const entry of fs10.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2264
3105
|
if (!entry.isDirectory()) {
|
|
2265
3106
|
continue;
|
|
2266
3107
|
}
|
|
2267
|
-
const file =
|
|
2268
|
-
if (!
|
|
3108
|
+
const file = path10.join(root, entry.name, "SKILL.md");
|
|
3109
|
+
if (!fs10.existsSync(file)) {
|
|
2269
3110
|
continue;
|
|
2270
3111
|
}
|
|
2271
3112
|
out.total += 1;
|
|
2272
|
-
const text =
|
|
3113
|
+
const text = fs10.readFileSync(file, "utf8");
|
|
2273
3114
|
const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
|
|
2274
3115
|
const fm = fmEnd === -1 ? "" : text.slice(0, fmEnd);
|
|
2275
3116
|
if (TRIGGER_KEYS.some((k) => new RegExp(`^${k}`, "m").test(fm))) {
|
|
@@ -2283,17 +3124,17 @@ function censusSkills(root) {
|
|
|
2283
3124
|
return out;
|
|
2284
3125
|
}
|
|
2285
3126
|
function defaultStore2(cwd) {
|
|
2286
|
-
return
|
|
3127
|
+
return path10.join(os4.homedir(), ".claude", "projects", cwd.replace(/[/.]/g, "-"));
|
|
2287
3128
|
}
|
|
2288
3129
|
function measureUsage(store, limit) {
|
|
2289
3130
|
const rep = { store, sessions: 0, assistantTurns: 0, invocations: 0, bySkill: {} };
|
|
2290
|
-
if (!
|
|
3131
|
+
if (!fs10.existsSync(store)) {
|
|
2291
3132
|
return rep;
|
|
2292
3133
|
}
|
|
2293
|
-
const files =
|
|
3134
|
+
const files = fs10.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path10.join(store, f)).sort((a, b) => fs10.statSync(b).mtimeMs - fs10.statSync(a).mtimeMs).slice(0, limit);
|
|
2294
3135
|
rep.sessions = files.length;
|
|
2295
3136
|
for (const file of files) {
|
|
2296
|
-
for (const line of
|
|
3137
|
+
for (const line of fs10.readFileSync(file, "utf8").split("\n")) {
|
|
2297
3138
|
if (!line.trim()) {
|
|
2298
3139
|
continue;
|
|
2299
3140
|
}
|
|
@@ -2356,7 +3197,7 @@ function render3(census, usage) {
|
|
|
2356
3197
|
}
|
|
2357
3198
|
totalInv += u.invocations;
|
|
2358
3199
|
lines.push(
|
|
2359
|
-
` ${
|
|
3200
|
+
` ${path10.basename(u.store).slice(-38).padEnd(38)} sessions=${String(u.sessions).padStart(3)} asst=${String(u.assistantTurns).padStart(6)} Skill calls=${String(u.invocations).padStart(3)}`
|
|
2360
3201
|
);
|
|
2361
3202
|
}
|
|
2362
3203
|
lines.push("");
|
|
@@ -2381,7 +3222,7 @@ function render3(census, usage) {
|
|
|
2381
3222
|
lines.push(" catalogue once per session, then count descriptions against bare names.");
|
|
2382
3223
|
return lines.join("\n");
|
|
2383
3224
|
}
|
|
2384
|
-
function
|
|
3225
|
+
function main10(argv) {
|
|
2385
3226
|
let limit = 30;
|
|
2386
3227
|
const stores = [];
|
|
2387
3228
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -2404,24 +3245,24 @@ function main8(argv) {
|
|
|
2404
3245
|
if (stores.length === 0) {
|
|
2405
3246
|
stores.push(defaultStore2(REPO_ROOT3));
|
|
2406
3247
|
}
|
|
2407
|
-
const census = censusSkills(
|
|
3248
|
+
const census = censusSkills(path10.join(REPO_ROOT3, SKILLS_ROOT));
|
|
2408
3249
|
const usage = stores.map((s) => measureUsage(s, limit));
|
|
2409
3250
|
process.stdout.write(`${render3(census, usage)}
|
|
2410
3251
|
`);
|
|
2411
3252
|
return 0;
|
|
2412
3253
|
}
|
|
2413
|
-
if (process.argv[1] !== void 0 && import.meta.url ===
|
|
2414
|
-
process.exit(
|
|
3254
|
+
if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL10(path10.resolve(process.argv[1])).href) {
|
|
3255
|
+
process.exit(main10(process.argv.slice(2)));
|
|
2415
3256
|
}
|
|
2416
3257
|
|
|
2417
3258
|
// src/scripts/report_skill_obligation_violations.ts
|
|
2418
|
-
import * as
|
|
2419
|
-
import * as
|
|
2420
|
-
import * as
|
|
3259
|
+
import * as fs11 from "node:fs";
|
|
3260
|
+
import * as os5 from "node:os";
|
|
3261
|
+
import * as path11 from "node:path";
|
|
2421
3262
|
import process3 from "node:process";
|
|
2422
|
-
import { fileURLToPath as
|
|
2423
|
-
var _HERE3 =
|
|
2424
|
-
var REPO_ROOT4 =
|
|
3263
|
+
import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL11 } from "node:url";
|
|
3264
|
+
var _HERE3 = fileURLToPath11(import.meta.url);
|
|
3265
|
+
var REPO_ROOT4 = path11.resolve(path11.dirname(_HERE3), "..", "..");
|
|
2425
3266
|
var OBLIGATION_LINE_RE = new RegExp(`${DETERMINISTIC_RE.source}.*`, "gm");
|
|
2426
3267
|
var PATH_RE = /`([A-Za-z0-9_./-]*\/[A-Za-z0-9_.-]+\.[a-z]{2,6})`/g;
|
|
2427
3268
|
var CMD_RE = /`([a-z][a-z0-9-]*(?: [a-z][a-z0-9:._-]*){1,3})`/g;
|
|
@@ -2459,7 +3300,7 @@ function _artefacts(line) {
|
|
|
2459
3300
|
return out;
|
|
2460
3301
|
}
|
|
2461
3302
|
function extractObligations(repoRoot) {
|
|
2462
|
-
const skillsRoot =
|
|
3303
|
+
const skillsRoot = path11.join(repoRoot, SKILLS_ROOT);
|
|
2463
3304
|
const census = censusSkills(skillsRoot);
|
|
2464
3305
|
const out = {
|
|
2465
3306
|
skills: census.withDeterministicObligation,
|
|
@@ -2473,7 +3314,7 @@ function extractObligations(repoRoot) {
|
|
|
2473
3314
|
for (const skill of census.withDeterministicObligation) {
|
|
2474
3315
|
let text;
|
|
2475
3316
|
try {
|
|
2476
|
-
text =
|
|
3317
|
+
text = fs11.readFileSync(path11.join(skillsRoot, skill, "SKILL.md"), "utf-8");
|
|
2477
3318
|
} catch {
|
|
2478
3319
|
continue;
|
|
2479
3320
|
}
|
|
@@ -2589,7 +3430,7 @@ function scanSessionForViolations(session, lines, forbidden, parsed) {
|
|
|
2589
3430
|
return flags;
|
|
2590
3431
|
}
|
|
2591
3432
|
function defaultStore3(cwd) {
|
|
2592
|
-
return
|
|
3433
|
+
return path11.join(os5.homedir(), ".claude", "projects", cwd.replace(/[/.]/g, "-"));
|
|
2593
3434
|
}
|
|
2594
3435
|
function scanStore2(repoRoot, store, limit) {
|
|
2595
3436
|
const census = extractObligations(repoRoot);
|
|
@@ -2602,14 +3443,14 @@ function scanStore2(repoRoot, store, limit) {
|
|
|
2602
3443
|
};
|
|
2603
3444
|
let files;
|
|
2604
3445
|
try {
|
|
2605
|
-
files =
|
|
3446
|
+
files = fs11.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path11.join(store, f), m: fs11.statSync(path11.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
|
|
2606
3447
|
} catch {
|
|
2607
3448
|
return report;
|
|
2608
3449
|
}
|
|
2609
3450
|
report.sessions = files.length;
|
|
2610
3451
|
for (const file of files) {
|
|
2611
|
-
const lines =
|
|
2612
|
-
const id =
|
|
3452
|
+
const lines = fs11.readFileSync(file, "utf-8").split("\n");
|
|
3453
|
+
const id = path11.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
|
|
2613
3454
|
const entries = _parseSession(lines);
|
|
2614
3455
|
if (loadedSkillsFrom(entries).size > 0) report.sessionsWithASkill += 1;
|
|
2615
3456
|
report.flags.push(...scanSessionForViolations(id, lines, census.forbidden, entries));
|
|
@@ -2679,7 +3520,7 @@ function render4(r) {
|
|
|
2679
3520
|
lines.push(" ships as detection-only, and this one says so.");
|
|
2680
3521
|
return lines.join("\n");
|
|
2681
3522
|
}
|
|
2682
|
-
function
|
|
3523
|
+
function main11(argv) {
|
|
2683
3524
|
const args = argv ?? process3.argv.slice(2);
|
|
2684
3525
|
let limit = 30;
|
|
2685
3526
|
let json = false;
|
|
@@ -2732,35 +3573,35 @@ function main9(argv) {
|
|
|
2732
3573
|
}
|
|
2733
3574
|
return 0;
|
|
2734
3575
|
}
|
|
2735
|
-
function
|
|
3576
|
+
function _isCliEntry10() {
|
|
2736
3577
|
if (process3.argv[1] === void 0) return false;
|
|
2737
|
-
if (import.meta.url ===
|
|
3578
|
+
if (import.meta.url === pathToFileURL11(path11.resolve(process3.argv[1])).href) return true;
|
|
2738
3579
|
try {
|
|
2739
|
-
return
|
|
3580
|
+
return fs11.realpathSync(_HERE3) === fs11.realpathSync(path11.resolve(process3.argv[1]));
|
|
2740
3581
|
} catch {
|
|
2741
3582
|
return false;
|
|
2742
3583
|
}
|
|
2743
3584
|
}
|
|
2744
|
-
if (
|
|
2745
|
-
process3.exit(
|
|
3585
|
+
if (_isCliEntry10()) {
|
|
3586
|
+
process3.exit(main11());
|
|
2746
3587
|
}
|
|
2747
3588
|
|
|
2748
3589
|
// src/scripts/report_conformance_funnel.ts
|
|
2749
|
-
var _HERE4 =
|
|
2750
|
-
var REPO_ROOT5 =
|
|
3590
|
+
var _HERE4 = fileURLToPath12(import.meta.url);
|
|
3591
|
+
var REPO_ROOT5 = path12.resolve(path12.dirname(_HERE4), "..", "..");
|
|
2751
3592
|
function buildFunnel(opts = {}) {
|
|
2752
3593
|
const repoRoot = opts.repoRoot ?? REPO_ROOT5;
|
|
2753
|
-
const projectRulesDir = opts.projectRulesDir ??
|
|
2754
|
-
const globalRulesDir = opts.globalRulesDir ??
|
|
2755
|
-
const sourceDir = opts.sourceDir ??
|
|
3594
|
+
const projectRulesDir = opts.projectRulesDir ?? path12.join(repoRoot, PROJECT_RULES);
|
|
3595
|
+
const globalRulesDir = opts.globalRulesDir ?? path12.join(os6.homedir(), GLOBAL_RULES);
|
|
3596
|
+
const sourceDir = opts.sourceDir ?? path12.join(repoRoot, PROJECTION_SOURCE);
|
|
2756
3597
|
const store = opts.store ?? defaultStore(repoRoot);
|
|
2757
3598
|
const limit = opts.limit ?? 30;
|
|
2758
3599
|
return {
|
|
2759
3600
|
delivered: measureDelivered(projectRulesDir, globalRulesDir),
|
|
2760
3601
|
divergence: compareCarriers(projectRulesDir, globalRulesDir, sourceDir),
|
|
2761
|
-
skills: censusSkills(
|
|
3602
|
+
skills: censusSkills(path12.join(repoRoot, SKILLS_ROOT)),
|
|
2762
3603
|
usage: measureUsage(store, limit),
|
|
2763
|
-
storePresent:
|
|
3604
|
+
storePresent: fs12.existsSync(store),
|
|
2764
3605
|
sk2: scanStore2(repoRoot, store, limit)
|
|
2765
3606
|
};
|
|
2766
3607
|
}
|
|
@@ -2885,7 +3726,7 @@ function render5(r) {
|
|
|
2885
3726
|
lines.push("workflow without a measured false-positive rate first (locked policy).");
|
|
2886
3727
|
return lines.join("\n");
|
|
2887
3728
|
}
|
|
2888
|
-
function
|
|
3729
|
+
function main12(argv) {
|
|
2889
3730
|
const args = argv ?? process4.argv.slice(2);
|
|
2890
3731
|
const opts = {};
|
|
2891
3732
|
const value = (i) => {
|
|
@@ -2901,7 +3742,7 @@ function main10(argv) {
|
|
|
2901
3742
|
`);
|
|
2902
3743
|
return 1;
|
|
2903
3744
|
}
|
|
2904
|
-
const resolved =
|
|
3745
|
+
const resolved = path12.resolve(v);
|
|
2905
3746
|
if (a === "--store") opts.store = resolved;
|
|
2906
3747
|
else if (a === "--project") opts.projectRulesDir = resolved;
|
|
2907
3748
|
else if (a === "--global") opts.globalRulesDir = resolved;
|
|
@@ -2931,21 +3772,21 @@ function main10(argv) {
|
|
|
2931
3772
|
`);
|
|
2932
3773
|
return 0;
|
|
2933
3774
|
}
|
|
2934
|
-
function
|
|
3775
|
+
function _isCliEntry11() {
|
|
2935
3776
|
if (process4.argv[1] === void 0) return false;
|
|
2936
|
-
if (import.meta.url ===
|
|
3777
|
+
if (import.meta.url === pathToFileURL12(path12.resolve(process4.argv[1])).href) return true;
|
|
2937
3778
|
try {
|
|
2938
|
-
return
|
|
3779
|
+
return fs12.realpathSync(_HERE4) === fs12.realpathSync(path12.resolve(process4.argv[1]));
|
|
2939
3780
|
} catch {
|
|
2940
3781
|
return false;
|
|
2941
3782
|
}
|
|
2942
3783
|
}
|
|
2943
|
-
if (
|
|
2944
|
-
process4.exit(
|
|
3784
|
+
if (_isCliEntry11()) {
|
|
3785
|
+
process4.exit(main12());
|
|
2945
3786
|
}
|
|
2946
3787
|
|
|
2947
3788
|
// src/scripts/_cli/cmd_conformance.ts
|
|
2948
|
-
var _HERE5 =
|
|
3789
|
+
var _HERE5 = fileURLToPath13(import.meta.url);
|
|
2949
3790
|
var PACKAGE_ROOT = resolvePackageRoot(import.meta.url);
|
|
2950
3791
|
var CONFORMANCE_CHECK_IDS = [
|
|
2951
3792
|
"txlog-clean",
|
|
@@ -2968,17 +3809,17 @@ function conformanceLogPath() {
|
|
|
2968
3809
|
if (override && override.trim().length > 0) {
|
|
2969
3810
|
return override;
|
|
2970
3811
|
}
|
|
2971
|
-
return
|
|
3812
|
+
return path13.join(os7.homedir(), ".event4u", "agent-config", "conformance-log.jsonl");
|
|
2972
3813
|
}
|
|
2973
3814
|
function installLogPath() {
|
|
2974
3815
|
const override = process5.env["AGENT_CONFIG_INSTALL_LOG"];
|
|
2975
3816
|
if (override && override.trim().length > 0) {
|
|
2976
3817
|
return override;
|
|
2977
3818
|
}
|
|
2978
|
-
return
|
|
3819
|
+
return path13.join(os7.homedir(), ".event4u", "agent-config", "install-log.jsonl");
|
|
2979
3820
|
}
|
|
2980
3821
|
function _check_txlog_clean(logPath = installLogPath()) {
|
|
2981
|
-
if (!
|
|
3822
|
+
if (!fs13.existsSync(logPath)) {
|
|
2982
3823
|
return {
|
|
2983
3824
|
id: "txlog-clean",
|
|
2984
3825
|
status: "ok",
|
|
@@ -3012,11 +3853,11 @@ function _check_txlog_clean(logPath = installLogPath()) {
|
|
|
3012
3853
|
};
|
|
3013
3854
|
}
|
|
3014
3855
|
function routeTargetPaths(target) {
|
|
3015
|
-
return routeTargetPathsPosix(target).map((rel) =>
|
|
3856
|
+
return routeTargetPathsPosix(target).map((rel) => path13.join(...rel.split("/")));
|
|
3016
3857
|
}
|
|
3017
3858
|
function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
3018
|
-
const routerPath =
|
|
3019
|
-
if (!
|
|
3859
|
+
const routerPath = path13.join(packageRoot, "dist", "router.json");
|
|
3860
|
+
if (!fs13.existsSync(routerPath)) {
|
|
3020
3861
|
return {
|
|
3021
3862
|
id: "router-pointers",
|
|
3022
3863
|
status: "fail",
|
|
@@ -3026,7 +3867,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
3026
3867
|
}
|
|
3027
3868
|
let router;
|
|
3028
3869
|
try {
|
|
3029
|
-
router = JSON.parse(
|
|
3870
|
+
router = JSON.parse(fs13.readFileSync(routerPath, "utf8"));
|
|
3030
3871
|
} catch (exc) {
|
|
3031
3872
|
return {
|
|
3032
3873
|
id: "router-pointers",
|
|
@@ -3035,13 +3876,13 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
3035
3876
|
remedy: "reinstall the package \u2014 dist/router.json is corrupt"
|
|
3036
3877
|
};
|
|
3037
3878
|
}
|
|
3038
|
-
const rulesDir =
|
|
3879
|
+
const rulesDir = path13.join(packageRoot, "dist", "agent-src", "rules");
|
|
3039
3880
|
const broken = [];
|
|
3040
3881
|
let ids = 0;
|
|
3041
3882
|
let targets = 0;
|
|
3042
3883
|
for (const kernelId of router["kernel"] ?? []) {
|
|
3043
3884
|
ids += 1;
|
|
3044
|
-
if (!
|
|
3885
|
+
if (!fs13.existsSync(path13.join(rulesDir, `${kernelId}.md`))) {
|
|
3045
3886
|
broken.push(`kernel:${kernelId} \u2192 rules/${kernelId}.md missing`);
|
|
3046
3887
|
}
|
|
3047
3888
|
}
|
|
@@ -3049,7 +3890,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
3049
3890
|
for (const entry of router[tier] ?? []) {
|
|
3050
3891
|
const ruleId = String(entry["id"] ?? "");
|
|
3051
3892
|
ids += 1;
|
|
3052
|
-
if (!
|
|
3893
|
+
if (!fs13.existsSync(path13.join(rulesDir, `${ruleId}.md`))) {
|
|
3053
3894
|
broken.push(`${tier}:${ruleId} \u2192 rules/${ruleId}.md missing`);
|
|
3054
3895
|
}
|
|
3055
3896
|
for (const target of entry["routes_to"] ?? []) {
|
|
@@ -3059,7 +3900,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
3059
3900
|
broken.push(`${ruleId} \u2192 unparseable routes_to ${JSON.stringify(target)}`);
|
|
3060
3901
|
continue;
|
|
3061
3902
|
}
|
|
3062
|
-
if (!candidates.some((rel) =>
|
|
3903
|
+
if (!candidates.some((rel) => fs13.existsSync(path13.join(packageRoot, rel)))) {
|
|
3063
3904
|
broken.push(`${ruleId} \u2192 ${target} (${candidates[0]} missing)`);
|
|
3064
3905
|
}
|
|
3065
3906
|
}
|
|
@@ -3091,9 +3932,9 @@ function detectSmokePlatform(projectRoot) {
|
|
|
3091
3932
|
return "claude";
|
|
3092
3933
|
}
|
|
3093
3934
|
function dispatcherRunner(packageRoot) {
|
|
3094
|
-
const localTsx =
|
|
3095
|
-
const script =
|
|
3096
|
-
if (
|
|
3935
|
+
const localTsx = path13.join(packageRoot, "node_modules", ".bin", "tsx");
|
|
3936
|
+
const script = path13.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
|
|
3937
|
+
if (fs13.existsSync(localTsx)) {
|
|
3097
3938
|
return [localTsx, script];
|
|
3098
3939
|
}
|
|
3099
3940
|
return ["npx", "tsx", script];
|
|
@@ -3110,7 +3951,7 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
|
|
|
3110
3951
|
...event === "stop" ? { stop_reason: "conformance-smoke" } : { source: "startup" }
|
|
3111
3952
|
});
|
|
3112
3953
|
const argv = [...runner, "--platform", platform, "--event", event];
|
|
3113
|
-
const proc =
|
|
3954
|
+
const proc = spawnSync2(argv[0], argv.slice(1), {
|
|
3114
3955
|
input: payload,
|
|
3115
3956
|
cwd: projectRoot,
|
|
3116
3957
|
encoding: "utf8",
|
|
@@ -3143,12 +3984,12 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
|
|
|
3143
3984
|
};
|
|
3144
3985
|
}
|
|
3145
3986
|
function readSettingsMode(projectRoot) {
|
|
3146
|
-
const settingsPath =
|
|
3147
|
-
if (!
|
|
3987
|
+
const settingsPath = path13.join(projectRoot, ".agent-settings.yml");
|
|
3988
|
+
if (!fs13.existsSync(settingsPath)) {
|
|
3148
3989
|
return "eager-all";
|
|
3149
3990
|
}
|
|
3150
3991
|
try {
|
|
3151
|
-
const parsed = YAML.parse(
|
|
3992
|
+
const parsed = YAML.parse(fs13.readFileSync(settingsPath, "utf8"));
|
|
3152
3993
|
const lean = parsed?.["lean_projection"] ?? null;
|
|
3153
3994
|
const mode = lean?.["mode"];
|
|
3154
3995
|
return typeof mode === "string" && mode.length > 0 ? mode : "eager-all";
|
|
@@ -3159,7 +4000,7 @@ function readSettingsMode(projectRoot) {
|
|
|
3159
4000
|
function kernelIdsFromRouter(packageRoot) {
|
|
3160
4001
|
try {
|
|
3161
4002
|
const router = JSON.parse(
|
|
3162
|
-
|
|
4003
|
+
fs13.readFileSync(path13.join(packageRoot, "dist", "router.json"), "utf8")
|
|
3163
4004
|
);
|
|
3164
4005
|
return new Set((router["kernel"] ?? []).map(String));
|
|
3165
4006
|
} catch {
|
|
@@ -3176,8 +4017,8 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
3176
4017
|
remedy: "set lean_projection.mode to 'eager-all' or 'thin' in .agent-settings.yml"
|
|
3177
4018
|
};
|
|
3178
4019
|
}
|
|
3179
|
-
const rulesDir = projectedRulesDir ??
|
|
3180
|
-
if (!
|
|
4020
|
+
const rulesDir = projectedRulesDir ?? path13.join(projectRoot, ".augment", "rules");
|
|
4021
|
+
if (!fs13.existsSync(rulesDir)) {
|
|
3181
4022
|
return {
|
|
3182
4023
|
id: "lean-projection",
|
|
3183
4024
|
status: "skipped",
|
|
@@ -3186,7 +4027,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
3186
4027
|
};
|
|
3187
4028
|
}
|
|
3188
4029
|
const kernel = kernelIdsFromRouter(packageRoot);
|
|
3189
|
-
const files =
|
|
4030
|
+
const files = fs13.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
|
|
3190
4031
|
const nonKernel = files.filter((f) => !kernel.has(f.replace(/\.md$/, "")));
|
|
3191
4032
|
if (nonKernel.length === 0) {
|
|
3192
4033
|
return {
|
|
@@ -3200,7 +4041,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
3200
4041
|
for (const f of nonKernel) {
|
|
3201
4042
|
let text = "";
|
|
3202
4043
|
try {
|
|
3203
|
-
text =
|
|
4044
|
+
text = fs13.readFileSync(path13.join(rulesDir, f), "utf8");
|
|
3204
4045
|
} catch {
|
|
3205
4046
|
continue;
|
|
3206
4047
|
}
|
|
@@ -3231,8 +4072,8 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
3231
4072
|
function _check_host_manifest(projectRoot) {
|
|
3232
4073
|
const detected = Object.entries(detectToolPresence(projectRoot)).filter(([, v]) => v === true).map(([k]) => k);
|
|
3233
4074
|
const detectedNote = detected.length > 0 ? `detected surfaces: ${detected.join(", ")}` : "no surfaces detected";
|
|
3234
|
-
const settingsPath =
|
|
3235
|
-
if (!
|
|
4075
|
+
const settingsPath = path13.join(projectRoot, ".agent-settings.yml");
|
|
4076
|
+
if (!fs13.existsSync(settingsPath)) {
|
|
3236
4077
|
return {
|
|
3237
4078
|
id: "host-manifest",
|
|
3238
4079
|
status: "ok",
|
|
@@ -3242,7 +4083,7 @@ function _check_host_manifest(projectRoot) {
|
|
|
3242
4083
|
}
|
|
3243
4084
|
let parsed = null;
|
|
3244
4085
|
try {
|
|
3245
|
-
parsed = YAML.parse(
|
|
4086
|
+
parsed = YAML.parse(fs13.readFileSync(settingsPath, "utf8"));
|
|
3246
4087
|
} catch (exc) {
|
|
3247
4088
|
return {
|
|
3248
4089
|
id: "host-manifest",
|
|
@@ -3341,7 +4182,7 @@ function _parse(argv) {
|
|
|
3341
4182
|
}
|
|
3342
4183
|
return opts;
|
|
3343
4184
|
}
|
|
3344
|
-
function
|
|
4185
|
+
function main13(argv = null) {
|
|
3345
4186
|
const opts = _parse(argv !== null ? Array.from(argv) : process5.argv.slice(2));
|
|
3346
4187
|
if (opts.funnel) {
|
|
3347
4188
|
print(render5(buildFunnel()));
|
|
@@ -3412,25 +4253,25 @@ function main11(argv = null) {
|
|
|
3412
4253
|
}
|
|
3413
4254
|
return failed ? 1 : 0;
|
|
3414
4255
|
}
|
|
3415
|
-
function
|
|
4256
|
+
function _isCliEntry12() {
|
|
3416
4257
|
if (process5.argv[1] === void 0) {
|
|
3417
4258
|
return false;
|
|
3418
4259
|
}
|
|
3419
|
-
const argvUrl =
|
|
4260
|
+
const argvUrl = pathToFileURL13(path13.resolve(process5.argv[1])).href;
|
|
3420
4261
|
if (import.meta.url === argvUrl) {
|
|
3421
4262
|
return true;
|
|
3422
4263
|
}
|
|
3423
4264
|
try {
|
|
3424
|
-
const here =
|
|
3425
|
-
const argv =
|
|
4265
|
+
const here = fs13.realpathSync(fileURLToPath13(import.meta.url));
|
|
4266
|
+
const argv = fs13.realpathSync(path13.resolve(process5.argv[1]));
|
|
3426
4267
|
return here === argv;
|
|
3427
4268
|
} catch {
|
|
3428
4269
|
return false;
|
|
3429
4270
|
}
|
|
3430
4271
|
}
|
|
3431
|
-
if (
|
|
4272
|
+
if (_isCliEntry12() || process5.argv[1] === _HERE5) {
|
|
3432
4273
|
try {
|
|
3433
|
-
process5.exitCode =
|
|
4274
|
+
process5.exitCode = main13(process5.argv.slice(2));
|
|
3434
4275
|
} catch (e) {
|
|
3435
4276
|
if (e instanceof ArgparseExit) {
|
|
3436
4277
|
process5.exitCode = e.code;
|
|
@@ -3453,7 +4294,7 @@ export {
|
|
|
3453
4294
|
conformanceLogPath,
|
|
3454
4295
|
detectSmokePlatform,
|
|
3455
4296
|
installLogPath,
|
|
3456
|
-
|
|
4297
|
+
main13 as main,
|
|
3457
4298
|
routeTargetPaths,
|
|
3458
4299
|
runConformanceChecks
|
|
3459
4300
|
};
|