@event4u/agent-config 9.28.0 → 9.30.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/AGENTS.md +6 -6
- package/CHANGELOG.md +172 -5
- package/README.md +2 -1
- package/agents/templates/.ai-council.yml.example +55 -24
- package/dist/agent-src/commands/analyze/inbox.md +22 -1
- package/dist/agent-src/commands/cost/report.md +2 -2
- package/dist/agent-src/commands/explain-run.md +31 -1
- package/dist/agent-src/commands/team/adversarial.md +8 -7
- package/dist/agent-src/commands/team/delegate.md +14 -12
- package/dist/agent-src/commands/team/review.md +9 -8
- package/dist/agent-src/commands/team/status.md +9 -8
- package/dist/agent-src/commands/team.md +29 -19
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +88 -2
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +29 -21
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +26 -15
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +22 -1
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +17 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +65 -1
- package/dist/agent-src/contexts/execution/subagent-routing.md +13 -11
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +25 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +6 -3
- package/dist/agent-src/rules/delegation-policy.md +11 -7
- package/dist/agent-src/rules/preservation-guard.md +9 -0
- package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
- package/dist/agent-src/rules/token-budget-discipline.md +1 -1
- package/dist/agent-src/scripts/roadmap_gates.ts +312 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/dist/agent-src/skills/forensics-report/SKILL.md +114 -0
- package/dist/agent-src/skills/forensics-report/evals/triggers.json +17 -0
- package/dist/agent-src/skills/premortem/SKILL.md +30 -1
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +17 -5
- package/dist/agent-src/skills/subagent-orchestration/schemas/subagent-status.json +61 -3
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
- package/dist/agent-src/templates/AGENTS.md +4 -4
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +23 -0
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-22GCIJ2O.js → chunk-66OVWGB5.js} +1 -1
- package/dist/cli-delegate/{chunk-7OMQCH4V.js → chunk-BTJNRUX3.js} +22 -35
- package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-DDUNNFYX.js} +24 -17
- package/dist/cli-delegate/{chunk-PQSAQKHW.js → chunk-EQY4FLJB.js} +3 -3
- package/dist/cli-delegate/{chunk-2BE5I2MN.js → chunk-IYTGIG2Y.js} +155 -91
- package/dist/cli-delegate/{chunk-GB2FSUEE.js → chunk-LCFJIQ4J.js} +32 -0
- package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
- package/dist/cli-delegate/chunk-MMJL4TIX.js +18 -0
- package/dist/cli-delegate/{chunk-JXARCGVN.js → chunk-MP3JT3IU.js} +1 -1
- package/dist/cli-delegate/chunk-O3CO5BFB.js +161 -0
- package/dist/cli-delegate/{chunk-REKCMYOU.js → chunk-PFBV6XRT.js} +36 -20
- package/dist/cli-delegate/chunk-RVGBH4XK.js +142 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +2803 -137
- package/dist/cli-delegate/cmd_doctor.js +10 -5
- 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 +88 -8
- 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 +3 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +177 -0
- package/dist/cli-delegate/cmd_settings_get.js +2 -8
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- 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 +72 -23
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -2
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +39 -2
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +12 -4
- package/dist/hooks/dispatch.js +2278 -611
- package/dist/install/install.mjs +56 -17
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +4122 -699
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +54 -0
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/scripts/condense.js +147 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +12 -7
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/settingsCarveOut.js +0 -9
- package/dist/shared/settingsCarveOut.js.map +1 -1
- package/dist/shared/settingsClasses.js +11 -9
- package/dist/shared/settingsClasses.js.map +1 -1
- package/dist/ui/assets/index-B5qGYOJo.js +26 -0
- package/dist/ui/assets/index-B5qGYOJo.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +58 -12
- package/package.json +1 -1
- package/src/agent-src/scripts/roadmap_gates.ts +312 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/src/agent-src/templates/AGENTS.md +4 -4
- package/src/config/agent-settings.template.yml +88 -66
- package/src/config/discovery/packs.yml +11 -0
- package/src/config/discovery/workspaces.yml +1 -1
- package/src/config/dispatch-economy-metrics.json +61 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/hook-token-budget.json +91 -0
- package/src/config/recycle-threshold-budget.json +12 -0
- package/src/config/rule-enforcement-baseline.json +86 -0
- package/src/scripts/_cli/cmd_conformance.ts +31 -44
- package/src/scripts/_cli/cmd_doctor.ts +65 -48
- package/src/scripts/_cli/cmd_session_recycle.ts +226 -0
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/agent_settings.ts +58 -0
- package/src/scripts/_lib/auto_dispatch.corpus.test.ts +1 -1
- package/src/scripts/_lib/auto_dispatch.ts +23 -18
- package/src/scripts/_lib/capsule_trigger.ts +124 -0
- package/src/scripts/_lib/host_capability.ts +63 -6
- package/src/scripts/_lib/judgment_ladder.ts +634 -0
- package/src/scripts/_lib/orchestration_gate.ts +8 -0
- package/src/scripts/_lib/orchestration_record.ts +78 -10
- package/src/scripts/_lib/orchestration_savings.ts +1 -1
- package/src/scripts/_lib/recycle_envelope_paths.ts +35 -0
- package/src/scripts/_lib/self_repair.ts +354 -13
- package/src/scripts/_lib/self_repair_store.ts +28 -0
- package/src/scripts/_lib/session_eol.ts +230 -0
- package/src/scripts/_lib/session_role.ts +42 -0
- package/src/scripts/_lib/subagent_capsule.ts +341 -0
- package/src/scripts/_lib/subagent_response.ts +94 -0
- package/src/scripts/_lib/subagent_spawn.ts +13 -0
- package/src/scripts/_lib/subagent_steering.ts +9 -4
- package/src/scripts/_lib/tier_budget_routing.ts +6 -1
- package/src/scripts/_lib/worker_budget.ts +37 -2
- package/src/scripts/ai_council/clients.ts +126 -55
- package/src/scripts/ai_council/config.ts +94 -6
- package/src/scripts/ai_council/handoff.ts +113 -0
- package/src/scripts/ai_council/modes.ts +19 -4
- package/src/scripts/ai_council/orchestrator.ts +105 -0
- package/src/scripts/ai_council/quorum.ts +84 -0
- package/src/scripts/ai_council/session.ts +44 -0
- package/src/scripts/ai_council/transport_resolver.ts +127 -1
- package/src/scripts/ai_team/availability.ts +110 -0
- package/src/scripts/ai_team/config.ts +34 -14
- package/src/scripts/ai_team/team_dispatch.ts +71 -29
- package/src/scripts/ask_transport.ts +221 -0
- package/src/scripts/bench_critic_protocol.ts +396 -0
- package/src/scripts/bench_hook_injection.ts +234 -0
- package/src/scripts/check_adr_frontmatter.ts +84 -40
- package/src/scripts/check_branch_freshness.ts +1 -0
- package/src/scripts/check_corpus_staleness.ts +18 -4
- package/src/scripts/check_detector_corpus.ts +219 -0
- package/src/scripts/check_gitignore_freshness.ts +42 -8
- package/src/scripts/check_hook_bundle_freshness.ts +24 -1
- package/src/scripts/check_kernel_prefix_stability.ts +1 -0
- package/src/scripts/check_standing_rule_delivery.ts +73 -32
- package/src/scripts/check_static_layer_stability.ts +154 -0
- package/src/scripts/check_suppression_hygiene.ts +48 -0
- package/src/scripts/condense.ts +162 -2
- package/src/scripts/council_cli.ts +401 -25
- package/src/scripts/dispatch_economy_report.ts +448 -0
- package/src/scripts/dispatch_r2_reviewer.ts +15 -2
- package/src/scripts/explain_run.ts +206 -4
- package/src/scripts/forensics_report.ts +333 -0
- package/src/scripts/generate_settings_reference.ts +1 -0
- package/src/scripts/handoff_context_hook.ts +112 -4
- package/src/scripts/hook_manifest.yaml +117 -10
- package/src/scripts/hooks/concern_registry.ts +8 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +106 -35
- package/src/scripts/hooks/dispatch_hook.ts +39 -2
- package/src/scripts/hooks/edit_shape_hook.ts +303 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +19 -1
- package/src/scripts/hooks/reread_guard_hook.ts +314 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +133 -14
- package/src/scripts/hooks/session_eol_hook.ts +268 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +507 -0
- package/src/scripts/install-hooks.sh +1 -1
- package/src/scripts/language_mirror_hook.ts +89 -0
- package/src/scripts/lint_decision_impact.ts +14 -1
- package/src/scripts/lint_discovery_vocabulary.ts +2 -0
- package/src/scripts/lint_hook_manifest.ts +82 -0
- package/src/scripts/lint_never_silent.ts +276 -0
- package/src/scripts/lint_no_activation_gates.ts +271 -0
- package/src/scripts/lint_readme_serial_comma.ts +1 -0
- package/src/scripts/lint_rule_enforcement_declaration.ts +347 -0
- package/src/scripts/measure_turn_end_gate.ts +194 -0
- package/src/scripts/orchestration_record.ts +12 -1
- package/src/scripts/rebuild_hook_bundle.ts +178 -0
- package/src/scripts/release.ts +74 -2
- package/src/scripts/report_conformance_funnel.ts +284 -0
- package/src/scripts/report_imperative_density.ts +1 -0
- package/src/scripts/routing_doctor.ts +118 -36
- package/src/scripts/score_skill_selection.ts +19 -5
- package/src/scripts/self_repair_cli.ts +337 -59
- package/src/scripts/session_eol_report.ts +261 -0
- package/src/scripts/team_events_spike.ts +271 -0
- package/src/scripts/team_review_gate_hook.ts +31 -9
- package/src/server/schemas/settings.ts +24 -17
- package/src/shared/settingsCarveOut.ts +0 -10
- package/src/shared/settingsClasses.ts +11 -9
- package/src/templates/compact-instructions.md +40 -0
- package/dist/ui/assets/index-DmR0mbk4.js +0 -26
- package/dist/ui/assets/index-DmR0mbk4.js.map +0 -1
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
readHookStdin
|
|
4
|
+
} from "./chunk-MMJL4TIX.js";
|
|
2
5
|
import {
|
|
3
6
|
ArgparseExit,
|
|
4
7
|
STATUS_SYMBOLS,
|
|
@@ -11,9 +14,12 @@ import {
|
|
|
11
14
|
_run_checks,
|
|
12
15
|
_run_checks_no_manifest,
|
|
13
16
|
_scan_foreign
|
|
14
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-IYTGIG2Y.js";
|
|
15
18
|
import "./chunk-FTAISGDQ.js";
|
|
16
|
-
import
|
|
19
|
+
import {
|
|
20
|
+
atomic_write_json
|
|
21
|
+
} from "./chunk-O3CO5BFB.js";
|
|
22
|
+
import "./chunk-LVRYBKTI.js";
|
|
17
23
|
import "./chunk-736JCWLM.js";
|
|
18
24
|
import {
|
|
19
25
|
atomicAppendLine,
|
|
@@ -22,7 +28,7 @@ import {
|
|
|
22
28
|
} from "./chunk-63WSP6YZ.js";
|
|
23
29
|
import "./chunk-GRRPEPRN.js";
|
|
24
30
|
import "./chunk-NXFWFAYV.js";
|
|
25
|
-
import "./chunk-
|
|
31
|
+
import "./chunk-LCFJIQ4J.js";
|
|
26
32
|
import "./chunk-NDJLNC6Q.js";
|
|
27
33
|
import {
|
|
28
34
|
detectToolPresence
|
|
@@ -39,73 +45,2764 @@ import {
|
|
|
39
45
|
|
|
40
46
|
// src/scripts/_cli/cmd_conformance.ts
|
|
41
47
|
var YAML = __toESM(require_dist(), 1);
|
|
48
|
+
import * as fs12 from "node:fs";
|
|
49
|
+
import * as os6 from "node:os";
|
|
50
|
+
import * as path12 from "node:path";
|
|
51
|
+
import process5 from "node:process";
|
|
52
|
+
import { spawnSync } from "node:child_process";
|
|
53
|
+
import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL11 } from "node:url";
|
|
54
|
+
|
|
55
|
+
// src/install/txlog.ts
|
|
56
|
+
import { createReadStream, createWriteStream, existsSync, readFileSync, renameSync, statSync, unlinkSync } from "node:fs";
|
|
57
|
+
var ROTATION_MAX_BYTES = 10 * 1024 * 1024;
|
|
58
|
+
var ROTATION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
59
|
+
var RECOVERY_DEPTH_CAP = 500;
|
|
60
|
+
function readRecentEntries(logPath) {
|
|
61
|
+
if (!existsSync(logPath)) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
const raw = readFileSync(logPath, "utf8");
|
|
65
|
+
const lines = raw.split("\n").filter((l) => l.length > 0);
|
|
66
|
+
const tail = lines.slice(-RECOVERY_DEPTH_CAP);
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const line of tail) {
|
|
69
|
+
const parsed = tryParseEntry(line);
|
|
70
|
+
if (parsed !== null) {
|
|
71
|
+
out.push(parsed);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
function tryParseEntry(line) {
|
|
77
|
+
try {
|
|
78
|
+
const obj = JSON.parse(line);
|
|
79
|
+
if (typeof obj.ts !== "string" || typeof obj.kind !== "string" || typeof obj.path !== "string") {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return obj;
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/scripts/router_target_paths.mjs
|
|
89
|
+
var ROUTE_TARGET_TEMPLATES = Object.freeze({
|
|
90
|
+
skill: Object.freeze(["dist/agent-src/skills/{id}/SKILL.md"]),
|
|
91
|
+
command: Object.freeze(["dist/agent-src/commands/{id}.md"]),
|
|
92
|
+
guideline: Object.freeze(["docs/guidelines/{id}.md"]),
|
|
93
|
+
contract: Object.freeze([
|
|
94
|
+
"docs/contracts/{id}.md",
|
|
95
|
+
"dist/agent-src/contexts/contracts/{id}.md"
|
|
96
|
+
])
|
|
97
|
+
});
|
|
98
|
+
function parseRouteTarget(target) {
|
|
99
|
+
const sep = target.indexOf(":");
|
|
100
|
+
if (sep <= 0) return null;
|
|
101
|
+
const kind = target.slice(0, sep);
|
|
102
|
+
const id = target.slice(sep + 1);
|
|
103
|
+
if (!id) return null;
|
|
104
|
+
if (!Object.prototype.hasOwnProperty.call(ROUTE_TARGET_TEMPLATES, kind)) return null;
|
|
105
|
+
return { kind, id };
|
|
106
|
+
}
|
|
107
|
+
function routeTargetPathsPosix(target) {
|
|
108
|
+
const parsed = parseRouteTarget(target);
|
|
109
|
+
if (parsed === null) return [];
|
|
110
|
+
return ROUTE_TARGET_TEMPLATES[parsed.kind].map((tpl) => tpl.replace("{id}", parsed.id));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/scripts/report_conformance_funnel.ts
|
|
114
|
+
import * as fs11 from "node:fs";
|
|
115
|
+
import * as os5 from "node:os";
|
|
116
|
+
import * as path11 from "node:path";
|
|
117
|
+
import process4 from "node:process";
|
|
118
|
+
import { fileURLToPath as fileURLToPath10, pathToFileURL as pathToFileURL10 } from "node:url";
|
|
119
|
+
|
|
120
|
+
// src/scripts/conformance_scan.ts
|
|
121
|
+
import { createHash } from "node:crypto";
|
|
122
|
+
import fs7 from "node:fs";
|
|
123
|
+
import os from "node:os";
|
|
124
|
+
import path7 from "node:path";
|
|
125
|
+
import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
|
|
126
|
+
|
|
127
|
+
// src/scripts/_lib/carrier_divergence.ts
|
|
42
128
|
import * as fs from "node:fs";
|
|
43
|
-
import * as os from "node:os";
|
|
44
129
|
import * as path from "node:path";
|
|
45
|
-
|
|
46
|
-
|
|
130
|
+
var OWNERSHIP_KEYS = ["package", "source_path"];
|
|
131
|
+
function stripOwnershipKeys(text) {
|
|
132
|
+
return text.split("\n").filter((line) => !OWNERSHIP_KEYS.some((k) => line.startsWith(`${k}:`))).join("\n");
|
|
133
|
+
}
|
|
134
|
+
function comparePair(a, b) {
|
|
135
|
+
if (a.equals(b)) {
|
|
136
|
+
return "identical";
|
|
137
|
+
}
|
|
138
|
+
if (stripOwnershipKeys(a.toString("utf-8")) === stripOwnershipKeys(b.toString("utf-8"))) {
|
|
139
|
+
return "provenance-only";
|
|
140
|
+
}
|
|
141
|
+
return "body-diff";
|
|
142
|
+
}
|
|
143
|
+
function censusRuleDir(dir) {
|
|
144
|
+
let files = 0;
|
|
145
|
+
let chars = 0;
|
|
146
|
+
let entries;
|
|
147
|
+
try {
|
|
148
|
+
entries = fs.readdirSync(dir);
|
|
149
|
+
} catch {
|
|
150
|
+
return { files: 0, chars: 0 };
|
|
151
|
+
}
|
|
152
|
+
for (const name of entries) {
|
|
153
|
+
if (!name.endsWith(".md")) continue;
|
|
154
|
+
try {
|
|
155
|
+
chars += fs.statSync(path.join(dir, name)).size;
|
|
156
|
+
files += 1;
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { files, chars };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/scripts/_lib/transcript_entry.ts
|
|
164
|
+
function _isObject(v) {
|
|
165
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
166
|
+
}
|
|
167
|
+
function isSidechain(entry) {
|
|
168
|
+
return entry["isSidechain"] === true;
|
|
169
|
+
}
|
|
170
|
+
function entryText(entry) {
|
|
171
|
+
const msg = entry["message"];
|
|
172
|
+
const content = _isObject(msg) ? msg["content"] : void 0;
|
|
173
|
+
if (typeof content === "string") {
|
|
174
|
+
return content;
|
|
175
|
+
}
|
|
176
|
+
if (!Array.isArray(content)) {
|
|
177
|
+
return "";
|
|
178
|
+
}
|
|
179
|
+
return content.filter((b) => _isObject(b) && b["type"] === "text").map((b) => String(b["text"] ?? "")).join("\n");
|
|
180
|
+
}
|
|
181
|
+
function toolUses(entry) {
|
|
182
|
+
const msg = entry["message"];
|
|
183
|
+
const content = _isObject(msg) ? msg["content"] : void 0;
|
|
184
|
+
if (!Array.isArray(content)) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
const out = [];
|
|
188
|
+
for (const part of content) {
|
|
189
|
+
if (!_isObject(part) || part["type"] !== "tool_use") {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
out.push({
|
|
193
|
+
name: typeof part["name"] === "string" ? part["name"] : "",
|
|
194
|
+
input: _isObject(part["input"]) ? part["input"] : {}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/scripts/language_mirror_hook.ts
|
|
201
|
+
import fs2 from "node:fs";
|
|
202
|
+
import path2 from "node:path";
|
|
47
203
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
48
204
|
|
|
49
|
-
// src/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
205
|
+
// src/scripts/_lib/prompt_shape.ts
|
|
206
|
+
function isSyntheticPrompt(prompt) {
|
|
207
|
+
const head = prompt.trimStart().slice(0, 400);
|
|
208
|
+
return head.startsWith("[SYSTEM NOTIFICATION - NOT USER INPUT]") || head.startsWith("<task-notification>") || head.startsWith("<system-reminder>") || head.startsWith("<local-command-caveat>") || /^\[SYSTEM NOTIFICATION\b/.test(head);
|
|
209
|
+
}
|
|
210
|
+
var DOCUMENT_HEAD = /^\s*(#{1,6}\s|-{3,}\s*$|={3,}\s*$|<[A-Za-z][\w-]*>\s*$)/;
|
|
211
|
+
function humanAuthoredLead(text) {
|
|
212
|
+
const kept = [];
|
|
213
|
+
for (const line of text.split("\n")) {
|
|
214
|
+
if (DOCUMENT_HEAD.test(line)) {
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
kept.push(line);
|
|
218
|
+
}
|
|
219
|
+
return kept.join("\n");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/scripts/language_mirror_hook.ts
|
|
223
|
+
var EXIT_ALLOW = 0;
|
|
224
|
+
var EXIT_WARN = 2;
|
|
225
|
+
var STATE_FILE = path2.join("agents", "state", "language-mirror.json");
|
|
226
|
+
var PIN_LOST_MARKER = path2.join("agents", "state", "language-mirror.pin-lost");
|
|
227
|
+
function _isObject2(v) {
|
|
228
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
229
|
+
}
|
|
230
|
+
var DE_MARKERS = /\b(der|die|das|den|dem|und|ist|sind|nicht|kein|keine|mach|mache|bitte|warum|wieso|weshalb|weiter|kannst|sollst|soll|musst|muss|hast|habe|haben|wir|ich|du|dann|noch|auch|schon|wenn|aber|alle|mit|ohne|damit|dass|wurde|werden|gibt|nochmal|erstell|erstelle|erstelle|fehler|regeln|arbeite|analysiere|fixe|nimm|lege|prüfe|pruefe|schau|zeig|mir|dir|sich|eine|einen|einem|einer|vom|zum|zur|beim|im)\b|[äöüÄÖÜß]/gi;
|
|
231
|
+
var EN_MARKERS = /\b(the|and|is|are|was|were|not|please|why|because|should|would|could|there|these|those|with|from|about|which|that|this|will|have|has|been|they|them|their|your|our|what|when|where|make|need|want|does|doesn't|don't|it's|let's)\b/gi;
|
|
232
|
+
var MIN_MARKERS = 2;
|
|
233
|
+
function instructionText(prompt) {
|
|
234
|
+
const OUTPUT_HEAD = /^\s*(To\s+\S+|remote:|error:|fatal:|hint:|warning:|npm ERR!|Error:|Traceback|Exception|[A-Za-z]*Error\b|at\s+\S+:\d+|\s{4,}\S|[-+]{3}\s|@@\s|\$\s|>\s|\||\d+\s*\|)/i;
|
|
235
|
+
const kept = [];
|
|
236
|
+
let inPaste = false;
|
|
237
|
+
for (const line of prompt.replace(/```[\s\S]*?```/g, "\n\n").split("\n")) {
|
|
238
|
+
if (!line.trim()) {
|
|
239
|
+
inPaste = false;
|
|
240
|
+
kept.push(line);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (OUTPUT_HEAD.test(line)) {
|
|
244
|
+
inPaste = true;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (inPaste) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
kept.push(line);
|
|
251
|
+
}
|
|
252
|
+
return kept.join("\n");
|
|
253
|
+
}
|
|
254
|
+
function classify(prompt) {
|
|
255
|
+
const text = instructionText(prompt);
|
|
256
|
+
const lead = humanAuthoredLead(text);
|
|
257
|
+
const leadVerdict = _score(lead);
|
|
258
|
+
if (leadVerdict.language !== "und") {
|
|
259
|
+
return leadVerdict;
|
|
260
|
+
}
|
|
261
|
+
return _score(text);
|
|
262
|
+
}
|
|
263
|
+
function _score(text) {
|
|
264
|
+
const de = (text.match(DE_MARKERS) ?? []).length;
|
|
265
|
+
const en = (text.match(EN_MARKERS) ?? []).length;
|
|
266
|
+
if (de < MIN_MARKERS && en < MIN_MARKERS) {
|
|
267
|
+
return { language: "und", de_markers: de, en_markers: en };
|
|
268
|
+
}
|
|
269
|
+
return { language: de >= en ? "de" : "en", de_markers: de, en_markers: en };
|
|
270
|
+
}
|
|
271
|
+
var LANGUAGE_NAME = {
|
|
272
|
+
de: "German (Deutsch)",
|
|
273
|
+
en: "English"
|
|
274
|
+
};
|
|
275
|
+
var LOCALE_ENV_KEYS = ["LC_ALL", "LC_MESSAGES", "LANG", "LANGUAGE"];
|
|
276
|
+
function systemLocaleVerdict(env = process.env) {
|
|
277
|
+
for (const key of LOCALE_ENV_KEYS) {
|
|
278
|
+
const raw = (env[key] ?? "").trim();
|
|
279
|
+
if (!raw) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const first = raw.split(":")[0] ?? "";
|
|
283
|
+
const primary = (first.split(/[._@-]/)[0] ?? "").toLowerCase();
|
|
284
|
+
if (primary === "c" || primary === "posix" || !primary) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (primary === "de") {
|
|
288
|
+
return "de";
|
|
289
|
+
}
|
|
290
|
+
if (primary === "en") {
|
|
291
|
+
return "en";
|
|
292
|
+
}
|
|
293
|
+
return "und";
|
|
294
|
+
}
|
|
295
|
+
return "und";
|
|
296
|
+
}
|
|
297
|
+
function noticeText(language, source) {
|
|
298
|
+
const name = LANGUAGE_NAME[language];
|
|
299
|
+
if (source === "system-locale") {
|
|
300
|
+
return `Reply language for this turn: ${name} \u2014 the prompt carried no language markers, so this fell back to the system locale. Write in another language and the next turn follows the prompt instead.`;
|
|
301
|
+
}
|
|
302
|
+
return `Reply language for this turn: ${name}.`;
|
|
303
|
+
}
|
|
304
|
+
function pinText(language, source = "prompt") {
|
|
305
|
+
const name = LANGUAGE_NAME[language];
|
|
306
|
+
if (source === "system-locale") {
|
|
307
|
+
return `<language-pin>
|
|
308
|
+
No language markers were found in this turn's prompt and no language was pinned yet, so the mirror target falls back to the environment's locale: ${name}. Use it for EVERY user-visible token this turn.
|
|
309
|
+
|
|
310
|
+
This is the WEAKER of the two provenances \u2014 it reflects the machine, not the user's own words. The first prompt that carries real markers replaces it. Do not treat it as a standing preference, and do not argue with the user about it if they write in something else.
|
|
311
|
+
</language-pin>`;
|
|
312
|
+
}
|
|
313
|
+
return `<language-pin>
|
|
314
|
+
The user submitted this turn's prompt in ${name}. That is the mirror target for EVERY user-visible token of your replies this turn \u2014 including the short inter-tool lines between tool calls, headings, table cells, bullets, status lines, and the recommendation label under any numbered-options block.
|
|
315
|
+
|
|
316
|
+
This pin exists because the trigger is not observable from the transcript alone: slash-command and skill bodies arrive in the user role and can be long and English, which is what drove the 626 wrong-language turns measured in the 30-session audit this hook was built from. Tool output, file contents, and an injected skill body are NOT the trigger \u2014 this pin is.
|
|
317
|
+
</language-pin>`;
|
|
318
|
+
}
|
|
319
|
+
function _loadState(target) {
|
|
320
|
+
try {
|
|
321
|
+
const raw = fs2.readFileSync(target, "utf8");
|
|
322
|
+
const decoded = JSON.parse(raw);
|
|
323
|
+
return _isObject2(decoded) ? decoded : {};
|
|
324
|
+
} catch {
|
|
325
|
+
return {};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function nextState(previous, classification, prompt_chars, session_id, now, locale = "und") {
|
|
329
|
+
if (classification.language === "und") {
|
|
330
|
+
if (previous.language === "de" || previous.language === "en") {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
if (locale === "und") {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
language: locale,
|
|
338
|
+
source: "system-locale",
|
|
339
|
+
detected_at: now,
|
|
340
|
+
prompt_chars,
|
|
341
|
+
de_markers: classification.de_markers,
|
|
342
|
+
en_markers: classification.en_markers,
|
|
343
|
+
session_id
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
language: classification.language,
|
|
348
|
+
source: "prompt",
|
|
349
|
+
detected_at: now,
|
|
350
|
+
prompt_chars,
|
|
351
|
+
de_markers: classification.de_markers,
|
|
352
|
+
en_markers: classification.en_markers,
|
|
353
|
+
session_id
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function _extractPrompt(payload) {
|
|
357
|
+
for (const key of ["prompt", "userPrompt", "user_prompt", "message", "text"]) {
|
|
358
|
+
const v = payload[key];
|
|
359
|
+
if (typeof v === "string" && v.trim()) {
|
|
360
|
+
return v;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return "";
|
|
364
|
+
}
|
|
365
|
+
function _setPinLost(consumer_root) {
|
|
366
|
+
try {
|
|
367
|
+
const target = path2.join(consumer_root, PIN_LOST_MARKER);
|
|
368
|
+
fs2.mkdirSync(path2.dirname(target), { recursive: true });
|
|
369
|
+
fs2.writeFileSync(target, (/* @__PURE__ */ new Date()).toISOString());
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function _pinLost(consumer_root) {
|
|
374
|
+
try {
|
|
375
|
+
return fs2.existsSync(path2.join(consumer_root, PIN_LOST_MARKER));
|
|
376
|
+
} catch {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function _clearPinLost(consumer_root) {
|
|
381
|
+
try {
|
|
382
|
+
fs2.rmSync(path2.join(consumer_root, PIN_LOST_MARKER), { force: true });
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function _reEmitAfterCompaction(consumer_root) {
|
|
387
|
+
if (!_pinLost(consumer_root)) {
|
|
388
|
+
return EXIT_ALLOW;
|
|
389
|
+
}
|
|
390
|
+
const previous = _loadState(path2.join(consumer_root, STATE_FILE));
|
|
391
|
+
const language = previous.language;
|
|
392
|
+
const source = previous.source === "system-locale" ? "system-locale" : "prompt";
|
|
393
|
+
_clearPinLost(consumer_root);
|
|
394
|
+
if (language !== "de" && language !== "en") {
|
|
395
|
+
return EXIT_ALLOW;
|
|
396
|
+
}
|
|
397
|
+
process.stdout.write(
|
|
398
|
+
`${JSON.stringify({
|
|
399
|
+
decision: "warn",
|
|
400
|
+
reason: noticeText(language, source),
|
|
401
|
+
additional_context: pinText(language, source)
|
|
402
|
+
})}
|
|
403
|
+
`
|
|
404
|
+
);
|
|
405
|
+
return EXIT_WARN;
|
|
406
|
+
}
|
|
407
|
+
function run(stdin_text, options) {
|
|
408
|
+
let envelope = {};
|
|
409
|
+
if (stdin_text.trim()) {
|
|
410
|
+
try {
|
|
411
|
+
const decoded = JSON.parse(stdin_text);
|
|
412
|
+
if (_isObject2(decoded)) {
|
|
413
|
+
envelope = decoded;
|
|
414
|
+
}
|
|
415
|
+
} catch {
|
|
416
|
+
return EXIT_ALLOW;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const payload = _isObject2(envelope["payload"]) ? envelope["payload"] : envelope;
|
|
420
|
+
const event = typeof envelope["event"] === "string" ? envelope["event"] : "";
|
|
421
|
+
if (event === "pre_compact") {
|
|
422
|
+
_setPinLost(options.consumer_root);
|
|
423
|
+
return EXIT_ALLOW;
|
|
424
|
+
}
|
|
425
|
+
if (event === "post_tool_use") {
|
|
426
|
+
return _reEmitAfterCompaction(options.consumer_root);
|
|
427
|
+
}
|
|
428
|
+
const prompt = _extractPrompt(payload);
|
|
429
|
+
if (!prompt) {
|
|
430
|
+
return EXIT_ALLOW;
|
|
431
|
+
}
|
|
432
|
+
if (isSyntheticPrompt(prompt)) {
|
|
433
|
+
return EXIT_ALLOW;
|
|
434
|
+
}
|
|
435
|
+
const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
|
|
436
|
+
const target = path2.join(options.consumer_root, STATE_FILE);
|
|
437
|
+
const previous = _loadState(target);
|
|
438
|
+
const classification = classify(prompt);
|
|
439
|
+
const next = nextState(
|
|
440
|
+
previous,
|
|
441
|
+
classification,
|
|
442
|
+
prompt.length,
|
|
443
|
+
session_id,
|
|
444
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
445
|
+
// Injectable so the suite can pin a locale instead of inheriting the
|
|
446
|
+
// machine's — a test whose verdict depends on the developer's `LANG` is a
|
|
447
|
+
// test that passes here and fails in CI for reasons unrelated to the diff.
|
|
448
|
+
systemLocaleVerdict(options.env ?? process.env)
|
|
449
|
+
);
|
|
450
|
+
const effective = next?.language ?? previous.language;
|
|
451
|
+
const effectiveSource = next?.source ?? (previous.source === "system-locale" ? "system-locale" : "prompt");
|
|
452
|
+
if (next) {
|
|
453
|
+
try {
|
|
454
|
+
atomic_write_json(target, next);
|
|
455
|
+
} catch {
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (effective === "de" || effective === "en") {
|
|
459
|
+
process.stdout.write(
|
|
460
|
+
`${JSON.stringify({ decision: "warn", reason: noticeText(effective, effectiveSource), additional_context: pinText(effective, effectiveSource) })}
|
|
461
|
+
`
|
|
462
|
+
);
|
|
463
|
+
return EXIT_WARN;
|
|
464
|
+
}
|
|
465
|
+
return EXIT_ALLOW;
|
|
466
|
+
}
|
|
467
|
+
function main(argv) {
|
|
468
|
+
const args = argv ?? process.argv.slice(2);
|
|
469
|
+
let consumer_root = process.cwd();
|
|
470
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
471
|
+
const a = args[i];
|
|
472
|
+
if (a === "--project-dir" && args[i + 1] !== void 0) {
|
|
473
|
+
consumer_root = args[i + 1];
|
|
474
|
+
i += 1;
|
|
475
|
+
} else if (a !== void 0 && a.startsWith("--project-dir=")) {
|
|
476
|
+
consumer_root = a.slice("--project-dir=".length);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return run(readHookStdin(), { consumer_root });
|
|
480
|
+
}
|
|
481
|
+
function _isCliEntry() {
|
|
482
|
+
if (true) {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
if (process.argv[1] === void 0) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
const argvUrl = pathToFileURL(path2.resolve(process.argv[1])).href;
|
|
489
|
+
if (import.meta.url === argvUrl) {
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
const here = fs2.realpathSync(fileURLToPath(import.meta.url));
|
|
494
|
+
const argv = fs2.realpathSync(path2.resolve(process.argv[1]));
|
|
495
|
+
return here === argv;
|
|
496
|
+
} catch {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (_isCliEntry()) {
|
|
501
|
+
process.exit(main());
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/scripts/git_authorization_hook.ts
|
|
505
|
+
import fs3 from "node:fs";
|
|
506
|
+
import path3 from "node:path";
|
|
507
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
508
|
+
var EXIT_ALLOW2 = 0;
|
|
509
|
+
var STATE_FILE2 = path3.join("agents", "state", "git-authorization.json");
|
|
510
|
+
function _isObject3(v) {
|
|
511
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
512
|
+
}
|
|
513
|
+
var PHRASES = [
|
|
514
|
+
{ op: "commit", re: /\b(commit(e|et|te|ten)?|committe|einchecken)\b/i },
|
|
515
|
+
{ op: "push", re: /\b(push(e|en|t)?|hochladen|hochschieben|raufschieben)\b/i },
|
|
516
|
+
{ op: "branch", re: /\b(branch|feature-branch|zweig)\b/i },
|
|
517
|
+
// A creation verb is required — "schau dir den PR an" is not "open a PR".
|
|
518
|
+
{
|
|
519
|
+
op: "pr-create",
|
|
520
|
+
re: /\b(erstell(e|en)?|mach(e|en)?|leg(e|en)?\s+an|(er)?(ö|oe)ffne|open|create|raise|aufmachen)\b[^.\n]{0,30}\b(pr|pull[- ]request)\b|\b(pr|pull[- ]request)\b[^.\n]{0,20}\b(erstellen|aufmachen|anlegen|(er)?(ö|oe)ffnen)\b/i
|
|
521
|
+
},
|
|
522
|
+
// `merge` as an ACTION, never as the noun in "merge conflict" / "merge commit".
|
|
523
|
+
{ op: "pr-merge", re: /\b(merge|merg(e|en|st|t)|zusammenf(ü|ue)hren|reinmergen)\b(?!\s*[- ]?(conflict|konflikt|commit|base|queue|state|status))/i },
|
|
524
|
+
// A tag is an ACTION here — bare "Tag" is the German word for day, and
|
|
525
|
+
// "Version" is an ordinary noun. Both authorized a BLOCK op before this.
|
|
526
|
+
{ op: "tag", re: /\b(tagge(n|st)?|tag\s+(setzen|anlegen|erstellen)|git\s+tag|--tags|--follow-tags)\b/i },
|
|
527
|
+
// "die release notes sind falsch" is a noun phrase, not an authorization.
|
|
528
|
+
{
|
|
529
|
+
op: "release",
|
|
530
|
+
re: /\b(releasen?|ver(ö|oe)ffentlich(e|en))\b(?!\s*[- ]?(notes?|branch|candidate|pr\b|datum|date|zweig))/i
|
|
531
|
+
},
|
|
532
|
+
{ op: "publish", re: /\b(publish(e|en)?|publiziere[n]?)\b/i }
|
|
533
|
+
];
|
|
534
|
+
var OUTPUT_LINE = /^\s*(To\s+\S+|remote:|error:|fatal:|hint:|warning:|!\s|\?\?\s|\s*\^|[-+]{3}\s|@@\s|\$\s|>\s|\d+\s+(pass|fail)|npm ERR!|Error:|Traceback|at\s+\S+:\d+)/i;
|
|
535
|
+
function isInterrogative(prose) {
|
|
536
|
+
const t = prose.trim();
|
|
537
|
+
if (!t) {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
const hasImperative = /\b(mach|mache|bitte|leg|lege|erstell|erstelle|f(ü|ue)hr|f(ü|ue)hre|setz|setze|starte|los|jetzt|go ahead|do it|ja[,.]?\s|ok[,.]?\s)\b/i.test(
|
|
541
|
+
t
|
|
542
|
+
);
|
|
543
|
+
if (hasImperative) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
return /\?\s*$/.test(t) || /^(was|wie|warum|wieso|weshalb|wann|wer|welche[rs]?|wo|ist|sind|kann|kannst|k(ö|oe)nnen|soll|sollen|darf|d(ü|ue)rfen|why|what|how|when|which|who|is|are|can|could|should|does|do)\b/i.test(
|
|
547
|
+
t
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
var PASTED_COMMANDS = [
|
|
551
|
+
{ op: "publish", re: /\bnpm\s+publish\b/i },
|
|
552
|
+
{ op: "tag", re: /\bgit\s+push\s+[^\n]*--tags\b|\bgit\s+tag\s+-a\b/i },
|
|
553
|
+
{ op: "release", re: /\bgh\s+release\s+create\b/i },
|
|
554
|
+
{ op: "pr-merge", re: /\bgh\s+pr\s+merge\b/i },
|
|
555
|
+
{ op: "pr-create", re: /\bgh\s+pr\s+create\b/i },
|
|
556
|
+
{ op: "push", re: /\bgit\s+push\b/i },
|
|
557
|
+
{ op: "commit", re: /\bgit\s+commit\b/i },
|
|
558
|
+
{ op: "branch", re: /\bgit\s+(checkout\s+-b|switch\s+-c|branch)\b/i }
|
|
559
|
+
];
|
|
560
|
+
function splitFences(prompt) {
|
|
561
|
+
const fences = [];
|
|
562
|
+
const prose = prompt.replace(/```[^\n]*\n([\s\S]*?)```/g, (_m, body) => {
|
|
563
|
+
fences.push(body);
|
|
564
|
+
return " ";
|
|
565
|
+
});
|
|
566
|
+
return { prose, fences };
|
|
567
|
+
}
|
|
568
|
+
function _commandLines(fence) {
|
|
569
|
+
return fence.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).filter((l) => !/^(remote:|error:|hint:|fatal:|warning:|To |!\s|\s*\^)/i.test(l)).map((l) => l.replace(/^[$>#]\s*/, "")).filter((l) => /^(git|gh|npm|pnpm|yarn|task)\b/.test(l));
|
|
570
|
+
}
|
|
571
|
+
function classifyAuthorization(prompt) {
|
|
572
|
+
const { prose, fences } = splitFences(prompt);
|
|
573
|
+
const authorized = /* @__PURE__ */ new Set();
|
|
574
|
+
const evidence = {};
|
|
575
|
+
const instruction = prose.split("\n").filter((l) => !OUTPUT_LINE.test(l)).join("\n");
|
|
576
|
+
for (const { op, re } of isInterrogative(instruction) ? [] : PHRASES) {
|
|
577
|
+
const m = re.exec(instruction);
|
|
578
|
+
if (m) {
|
|
579
|
+
authorized.add(op);
|
|
580
|
+
evidence[op] = `prose: "${m[0]}"`;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
for (const fence of fences) {
|
|
584
|
+
if (fence.split("\n").some((l) => OUTPUT_LINE.test(l))) {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
for (const line of _commandLines(fence)) {
|
|
588
|
+
for (const { op, re } of PASTED_COMMANDS) {
|
|
589
|
+
if (re.test(line)) {
|
|
590
|
+
authorized.add(op);
|
|
591
|
+
evidence[op] = `pasted command: "${line.slice(0, 80)}"`;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return { authorized: [...authorized], evidence };
|
|
597
|
+
}
|
|
598
|
+
function run2(stdin_text, options) {
|
|
599
|
+
let envelope = {};
|
|
600
|
+
if (stdin_text.trim()) {
|
|
601
|
+
try {
|
|
602
|
+
const decoded = JSON.parse(stdin_text);
|
|
603
|
+
if (_isObject3(decoded)) {
|
|
604
|
+
envelope = decoded;
|
|
605
|
+
}
|
|
606
|
+
} catch {
|
|
607
|
+
return EXIT_ALLOW2;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const payload = _isObject3(envelope["payload"]) ? envelope["payload"] : envelope;
|
|
611
|
+
let prompt = "";
|
|
612
|
+
for (const key of ["prompt", "userPrompt", "user_prompt", "message", "text"]) {
|
|
613
|
+
const v = payload[key];
|
|
614
|
+
if (typeof v === "string" && v.trim()) {
|
|
615
|
+
prompt = v;
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (!prompt) {
|
|
620
|
+
return EXIT_ALLOW2;
|
|
621
|
+
}
|
|
622
|
+
const { authorized, evidence } = classifyAuthorization(prompt);
|
|
623
|
+
const ledger = {
|
|
624
|
+
session_id: typeof envelope["session_id"] === "string" ? envelope["session_id"] : "",
|
|
625
|
+
detected_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
626
|
+
authorized,
|
|
627
|
+
evidence,
|
|
628
|
+
prompt_chars: prompt.length
|
|
629
|
+
};
|
|
630
|
+
try {
|
|
631
|
+
atomic_write_json(path3.join(options.consumer_root, STATE_FILE2), ledger);
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
634
|
+
return EXIT_ALLOW2;
|
|
635
|
+
}
|
|
636
|
+
function main2(argv) {
|
|
637
|
+
const args = argv ?? process.argv.slice(2);
|
|
638
|
+
let consumer_root = process.cwd();
|
|
639
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
640
|
+
const a = args[i];
|
|
641
|
+
if (a === "--project-dir" && args[i + 1] !== void 0) {
|
|
642
|
+
consumer_root = args[i + 1];
|
|
643
|
+
i += 1;
|
|
644
|
+
} else if (a !== void 0 && a.startsWith("--project-dir=")) {
|
|
645
|
+
consumer_root = a.slice("--project-dir=".length);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return run2(readHookStdin(), { consumer_root });
|
|
649
|
+
}
|
|
650
|
+
function _isCliEntry2() {
|
|
651
|
+
if (true) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
if (process.argv[1] === void 0) {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
const argvUrl = pathToFileURL2(path3.resolve(process.argv[1])).href;
|
|
658
|
+
if (import.meta.url === argvUrl) {
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
const here = fs3.realpathSync(fileURLToPath2(import.meta.url));
|
|
663
|
+
const argv = fs3.realpathSync(path3.resolve(process.argv[1]));
|
|
664
|
+
return here === argv;
|
|
665
|
+
} catch {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (_isCliEntry2()) {
|
|
670
|
+
process.exit(main2());
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/scripts/hooks/block_unauthorized_git.ts
|
|
674
|
+
import fs4 from "node:fs";
|
|
675
|
+
import path4 from "node:path";
|
|
676
|
+
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL3 } from "node:url";
|
|
677
|
+
var EXIT_ALLOW3 = 0;
|
|
678
|
+
var EXIT_BLOCK = 1;
|
|
679
|
+
function _isObject4(v) {
|
|
680
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
681
|
+
}
|
|
682
|
+
var BLOCK_OPS = /* @__PURE__ */ new Set([
|
|
683
|
+
"publish",
|
|
684
|
+
"tag",
|
|
685
|
+
"release",
|
|
686
|
+
"pr-merge"
|
|
687
|
+
]);
|
|
688
|
+
var WARN_OPS = /* @__PURE__ */ new Set([
|
|
689
|
+
"commit",
|
|
690
|
+
"push",
|
|
691
|
+
"pr-create",
|
|
692
|
+
"branch"
|
|
693
|
+
]);
|
|
694
|
+
var G = "(?:-[A-Za-z-]+(?:=\\S+)?(?:\\s+\\S+)?\\s+)*";
|
|
695
|
+
var COMMAND_OPS = [
|
|
696
|
+
{ op: "publish", re: new RegExp(`\\b(npm|pnpm|yarn)\\s+${G}publish\\b`, "i") },
|
|
697
|
+
{ op: "release", re: /\bgh\s+release\s+create\b|\bgh\s+api\b[^\n]*\/releases\b/i },
|
|
698
|
+
{
|
|
699
|
+
op: "tag",
|
|
700
|
+
re: new RegExp(
|
|
701
|
+
`\\bgit\\s+${G}push\\b[^\\n;|&]*(--tags|--follow-tags|refs\\/tags\\/)`,
|
|
702
|
+
"i"
|
|
703
|
+
)
|
|
704
|
+
},
|
|
705
|
+
{ op: "pr-merge", re: /\bgh\s+pr\s+merge\b|\bgh\s+api\b[^\n]*\/pulls\/\d+\/merge\b/i },
|
|
706
|
+
{ op: "pr-create", re: /\bgh\s+pr\s+create\b|\bgh\s+api\b[^\n]*-X\s+POST[^\n]*\/pulls\b/i },
|
|
707
|
+
{ op: "push", re: new RegExp(`\\bgit\\s+${G}push\\b`, "i") },
|
|
708
|
+
{ op: "commit", re: new RegExp(`\\bgit\\s+${G}commit\\b`, "i") },
|
|
709
|
+
{ op: "branch", re: new RegExp(`\\bgit\\s+${G}(checkout\\s+-b|switch\\s+-c)\\b`, "i") }
|
|
710
|
+
];
|
|
711
|
+
var COMMAND_TOOLS = /* @__PURE__ */ new Set([
|
|
712
|
+
"launch-process",
|
|
713
|
+
"launch_process",
|
|
714
|
+
"Bash",
|
|
715
|
+
"BashTool",
|
|
716
|
+
"run-process",
|
|
717
|
+
"runProcess",
|
|
718
|
+
"shell",
|
|
719
|
+
"execute_shell",
|
|
720
|
+
"RunShellCommand"
|
|
721
|
+
]);
|
|
722
|
+
function extractCommand(envelope) {
|
|
723
|
+
const payload = _isObject4(envelope["payload"]) ? envelope["payload"] : envelope;
|
|
724
|
+
const toolRaw = payload["tool_name"] ?? payload["toolName"] ?? payload["tool"];
|
|
725
|
+
const tool = typeof toolRaw === "string" ? toolRaw : null;
|
|
726
|
+
if (tool !== null && !COMMAND_TOOLS.has(tool)) {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
const input = _isObject4(payload["tool_input"]) ? payload["tool_input"] : _isObject4(payload["toolInput"]) ? payload["toolInput"] : payload;
|
|
730
|
+
for (const key of ["command", "cmd", "script"]) {
|
|
731
|
+
const v = input[key];
|
|
732
|
+
if (typeof v === "string" && v.trim()) {
|
|
733
|
+
return v;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
function splitOutsideQuotes(command) {
|
|
739
|
+
const parts = [];
|
|
740
|
+
let cur = "";
|
|
741
|
+
let quote = null;
|
|
742
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
743
|
+
const ch = command[i];
|
|
744
|
+
if (quote !== null) {
|
|
745
|
+
if (quote === "$'" && ch === "\\" && i + 1 < command.length) {
|
|
746
|
+
cur += ch + command[i + 1];
|
|
747
|
+
i += 1;
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
cur += ch;
|
|
751
|
+
if (ch === (quote === "$'" ? "'" : quote)) {
|
|
752
|
+
quote = null;
|
|
753
|
+
} else if (ch === "\\" && quote === '"' && i + 1 < command.length) {
|
|
754
|
+
cur += command[i + 1];
|
|
755
|
+
i += 1;
|
|
756
|
+
}
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (ch === "$" && (command[i + 1] === "'" || command[i + 1] === '"')) {
|
|
760
|
+
quote = command[i + 1] === "'" ? "$'" : '"';
|
|
761
|
+
cur += ch + command[i + 1];
|
|
762
|
+
i += 1;
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (ch === '"' || ch === "'") {
|
|
766
|
+
quote = ch;
|
|
767
|
+
cur += ch;
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (ch === "\\" && i + 1 < command.length) {
|
|
771
|
+
cur += ch + command[i + 1];
|
|
772
|
+
i += 1;
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
if (ch === "\n" || ch === ";" || ch === "|" || ch === "&") {
|
|
776
|
+
if ((ch === "|" || ch === "&") && command[i + 1] === ch) {
|
|
777
|
+
i += 1;
|
|
778
|
+
}
|
|
779
|
+
parts.push(cur);
|
|
780
|
+
cur = "";
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
cur += ch;
|
|
784
|
+
}
|
|
785
|
+
if (quote !== null) {
|
|
786
|
+
parts.push(...splitIgnoringQuotes(cur));
|
|
787
|
+
return parts;
|
|
788
|
+
}
|
|
789
|
+
parts.push(cur);
|
|
790
|
+
return parts;
|
|
791
|
+
}
|
|
792
|
+
function splitIgnoringQuotes(fragment) {
|
|
793
|
+
const parts = [];
|
|
794
|
+
let cur = "";
|
|
795
|
+
for (let i = 0; i < fragment.length; i += 1) {
|
|
796
|
+
const ch = fragment[i];
|
|
797
|
+
if (ch === "\\" && i + 1 < fragment.length) {
|
|
798
|
+
cur += ch + fragment[i + 1];
|
|
799
|
+
i += 1;
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (ch === "\n" || ch === ";" || ch === "|" || ch === "&") {
|
|
803
|
+
if ((ch === "|" || ch === "&") && fragment[i + 1] === ch) {
|
|
804
|
+
i += 1;
|
|
805
|
+
}
|
|
806
|
+
parts.push(cur);
|
|
807
|
+
cur = "";
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
cur += ch;
|
|
811
|
+
}
|
|
812
|
+
parts.push(cur);
|
|
813
|
+
return parts;
|
|
814
|
+
}
|
|
815
|
+
function substitutionPayloads(segment) {
|
|
816
|
+
const out = [];
|
|
817
|
+
for (let i = 0; i < segment.length; i += 1) {
|
|
818
|
+
const ch = segment[i];
|
|
819
|
+
if (ch === "\\") {
|
|
820
|
+
i += 1;
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
if ((ch === "$" || ch === "<" || ch === ">") && segment[i + 1] === "(") {
|
|
824
|
+
let open = 1;
|
|
825
|
+
let j = i + 2;
|
|
826
|
+
const start = j;
|
|
827
|
+
for (; j < segment.length && open > 0; j += 1) {
|
|
828
|
+
const c = segment[j];
|
|
829
|
+
if (c === "\\") {
|
|
830
|
+
j += 1;
|
|
831
|
+
} else if (c === "(") {
|
|
832
|
+
open += 1;
|
|
833
|
+
} else if (c === ")") {
|
|
834
|
+
open -= 1;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
out.push(segment.slice(start, open === 0 ? j - 1 : segment.length));
|
|
838
|
+
i = j - 1;
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (ch === "`") {
|
|
842
|
+
const end = segment.indexOf("`", i + 1);
|
|
843
|
+
out.push(segment.slice(i + 1, end === -1 ? segment.length : end));
|
|
844
|
+
i = end === -1 ? segment.length : end;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return out;
|
|
848
|
+
}
|
|
849
|
+
function invokedSegments(command, depth = 0) {
|
|
850
|
+
if (depth > 3) {
|
|
851
|
+
return [];
|
|
852
|
+
}
|
|
853
|
+
const out = [];
|
|
854
|
+
const withoutHeredocs = command.replace(
|
|
855
|
+
/<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1[\s\S]*?^\s*\2\s*$/gm,
|
|
856
|
+
"<<HEREDOC"
|
|
857
|
+
);
|
|
858
|
+
for (const raw of splitOutsideQuotes(withoutHeredocs)) {
|
|
859
|
+
let seg = raw.trim();
|
|
860
|
+
if (!seg) {
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
seg = seg.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S*)\s+)+/, "");
|
|
864
|
+
for (const payload of substitutionPayloads(seg)) {
|
|
865
|
+
out.push(...invokedSegments(payload, depth + 1));
|
|
866
|
+
}
|
|
867
|
+
const wrapped = /^(?:\S*\/)?(?:ba|z|k)?sh\s+-[a-z]*c\s+(?:"([^"]*)"|'([^']*)'|(\S+))/.exec(seg);
|
|
868
|
+
if (wrapped) {
|
|
869
|
+
out.push(...invokedSegments(wrapped[1] ?? wrapped[2] ?? wrapped[3] ?? "", depth + 1));
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
const evaled = /^eval\s+(?:"([^"]*)"|'([^']*)'|(.+))$/.exec(seg);
|
|
873
|
+
if (evaled) {
|
|
874
|
+
out.push(...invokedSegments(evaled[1] ?? evaled[2] ?? evaled[3] ?? "", depth + 1));
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
out.push(seg.replace(/^(\S+)/, (w) => w.replace(/['"]/g, "")));
|
|
878
|
+
}
|
|
879
|
+
return out;
|
|
880
|
+
}
|
|
881
|
+
function commandOp(command) {
|
|
882
|
+
let found = null;
|
|
883
|
+
for (const seg of invokedSegments(command)) {
|
|
884
|
+
if (!/^(?:\S*\/)?(git|gh|npm|pnpm|yarn)\b/.test(seg)) {
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
for (const { op, re } of COMMAND_OPS) {
|
|
888
|
+
if (re.test(seg)) {
|
|
889
|
+
if (BLOCK_OPS.has(op)) {
|
|
890
|
+
return op;
|
|
891
|
+
}
|
|
892
|
+
found ??= op;
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return found;
|
|
898
|
+
}
|
|
899
|
+
var LEDGER_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
900
|
+
function _loadLedger(consumer_root, session_id, now) {
|
|
901
|
+
try {
|
|
902
|
+
const raw = fs4.readFileSync(path4.join(consumer_root, STATE_FILE2), "utf8");
|
|
903
|
+
const decoded = JSON.parse(raw);
|
|
904
|
+
if (_isObject4(decoded) && Array.isArray(decoded["authorized"])) {
|
|
905
|
+
const ledgerSession = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
|
|
906
|
+
if (session_id && ledgerSession && ledgerSession !== session_id) {
|
|
907
|
+
return { authorized: /* @__PURE__ */ new Set(), present: false };
|
|
908
|
+
}
|
|
909
|
+
const at = Date.parse(String(decoded["detected_at"] ?? ""));
|
|
910
|
+
if (Number.isFinite(at) && now - at > LEDGER_MAX_AGE_MS) {
|
|
911
|
+
return { authorized: /* @__PURE__ */ new Set(), present: false };
|
|
912
|
+
}
|
|
913
|
+
return {
|
|
914
|
+
authorized: new Set(decoded["authorized"].filter(Boolean)),
|
|
915
|
+
present: true
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
} catch {
|
|
919
|
+
}
|
|
920
|
+
return { authorized: /* @__PURE__ */ new Set(), present: false };
|
|
921
|
+
}
|
|
922
|
+
function decide(command, ledger) {
|
|
923
|
+
if (command === null) {
|
|
924
|
+
return { exit: EXIT_ALLOW3, stdout: "", stderr: "" };
|
|
925
|
+
}
|
|
926
|
+
const op = commandOp(command);
|
|
927
|
+
if (op === null || ledger.authorized.has(op)) {
|
|
928
|
+
return { exit: EXIT_ALLOW3, stdout: "", stderr: "" };
|
|
929
|
+
}
|
|
930
|
+
const shown = command.length > 120 ? `${command.slice(0, 117)}\u2026` : command;
|
|
931
|
+
if (BLOCK_OPS.has(op)) {
|
|
932
|
+
const reason = `Blocked: \`${op}\` with no authorization in this turn's prompt. This operation is irreversible and non-destructive-by-default already declares it never-autonomous. Command: ${shown}. Ask the user, in one numbered-options block, and run it only after they answer this turn.`;
|
|
933
|
+
return { exit: EXIT_BLOCK, stdout: "", stderr: `${reason}
|
|
934
|
+
` };
|
|
935
|
+
}
|
|
936
|
+
if (WARN_OPS.has(op)) {
|
|
937
|
+
const reason = `No \`${op}\` authorization found in this turn's prompt (commit-policy: a one-shot authorization is spent on the operation it named). If the user did authorize it this turn, proceed; otherwise stop and ask. Command: ${shown}`;
|
|
938
|
+
return {
|
|
939
|
+
exit: EXIT_ALLOW3,
|
|
940
|
+
stdout: `${JSON.stringify({ decision: "warn", reason })}
|
|
941
|
+
`,
|
|
942
|
+
stderr: ""
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
return { exit: EXIT_ALLOW3, stdout: "", stderr: "" };
|
|
946
|
+
}
|
|
947
|
+
function run3(stdin_text, options) {
|
|
948
|
+
let envelope = {};
|
|
949
|
+
if (stdin_text.trim()) {
|
|
950
|
+
try {
|
|
951
|
+
const decoded = JSON.parse(stdin_text);
|
|
952
|
+
if (_isObject4(decoded)) {
|
|
953
|
+
envelope = decoded;
|
|
954
|
+
}
|
|
955
|
+
} catch {
|
|
956
|
+
return EXIT_ALLOW3;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
|
|
960
|
+
const decision = decide(
|
|
961
|
+
extractCommand(envelope),
|
|
962
|
+
_loadLedger(options.consumer_root, session_id, Date.now())
|
|
963
|
+
);
|
|
964
|
+
if (decision.stdout) {
|
|
965
|
+
process.stdout.write(decision.stdout);
|
|
966
|
+
}
|
|
967
|
+
if (decision.stderr) {
|
|
968
|
+
process.stderr.write(decision.stderr);
|
|
969
|
+
}
|
|
970
|
+
return decision.exit;
|
|
971
|
+
}
|
|
972
|
+
function main3(argv) {
|
|
973
|
+
const args = argv ?? process.argv.slice(2);
|
|
974
|
+
let consumer_root = process.cwd();
|
|
975
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
976
|
+
const a = args[i];
|
|
977
|
+
if (a === "--project-dir" && args[i + 1] !== void 0) {
|
|
978
|
+
consumer_root = args[i + 1];
|
|
979
|
+
i += 1;
|
|
980
|
+
} else if (a !== void 0 && a.startsWith("--project-dir=")) {
|
|
981
|
+
consumer_root = a.slice("--project-dir=".length);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return run3(readHookStdin(), { consumer_root });
|
|
985
|
+
}
|
|
986
|
+
function _isCliEntry3() {
|
|
987
|
+
if (true) {
|
|
988
|
+
return false;
|
|
989
|
+
}
|
|
990
|
+
if (process.argv[1] === void 0) {
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
const argvUrl = pathToFileURL3(path4.resolve(process.argv[1])).href;
|
|
994
|
+
if (import.meta.url === argvUrl) {
|
|
995
|
+
return true;
|
|
996
|
+
}
|
|
997
|
+
try {
|
|
998
|
+
const here = fs4.realpathSync(fileURLToPath3(import.meta.url));
|
|
999
|
+
const argv = fs4.realpathSync(path4.resolve(process.argv[1]));
|
|
1000
|
+
return here === argv;
|
|
1001
|
+
} catch {
|
|
1002
|
+
return false;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (_isCliEntry3()) {
|
|
1006
|
+
process.exit(main3());
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/scripts/before_complete_hook.ts
|
|
1010
|
+
import fs5 from "node:fs";
|
|
1011
|
+
import path5 from "node:path";
|
|
1012
|
+
import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
1013
|
+
var STATE_FILE3 = path5.join("agents", "state", "verify-before-complete.json");
|
|
1014
|
+
var COMMAND_TOOLS2 = /* @__PURE__ */ new Set([
|
|
1015
|
+
"launch-process",
|
|
1016
|
+
"launch_process",
|
|
1017
|
+
// Augment
|
|
1018
|
+
"Bash",
|
|
1019
|
+
"BashTool",
|
|
1020
|
+
// Claude Code
|
|
1021
|
+
"run-process",
|
|
1022
|
+
"runProcess",
|
|
1023
|
+
// variants
|
|
1024
|
+
"shell",
|
|
1025
|
+
"execute_shell",
|
|
1026
|
+
// generic / Cline
|
|
1027
|
+
"RunShellCommand"
|
|
1028
|
+
// Cursor
|
|
1029
|
+
]);
|
|
1030
|
+
var _VERIFICATION_RE = new RegExp(
|
|
1031
|
+
"(?:^|[\\s;&|`(])(task\\s+(?:ci|test|tests|lint|check|qa|phpstan|rector|ecs|pest|pytest)|(?:\\./|\\.venv/bin/|vendor/bin/)?(?:pest|phpunit|phpstan|psalm|rector|ecs)\\b|(?:python3?|\\.venv/bin/python3?)\\s+-m\\s+pytest|pytest\\b|(?:npm|pnpm|yarn|bun)\\s+(?:run\\s+)?(?:test|check|lint|typecheck|tsc)|cargo\\s+(?:test|check|clippy)|go\\s+test|make\\s+(?:test|check|lint)|composer\\s+(?:test|check|lint|phpstan)|(?:php\\s+)?artisan\\s+test)",
|
|
1032
|
+
"i"
|
|
1033
|
+
);
|
|
1034
|
+
function _now() {
|
|
1035
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "+00:00");
|
|
1036
|
+
}
|
|
1037
|
+
function _empty_state() {
|
|
1038
|
+
return {
|
|
1039
|
+
schema_version: 1,
|
|
1040
|
+
session_id: "",
|
|
1041
|
+
turn_started_at: null,
|
|
1042
|
+
last_verification: null,
|
|
1043
|
+
verifications_this_turn: 0,
|
|
1044
|
+
verifications_this_session: 0,
|
|
1045
|
+
last_stop_at: null,
|
|
1046
|
+
verified_this_turn: false,
|
|
1047
|
+
ci_saw_pending: false,
|
|
1048
|
+
nonevidence_this_turn: 0,
|
|
1049
|
+
checked_at: _now()
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
function _load_state(target) {
|
|
1053
|
+
let isFile = false;
|
|
1054
|
+
try {
|
|
1055
|
+
isFile = fs5.statSync(target).isFile();
|
|
1056
|
+
} catch {
|
|
1057
|
+
isFile = false;
|
|
1058
|
+
}
|
|
1059
|
+
if (!isFile) {
|
|
1060
|
+
return _empty_state();
|
|
1061
|
+
}
|
|
1062
|
+
try {
|
|
1063
|
+
const decoded = JSON.parse(fs5.readFileSync(target, "utf-8"));
|
|
1064
|
+
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
1065
|
+
return { ..._empty_state(), ...decoded };
|
|
1066
|
+
}
|
|
1067
|
+
} catch {
|
|
1068
|
+
}
|
|
1069
|
+
return _empty_state();
|
|
1070
|
+
}
|
|
1071
|
+
function _extract_command(payload) {
|
|
1072
|
+
const toolRaw = payload["tool_name"] || payload["toolName"] || payload["tool"];
|
|
1073
|
+
if (!(typeof toolRaw === "string" && COMMAND_TOOLS2.has(toolRaw))) {
|
|
1074
|
+
return [typeof toolRaw === "string" ? toolRaw : null, null];
|
|
1075
|
+
}
|
|
1076
|
+
const tool = toolRaw;
|
|
1077
|
+
const ti = payload["tool_input"];
|
|
1078
|
+
if (typeof ti === "object" && ti !== null && !Array.isArray(ti)) {
|
|
1079
|
+
for (const key of ["command", "cmd", "shell_command"]) {
|
|
1080
|
+
const v = ti[key];
|
|
1081
|
+
if (typeof v === "string" && v) {
|
|
1082
|
+
return [tool, v];
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
for (const key of ["command", "cmd"]) {
|
|
1087
|
+
const v = payload[key];
|
|
1088
|
+
if (typeof v === "string" && v) {
|
|
1089
|
+
return [tool, v];
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return [tool, null];
|
|
1093
|
+
}
|
|
1094
|
+
function _is_verification(command) {
|
|
1095
|
+
return _VERIFICATION_RE.test(command) || isCiPoll(command);
|
|
1096
|
+
}
|
|
1097
|
+
var _VACUOUS_PATTERNS = [
|
|
1098
|
+
// gh pr checks / gh run — no checks registered at all.
|
|
1099
|
+
/\b0\s+(?:checks?|runs?)\b/i,
|
|
1100
|
+
// Test runners reporting an empty run.
|
|
1101
|
+
/\bno tests? (?:ran|found|to run|were run)\b/i,
|
|
1102
|
+
/\bNo test files found\b/i,
|
|
1103
|
+
/\b0\s+pass(?:ed|ing)?\b[\s,|·]*\b0\s+fail(?:ed|ing|ures)?\b/i,
|
|
1104
|
+
/\bTests?\s+0\s+passed\b/i,
|
|
1105
|
+
/\bRan 0 tests?\b/i,
|
|
1106
|
+
// Linters / scanners with an empty target set.
|
|
1107
|
+
/\bno files? (?:matched|to (?:lint|check|scan)|found)\b/i,
|
|
1108
|
+
/\b0\s+files?\s+(?:checked|scanned|linted)\b/i,
|
|
1109
|
+
/\bnothing to check\b/i
|
|
1110
|
+
];
|
|
1111
|
+
function isVacuousOutput(output) {
|
|
1112
|
+
const text = output.trim();
|
|
1113
|
+
if (!text) {
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
1117
|
+
return lines.every((l) => _VACUOUS_PATTERNS.some((re) => re.test(l)));
|
|
1118
|
+
}
|
|
1119
|
+
var _CI_POLL_RE = /\bgh\s+(?:pr\s+checks|run\s+(?:watch|list|view))\b/i;
|
|
1120
|
+
function isCiPoll(command) {
|
|
1121
|
+
return _CI_POLL_RE.test(command);
|
|
1122
|
+
}
|
|
1123
|
+
function pendingCount(output) {
|
|
1124
|
+
const m = /(\d+)\s+(?:checks? )?pending\b/i.exec(output) ?? /\bpending[:=]\s*(\d+)/i.exec(output);
|
|
1125
|
+
if (m?.[1] !== void 0) {
|
|
1126
|
+
return Number(m[1]);
|
|
1127
|
+
}
|
|
1128
|
+
const rows = output.match(/^\S.*\b(pending|in_progress|queued)\b/gim);
|
|
1129
|
+
if (rows) {
|
|
1130
|
+
return rows.length;
|
|
1131
|
+
}
|
|
1132
|
+
if (/\bno checks reported\b/i.test(output)) {
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
if (/^\S+\s+(pass|fail|skipping|successful|failing)\b/im.test(output)) {
|
|
1136
|
+
return 0;
|
|
1137
|
+
}
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
function _extract_output(payload) {
|
|
1141
|
+
for (const key of ["tool_response", "toolResponse", "output", "stdout", "result"]) {
|
|
1142
|
+
const v = payload[key];
|
|
1143
|
+
if (typeof v === "string") {
|
|
1144
|
+
return v;
|
|
1145
|
+
}
|
|
1146
|
+
if (v !== null && typeof v === "object") {
|
|
1147
|
+
return JSON.stringify(v);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
function _reset_turn(state, session_id) {
|
|
1153
|
+
state["session_id"] = session_id || state["session_id"] || "";
|
|
1154
|
+
state["turn_started_at"] = _now();
|
|
1155
|
+
state["verifications_this_turn"] = 0;
|
|
1156
|
+
state["verified_this_turn"] = false;
|
|
1157
|
+
state["ci_saw_pending"] = false;
|
|
1158
|
+
state["nonevidence_this_turn"] = 0;
|
|
1159
|
+
return state;
|
|
1160
|
+
}
|
|
1161
|
+
function _asInt(v) {
|
|
1162
|
+
if (!v) {
|
|
1163
|
+
return 0;
|
|
1164
|
+
}
|
|
1165
|
+
if (typeof v === "number") {
|
|
1166
|
+
return Math.trunc(v);
|
|
1167
|
+
}
|
|
1168
|
+
const n = Number(v);
|
|
1169
|
+
return Number.isFinite(n) ? Math.trunc(n) : 0;
|
|
1170
|
+
}
|
|
1171
|
+
function _update(state, event, envelope) {
|
|
1172
|
+
const session_id = envelope["session_id"] || state["session_id"] || "";
|
|
1173
|
+
if (session_id && session_id !== state["session_id"]) {
|
|
1174
|
+
state["session_id"] = session_id;
|
|
1175
|
+
state["verifications_this_session"] = 0;
|
|
1176
|
+
state = _reset_turn(state, session_id);
|
|
1177
|
+
}
|
|
1178
|
+
let payload = envelope["payload"];
|
|
1179
|
+
if (!(typeof payload === "object" && payload !== null && !Array.isArray(payload))) {
|
|
1180
|
+
payload = {};
|
|
1181
|
+
}
|
|
1182
|
+
const pl = payload;
|
|
1183
|
+
if (event === "session_start" || event === "user_prompt_submit") {
|
|
1184
|
+
state = _reset_turn(state, session_id);
|
|
1185
|
+
} else if (event === "post_tool_use") {
|
|
1186
|
+
const [tool, cmd] = _extract_command(pl);
|
|
1187
|
+
if (cmd && _is_verification(cmd)) {
|
|
1188
|
+
const output = _extract_output(pl);
|
|
1189
|
+
const vacuous = output !== null && isVacuousOutput(output);
|
|
1190
|
+
let counts = !vacuous;
|
|
1191
|
+
if (isCiPoll(cmd)) {
|
|
1192
|
+
const pending = output === null ? null : pendingCount(output);
|
|
1193
|
+
if (pending !== null && pending > 0) {
|
|
1194
|
+
state["ci_saw_pending"] = true;
|
|
1195
|
+
counts = false;
|
|
1196
|
+
} else if (pending === 0) {
|
|
1197
|
+
counts = !vacuous && state["ci_saw_pending"] === true;
|
|
1198
|
+
} else {
|
|
1199
|
+
counts = false;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
state["last_verification"] = {
|
|
1203
|
+
command: cmd.slice(0, 512),
|
|
1204
|
+
tool,
|
|
1205
|
+
at: _now(),
|
|
1206
|
+
platform: envelope["platform"] || "",
|
|
1207
|
+
vacuous,
|
|
1208
|
+
counted: counts
|
|
1209
|
+
};
|
|
1210
|
+
if (counts) {
|
|
1211
|
+
state["verifications_this_turn"] = _asInt(state["verifications_this_turn"]) + 1;
|
|
1212
|
+
state["verifications_this_session"] = _asInt(state["verifications_this_session"]) + 1;
|
|
1213
|
+
state["verified_this_turn"] = true;
|
|
1214
|
+
} else {
|
|
1215
|
+
state["nonevidence_this_turn"] = _asInt(state["nonevidence_this_turn"]) + 1;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
} else if (event === "stop") {
|
|
1219
|
+
state["last_stop_at"] = _now();
|
|
1220
|
+
}
|
|
1221
|
+
state["checked_at"] = _now();
|
|
1222
|
+
return state;
|
|
1223
|
+
}
|
|
1224
|
+
function run4(stdin_text, options) {
|
|
1225
|
+
const { consumer_root } = options;
|
|
1226
|
+
const verbose = options.verbose ?? false;
|
|
1227
|
+
let envelope = {};
|
|
1228
|
+
if (stdin_text.trim()) {
|
|
1229
|
+
try {
|
|
1230
|
+
const decoded = JSON.parse(stdin_text);
|
|
1231
|
+
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
1232
|
+
envelope = decoded;
|
|
1233
|
+
}
|
|
1234
|
+
} catch {
|
|
1235
|
+
envelope = {};
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
const event = envelope["event"] || "";
|
|
1239
|
+
const target = path5.join(consumer_root, STATE_FILE3);
|
|
1240
|
+
let state = _load_state(target);
|
|
1241
|
+
state = _update(state, event, envelope);
|
|
1242
|
+
try {
|
|
1243
|
+
atomic_write_json(target, state);
|
|
1244
|
+
} catch {
|
|
1245
|
+
if (verbose) {
|
|
1246
|
+
process.stderr.write("verify-before-complete-hook: state write failed\n");
|
|
1247
|
+
}
|
|
1248
|
+
return 0;
|
|
1249
|
+
}
|
|
1250
|
+
if (verbose) {
|
|
1251
|
+
process.stderr.write(
|
|
1252
|
+
`verify-before-complete-hook: event=${event} verified_this_turn=${pyRepr(state["verified_this_turn"])} verifications_this_turn=${pyRepr(state["verifications_this_turn"])}
|
|
1253
|
+
`
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
return 0;
|
|
1257
|
+
}
|
|
1258
|
+
function pyRepr(value) {
|
|
1259
|
+
if (value === null || value === void 0) {
|
|
1260
|
+
return "None";
|
|
1261
|
+
}
|
|
1262
|
+
if (typeof value === "boolean") {
|
|
1263
|
+
return value ? "True" : "False";
|
|
1264
|
+
}
|
|
1265
|
+
return String(value);
|
|
1266
|
+
}
|
|
1267
|
+
function parse_args(argv) {
|
|
1268
|
+
let platform = "generic";
|
|
1269
|
+
let verbose = false;
|
|
1270
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
1271
|
+
const arg = argv[i];
|
|
1272
|
+
if (arg === "--platform") {
|
|
1273
|
+
platform = argv[i + 1] ?? "generic";
|
|
1274
|
+
i += 1;
|
|
1275
|
+
} else if (arg !== void 0 && arg.startsWith("--platform=")) {
|
|
1276
|
+
platform = arg.slice("--platform=".length);
|
|
1277
|
+
} else if (arg === "--verbose") {
|
|
1278
|
+
verbose = true;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return { platform, verbose };
|
|
1282
|
+
}
|
|
1283
|
+
function _readStdin() {
|
|
1284
|
+
return readHookStdin();
|
|
1285
|
+
}
|
|
1286
|
+
function main4(argv) {
|
|
1287
|
+
const args = parse_args(argv ?? process.argv.slice(2));
|
|
1288
|
+
return run4(_readStdin(), { consumer_root: process.cwd(), verbose: args.verbose });
|
|
1289
|
+
}
|
|
1290
|
+
function _isCliEntry4() {
|
|
1291
|
+
if (true) {
|
|
1292
|
+
return false;
|
|
1293
|
+
}
|
|
1294
|
+
if (process.argv[1] === void 0) {
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
const argvUrl = pathToFileURL4(path5.resolve(process.argv[1])).href;
|
|
1298
|
+
if (import.meta.url === argvUrl) {
|
|
1299
|
+
return true;
|
|
1300
|
+
}
|
|
1301
|
+
try {
|
|
1302
|
+
const here = fs5.realpathSync(fileURLToPath4(import.meta.url));
|
|
1303
|
+
const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
|
|
1304
|
+
return here === argv;
|
|
1305
|
+
} catch {
|
|
1306
|
+
return false;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
var isCliEntry = _isCliEntry4();
|
|
1310
|
+
if (isCliEntry) {
|
|
1311
|
+
process.exit(main4(process.argv.slice(2)));
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// src/scripts/hooks/evidence_independence.ts
|
|
1315
|
+
import crypto from "node:crypto";
|
|
1316
|
+
import fs6 from "node:fs";
|
|
1317
|
+
import path6 from "node:path";
|
|
1318
|
+
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
|
|
1319
|
+
var EXIT_ALLOW4 = 0;
|
|
1320
|
+
var EXIT_BLOCK2 = 1;
|
|
1321
|
+
var STATE_FILE4 = path6.join("agents", "state", "evidence-dispatch.json");
|
|
1322
|
+
function _isObject5(v) {
|
|
1323
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1324
|
+
}
|
|
1325
|
+
var DISPATCH_TOOLS = /* @__PURE__ */ new Set([
|
|
1326
|
+
"Agent",
|
|
1327
|
+
"Task",
|
|
1328
|
+
"task",
|
|
1329
|
+
"dispatch_agent",
|
|
1330
|
+
"dispatchAgent",
|
|
1331
|
+
"launch_agent",
|
|
1332
|
+
"run_subagent",
|
|
1333
|
+
"Subagent"
|
|
1334
|
+
]);
|
|
1335
|
+
var EVALUATION_RE = /\b(review|reviewer|audit|auditor|judge|verdict|blind[- ]pass|blind review|adversarial|critique|assess(ment)?|verify (my|the) (work|change|diff|implementation)|find (any )?(bugs|issues|defects|problems) in (my|the)|pr(ü|ue)f(e|ung)?|begutachte|gutachten|bewerte|(gegen)?check(e)?|durchsicht|review(e|iere))\b/i;
|
|
1336
|
+
var PRELOADED_VERDICT_RE = [
|
|
1337
|
+
// German — the operator writes German, and an English-only list meant no
|
|
1338
|
+
// German dispatch was ever screened at all.
|
|
1339
|
+
/\bich erwarte (hier )?(keine|nichts)\b/i,
|
|
1340
|
+
/\b(du )?(wirst|solltest) (hier )?nichts finden\b/i,
|
|
1341
|
+
/\bbest(ä|ae)tige (nur|kurz)\b/i,
|
|
1342
|
+
/\b(ist|sollte) (wahrscheinlich|vermutlich|sicher) (sauber|korrekt|in ordnung|fine)\b/i,
|
|
1343
|
+
/\bkeine findings? (erwartet|zu erwarten)\b/i,
|
|
1344
|
+
// English paraphrases of the audited construct.
|
|
1345
|
+
/\bi am confident there is nothing\b/i,
|
|
1346
|
+
/\ba clean pass is the likely\b/i,
|
|
1347
|
+
/\bno[- ]findings? (is|are) (expected|welcome|fine|acceptable|the likely)/i,
|
|
1348
|
+
/\b(you )?(should|will|probably) find (nothing|no (issues|problems|bugs|findings))/i,
|
|
1349
|
+
/\bconfirm (that )?there (are|is) no\b/i,
|
|
1350
|
+
/\bi (believe|think|expect) (this|it) is (clean|correct|fine)\b/i,
|
|
1351
|
+
/\bexpect(ed)? (to be )?(clean|green|no findings)\b/i,
|
|
1352
|
+
/\bjust confirm\b/i,
|
|
1353
|
+
/\bit('s| is) (probably|likely) fine\b/i
|
|
1354
|
+
];
|
|
1355
|
+
function isDispatchTool(tool) {
|
|
1356
|
+
return tool !== null && DISPATCH_TOOLS.has(tool);
|
|
1357
|
+
}
|
|
1358
|
+
function isEvaluationPrompt(prompt) {
|
|
1359
|
+
return EVALUATION_RE.test(prompt);
|
|
1360
|
+
}
|
|
1361
|
+
var SELF_SCOPE_RE = /\b(my (work|change|diff|implementation|code|fix|patch|branch|pr)|this (diff|change|branch|pr|delta|implementation|patch)|the delta|the change i|what i (wrote|built|changed|implemented)|i just (wrote|built|changed|implemented))\b/i;
|
|
1362
|
+
function isSelfScoped(prompt) {
|
|
1363
|
+
return SELF_SCOPE_RE.test(prompt);
|
|
1364
|
+
}
|
|
1365
|
+
function preloadedVerdict(prompt) {
|
|
1366
|
+
for (const re of PRELOADED_VERDICT_RE) {
|
|
1367
|
+
const m = re.exec(prompt);
|
|
1368
|
+
if (m) {
|
|
1369
|
+
return m[0];
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
function extractDispatch(envelope) {
|
|
1375
|
+
const payload = _isObject5(envelope["payload"]) ? envelope["payload"] : envelope;
|
|
1376
|
+
const toolRaw = payload["tool_name"] ?? payload["toolName"] ?? payload["tool"];
|
|
1377
|
+
const tool = typeof toolRaw === "string" ? toolRaw : null;
|
|
1378
|
+
const input = _isObject5(payload["tool_input"]) ? payload["tool_input"] : _isObject5(payload["toolInput"]) ? payload["toolInput"] : payload;
|
|
1379
|
+
for (const key of ["prompt", "instructions", "task", "description"]) {
|
|
1380
|
+
const v = input[key];
|
|
1381
|
+
if (typeof v === "string" && v.trim()) {
|
|
1382
|
+
return [tool, v];
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
return [tool, ""];
|
|
1386
|
+
}
|
|
1387
|
+
function _ledgerStamp(consumer_root) {
|
|
1388
|
+
try {
|
|
1389
|
+
const raw = fs6.readFileSync(
|
|
1390
|
+
path6.join(consumer_root, "agents", "state", "git-authorization.json"),
|
|
1391
|
+
"utf8"
|
|
1392
|
+
);
|
|
1393
|
+
const d = JSON.parse(raw);
|
|
1394
|
+
return typeof d["detected_at"] === "string" ? d["detected_at"] : "";
|
|
1395
|
+
} catch {
|
|
1396
|
+
return "";
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
function _load(target) {
|
|
1400
|
+
try {
|
|
1401
|
+
const decoded = JSON.parse(fs6.readFileSync(target, "utf8"));
|
|
1402
|
+
if (_isObject5(decoded) && Array.isArray(decoded["evaluations"])) {
|
|
1403
|
+
return decoded;
|
|
1404
|
+
}
|
|
1405
|
+
} catch {
|
|
1406
|
+
}
|
|
1407
|
+
return { session_id: "", turn_started_at: "", evaluations: [] };
|
|
1408
|
+
}
|
|
1409
|
+
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.";
|
|
1410
|
+
function decide2(tool, prompt, priorEvaluations) {
|
|
1411
|
+
if (!isDispatchTool(tool) || !isEvaluationPrompt(prompt)) {
|
|
1412
|
+
return { exit: EXIT_ALLOW4, stdout: "", stderr: "", evaluations: priorEvaluations };
|
|
1413
|
+
}
|
|
1414
|
+
const preloaded = preloadedVerdict(prompt);
|
|
1415
|
+
if (preloaded !== null) {
|
|
1416
|
+
return {
|
|
1417
|
+
exit: EXIT_BLOCK2,
|
|
1418
|
+
stdout: "",
|
|
1419
|
+
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.
|
|
1420
|
+
`,
|
|
1421
|
+
evaluations: priorEvaluations
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
if (!isSelfScoped(prompt)) {
|
|
1425
|
+
return { exit: EXIT_ALLOW4, stdout: "", stderr: "", evaluations: priorEvaluations };
|
|
1426
|
+
}
|
|
1427
|
+
if (priorEvaluations >= 1) {
|
|
1428
|
+
return {
|
|
1429
|
+
exit: EXIT_BLOCK2,
|
|
1430
|
+
stdout: "",
|
|
1431
|
+
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.
|
|
1432
|
+
`,
|
|
1433
|
+
evaluations: priorEvaluations
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
return {
|
|
1437
|
+
exit: EXIT_ALLOW4,
|
|
1438
|
+
stdout: `${JSON.stringify({ decision: "warn", reason: WARN_TEXT })}
|
|
1439
|
+
`,
|
|
1440
|
+
stderr: "",
|
|
1441
|
+
evaluations: priorEvaluations + 1
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
function run5(stdin_text, options) {
|
|
1445
|
+
let envelope = {};
|
|
1446
|
+
if (stdin_text.trim()) {
|
|
1447
|
+
try {
|
|
1448
|
+
const decoded = JSON.parse(stdin_text);
|
|
1449
|
+
if (_isObject5(decoded)) {
|
|
1450
|
+
envelope = decoded;
|
|
1451
|
+
}
|
|
1452
|
+
} catch {
|
|
1453
|
+
return EXIT_ALLOW4;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
const target = path6.join(options.consumer_root, STATE_FILE4);
|
|
1457
|
+
const state = _load(target);
|
|
1458
|
+
const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
|
|
1459
|
+
const turnMarker = `${session_id}:${_ledgerStamp(options.consumer_root)}`;
|
|
1460
|
+
if (state.session_id !== turnMarker) {
|
|
1461
|
+
state.session_id = turnMarker;
|
|
1462
|
+
state.turn_started_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1463
|
+
state.evaluations = [];
|
|
1464
|
+
}
|
|
1465
|
+
const [tool, prompt] = extractDispatch(envelope);
|
|
1466
|
+
const decision = decide2(tool, prompt, state.evaluations.length);
|
|
1467
|
+
if (decision.evaluations > state.evaluations.length) {
|
|
1468
|
+
state.evaluations.push({
|
|
1469
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1470
|
+
digest: crypto.createHash("sha256").update(prompt).digest("hex").slice(0, 16),
|
|
1471
|
+
prompt_chars: prompt.length
|
|
1472
|
+
});
|
|
1473
|
+
try {
|
|
1474
|
+
atomic_write_json(target, state);
|
|
1475
|
+
} catch {
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
if (decision.stdout) {
|
|
1479
|
+
process.stdout.write(decision.stdout);
|
|
1480
|
+
}
|
|
1481
|
+
if (decision.stderr) {
|
|
1482
|
+
process.stderr.write(decision.stderr);
|
|
1483
|
+
}
|
|
1484
|
+
return decision.exit;
|
|
1485
|
+
}
|
|
1486
|
+
function main5(argv) {
|
|
1487
|
+
const args = argv ?? process.argv.slice(2);
|
|
1488
|
+
let consumer_root = process.cwd();
|
|
1489
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1490
|
+
const a = args[i];
|
|
1491
|
+
if (a === "--project-dir" && args[i + 1] !== void 0) {
|
|
1492
|
+
consumer_root = args[i + 1];
|
|
1493
|
+
i += 1;
|
|
1494
|
+
} else if (a !== void 0 && a.startsWith("--project-dir=")) {
|
|
1495
|
+
consumer_root = a.slice("--project-dir=".length);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
return run5(readHookStdin(), { consumer_root });
|
|
1499
|
+
}
|
|
1500
|
+
function _isCliEntry5() {
|
|
1501
|
+
if (true) {
|
|
1502
|
+
return false;
|
|
1503
|
+
}
|
|
1504
|
+
if (process.argv[1] === void 0) {
|
|
1505
|
+
return false;
|
|
1506
|
+
}
|
|
1507
|
+
const argvUrl = pathToFileURL5(path6.resolve(process.argv[1])).href;
|
|
1508
|
+
if (import.meta.url === argvUrl) {
|
|
1509
|
+
return true;
|
|
1510
|
+
}
|
|
1511
|
+
try {
|
|
1512
|
+
const here = fs6.realpathSync(fileURLToPath5(import.meta.url));
|
|
1513
|
+
const argv = fs6.realpathSync(path6.resolve(process.argv[1]));
|
|
1514
|
+
return here === argv;
|
|
1515
|
+
} catch {
|
|
1516
|
+
return false;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (_isCliEntry5()) {
|
|
1520
|
+
process.exit(main5());
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// src/scripts/conformance_scan.ts
|
|
1524
|
+
var REPO_ROOT = path7.resolve(path7.dirname(fileURLToPath6(import.meta.url)), "..", "..");
|
|
1525
|
+
var OBSERVED_BAND = { low: 9.1, high: 39.2 };
|
|
1526
|
+
var BAND_MIN_TURNS = 1978;
|
|
1527
|
+
function bandVerdict(rate_pct, assistant_turns) {
|
|
1528
|
+
if (assistant_turns < BAND_MIN_TURNS) {
|
|
1529
|
+
return "corpus-too-small";
|
|
1530
|
+
}
|
|
1531
|
+
return rate_pct >= OBSERVED_BAND.low && rate_pct <= OBSERVED_BAND.high ? "inside" : "outside";
|
|
1532
|
+
}
|
|
1533
|
+
function _isObject6(v) {
|
|
1534
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1535
|
+
}
|
|
1536
|
+
function userText(entry) {
|
|
1537
|
+
if (entry["type"] !== "user" || isSidechain(entry)) {
|
|
1538
|
+
return null;
|
|
1539
|
+
}
|
|
1540
|
+
const text = entryText(entry);
|
|
1541
|
+
if (!text.trim()) {
|
|
1542
|
+
return null;
|
|
1543
|
+
}
|
|
1544
|
+
if (text.includes("<system-reminder>") || text.startsWith("<local-command-stdout>")) {
|
|
1545
|
+
return null;
|
|
1546
|
+
}
|
|
1547
|
+
return text;
|
|
1548
|
+
}
|
|
1549
|
+
function assistantText(entry) {
|
|
1550
|
+
if (entry["type"] !== "assistant" || isSidechain(entry)) {
|
|
1551
|
+
return null;
|
|
1552
|
+
}
|
|
1553
|
+
return entryText(entry).trim() || null;
|
|
1554
|
+
}
|
|
1555
|
+
function toolUses2(entry) {
|
|
1556
|
+
const msg = entry["message"];
|
|
1557
|
+
const content = _isObject6(msg) ? msg["content"] : void 0;
|
|
1558
|
+
if (!Array.isArray(content)) {
|
|
1559
|
+
return [];
|
|
1560
|
+
}
|
|
1561
|
+
return content.filter((b) => _isObject6(b) && b["type"] === "tool_use").map((b) => {
|
|
1562
|
+
const o = b;
|
|
1563
|
+
return {
|
|
1564
|
+
name: String(o["name"] ?? ""),
|
|
1565
|
+
input: _isObject6(o["input"]) ? o["input"] : {}
|
|
1566
|
+
};
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
function isInjectedBody(text) {
|
|
1570
|
+
if (/^(Base directory for this skill|<command-message>|<command-name>|<local-command)/.test(text)) {
|
|
1571
|
+
return true;
|
|
1572
|
+
}
|
|
1573
|
+
return text.length > 2500 && classify(text).language === "en";
|
|
1574
|
+
}
|
|
1575
|
+
var HARNESS_TEXT = /^(API Error:|Request (timed out|was aborted)|\[Request interrupted|Error: |Credit balance is too low|You've hit your|Prompt is too long)/i;
|
|
1576
|
+
var EN_OPENER = /^(let me|i'?ll |i'?m |found it|ok[,. ]|okay|alright|here'?s|now[,. ]|looking|checking|reading|running|the |this |that |there |we |you |good |right[,. ]|perfect|done[.,]|all |confirmed|correct)/i;
|
|
1577
|
+
function scanSession(sessionId, lines) {
|
|
1578
|
+
const report = {
|
|
1579
|
+
session: sessionId,
|
|
1580
|
+
user_turns: 0,
|
|
1581
|
+
assistant_turns: 0,
|
|
1582
|
+
violations: []
|
|
1583
|
+
};
|
|
1584
|
+
let pinned = null;
|
|
1585
|
+
let pendingPoll = null;
|
|
1586
|
+
let authorized = /* @__PURE__ */ new Set();
|
|
1587
|
+
let sawPending = false;
|
|
1588
|
+
let evaluationsThisTurn = 0;
|
|
1589
|
+
let turnsSincePrompt = 0;
|
|
1590
|
+
let compactionSincePrompt = false;
|
|
1591
|
+
for (const line of lines) {
|
|
1592
|
+
if (!line.trim()) {
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
let entry;
|
|
1596
|
+
try {
|
|
1597
|
+
entry = JSON.parse(line);
|
|
1598
|
+
} catch {
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
if (entry["type"] === "user" && entry["toolUseResult"] !== void 0 && pendingPoll !== null) {
|
|
1602
|
+
const raw = entry["toolUseResult"];
|
|
1603
|
+
const out = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
1604
|
+
const pending = pendingCount(out);
|
|
1605
|
+
if (pending !== null && pending > 0) {
|
|
1606
|
+
sawPending = true;
|
|
1607
|
+
} else if (isVacuousOutput(out) || pending === 0 && !sawPending) {
|
|
1608
|
+
report.violations.push({
|
|
1609
|
+
check: "vacuous-evidence",
|
|
1610
|
+
session: sessionId,
|
|
1611
|
+
at: pendingPoll.at,
|
|
1612
|
+
detail: `CI poll read as settled without an in-flight observation: ${out.slice(0, 90)}`
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
pendingPoll = null;
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
if (entry["type"] === "system" && entry["subtype"] === "compact_boundary") {
|
|
1619
|
+
compactionSincePrompt = true;
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
const ut = userText(entry);
|
|
1623
|
+
if (ut !== null) {
|
|
1624
|
+
if (entry["isCompactSummary"] === true) {
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
if (isSyntheticPrompt(ut)) {
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
if (isInjectedBody(ut)) {
|
|
1631
|
+
continue;
|
|
1632
|
+
}
|
|
1633
|
+
report.user_turns += 1;
|
|
1634
|
+
turnsSincePrompt = 0;
|
|
1635
|
+
compactionSincePrompt = false;
|
|
1636
|
+
const c = classify(ut);
|
|
1637
|
+
if (c.language !== "und") {
|
|
1638
|
+
pinned = c.language;
|
|
1639
|
+
}
|
|
1640
|
+
authorized = new Set(classifyAuthorization(ut).authorized);
|
|
1641
|
+
sawPending = false;
|
|
1642
|
+
evaluationsThisTurn = 0;
|
|
1643
|
+
pendingPoll = null;
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
if (entry["type"] !== "assistant" || entry["isSidechain"] === true) {
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
const at = String(entry["timestamp"] ?? "");
|
|
1650
|
+
const prose = assistantText(entry);
|
|
1651
|
+
if (prose !== null) {
|
|
1652
|
+
report.assistant_turns += 1;
|
|
1653
|
+
turnsSincePrompt += 1;
|
|
1654
|
+
if (pinned === "de") {
|
|
1655
|
+
const first = prose.split("\n").find((l) => l.trim()) ?? "";
|
|
1656
|
+
if (HARNESS_TEXT.test(first.trim())) {
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
const c = classify(first);
|
|
1660
|
+
if (first.length > 12 && (c.language === "en" || EN_OPENER.test(first.trim()))) {
|
|
1661
|
+
report.violations.push({
|
|
1662
|
+
check: "language-pin",
|
|
1663
|
+
session: sessionId,
|
|
1664
|
+
at,
|
|
1665
|
+
detail: `German pin, English reply opener: "${first.slice(0, 110)}"`,
|
|
1666
|
+
turns_since_prompt: turnsSincePrompt,
|
|
1667
|
+
compaction_since_prompt: compactionSincePrompt
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
for (const tu of toolUses2(entry)) {
|
|
1673
|
+
const cmd = typeof tu.input["command"] === "string" ? tu.input["command"] : null;
|
|
1674
|
+
if (cmd !== null) {
|
|
1675
|
+
const op = commandOp(cmd);
|
|
1676
|
+
if (op !== null && !authorized.has(op) && BLOCK_OPS.has(op)) {
|
|
1677
|
+
report.violations.push({
|
|
1678
|
+
check: "git-authorization",
|
|
1679
|
+
session: sessionId,
|
|
1680
|
+
at,
|
|
1681
|
+
detail: `irreversible \`${op}\` with no authorization in the turn's prompt: ${cmd.slice(0, 110)}`
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
if (isCiPoll(cmd)) {
|
|
1685
|
+
pendingPoll = { at, cmd };
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
const prompt = typeof tu.input["prompt"] === "string" ? tu.input["prompt"] : null;
|
|
1689
|
+
if (prompt !== null && isEvaluationPrompt(prompt)) {
|
|
1690
|
+
const preloaded = preloadedVerdict(prompt);
|
|
1691
|
+
if (preloaded !== null) {
|
|
1692
|
+
report.violations.push({
|
|
1693
|
+
check: "evidence-steering",
|
|
1694
|
+
session: sessionId,
|
|
1695
|
+
at,
|
|
1696
|
+
detail: `evaluation prompt pre-loads its verdict: "${preloaded}"`
|
|
1697
|
+
});
|
|
1698
|
+
} else if (isSelfScoped(prompt)) {
|
|
1699
|
+
if (evaluationsThisTurn >= 1) {
|
|
1700
|
+
report.violations.push({
|
|
1701
|
+
check: "evidence-steering",
|
|
1702
|
+
session: sessionId,
|
|
1703
|
+
at,
|
|
1704
|
+
detail: "second self-review dispatch in one turn (verdict shopping)"
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
evaluationsThisTurn += 1;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
return report;
|
|
1713
|
+
}
|
|
1714
|
+
var DEFAULT_RATE_SERIES = path7.join(
|
|
1715
|
+
REPO_ROOT,
|
|
1716
|
+
"agents",
|
|
1717
|
+
"runtime",
|
|
1718
|
+
"state",
|
|
1719
|
+
"conformance-rates.jsonl"
|
|
1720
|
+
);
|
|
1721
|
+
function defaultStore(projectDir) {
|
|
1722
|
+
const slug = projectDir.replace(/[/.]/g, "-");
|
|
1723
|
+
return path7.join(process.env["HOME"] ?? "", ".claude", "projects", slug);
|
|
1724
|
+
}
|
|
1725
|
+
function _tokens(chars) {
|
|
1726
|
+
return Math.round(chars / 4);
|
|
1727
|
+
}
|
|
1728
|
+
function measureDelivered(projectRulesDir, globalRulesDir) {
|
|
1729
|
+
const p = censusRuleDir(projectRulesDir);
|
|
1730
|
+
const g = censusRuleDir(globalRulesDir);
|
|
1731
|
+
return {
|
|
1732
|
+
project: {
|
|
1733
|
+
dir: projectRulesDir,
|
|
1734
|
+
present: fs7.existsSync(projectRulesDir),
|
|
1735
|
+
files: p.files,
|
|
1736
|
+
tokens: _tokens(p.chars)
|
|
1737
|
+
},
|
|
1738
|
+
global: {
|
|
1739
|
+
dir: globalRulesDir,
|
|
1740
|
+
present: fs7.existsSync(globalRulesDir),
|
|
1741
|
+
files: g.files,
|
|
1742
|
+
tokens: _tokens(g.chars)
|
|
1743
|
+
},
|
|
1744
|
+
union_tokens: _tokens(p.chars + g.chars)
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
function storeKey(store) {
|
|
1748
|
+
return createHash("sha256").update(path7.resolve(store)).digest("hex").slice(0, 12);
|
|
1749
|
+
}
|
|
1750
|
+
function scanStore(store, limit) {
|
|
1751
|
+
const files = fs7.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs7.statSync(path7.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
|
|
1752
|
+
const per_session = [];
|
|
1753
|
+
for (const f of files) {
|
|
1754
|
+
const lines = fs7.readFileSync(path7.join(store, f), "utf8").split("\n");
|
|
1755
|
+
const r = scanSession(f.replace(/\.jsonl$/, "").slice(0, 8), lines);
|
|
1756
|
+
if (r.assistant_turns > 0) {
|
|
1757
|
+
per_session.push(r);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
const totals = {
|
|
1761
|
+
"language-pin": 0,
|
|
1762
|
+
"git-authorization": 0,
|
|
1763
|
+
"vacuous-evidence": 0,
|
|
1764
|
+
"evidence-steering": 0
|
|
1765
|
+
};
|
|
1766
|
+
for (const s of per_session) {
|
|
1767
|
+
for (const v of s.violations) {
|
|
1768
|
+
totals[v.check] = (totals[v.check] ?? 0) + 1;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
const assistant_turns = per_session.reduce((n, s) => n + s.assistant_turns, 0);
|
|
1772
|
+
const language_pin = totals["language-pin"] ?? 0;
|
|
1773
|
+
const rate_pct = assistant_turns === 0 ? 0 : Number((100 * language_pin / assistant_turns).toFixed(1));
|
|
1774
|
+
const delivered = measureDelivered(
|
|
1775
|
+
path7.join(REPO_ROOT, ".claude", "rules"),
|
|
1776
|
+
path7.join(process.env["HOME"] ?? os.homedir(), ".claude", "rules")
|
|
1777
|
+
);
|
|
1778
|
+
const ownStore = path7.resolve(store) === path7.resolve(defaultStore(REPO_ROOT));
|
|
1779
|
+
return {
|
|
1780
|
+
scanned_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1781
|
+
store,
|
|
1782
|
+
sessions: per_session.length,
|
|
1783
|
+
totals,
|
|
1784
|
+
per_session,
|
|
1785
|
+
delivered,
|
|
1786
|
+
rate: {
|
|
1787
|
+
store_key: storeKey(store),
|
|
1788
|
+
sessions: per_session.length,
|
|
1789
|
+
assistant_turns,
|
|
1790
|
+
language_pin,
|
|
1791
|
+
rate_pct,
|
|
1792
|
+
band: bandVerdict(rate_pct, assistant_turns),
|
|
1793
|
+
// Only attach the carrier figures when the rate and the carriers describe
|
|
1794
|
+
// the same project.
|
|
1795
|
+
delivered_project_tokens: ownStore ? delivered.project.tokens : null,
|
|
1796
|
+
delivered_global_tokens: ownStore ? delivered.global.tokens : null
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
function recordRate(file, scanned_at, rate) {
|
|
1801
|
+
fs7.mkdirSync(path7.dirname(file), { recursive: true });
|
|
1802
|
+
fs7.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
|
|
1803
|
+
`, "utf8");
|
|
1804
|
+
}
|
|
1805
|
+
function render(report) {
|
|
1806
|
+
const lines = [];
|
|
1807
|
+
lines.push(`conformance:behavior \xB7 ${report.sessions} session(s) \xB7 ${report.store}`);
|
|
1808
|
+
for (const [k, v] of Object.entries(report.totals)) {
|
|
1809
|
+
lines.push(` ${v === 0 ? "\u2705" : "\u26A0\uFE0F "} ${k.padEnd(18)} ${v}`);
|
|
1810
|
+
}
|
|
1811
|
+
const worst = [...report.per_session].filter((s) => s.violations.length > 0).sort((a, b) => b.violations.length - a.violations.length).slice(0, 5);
|
|
1812
|
+
if (worst.length > 0) {
|
|
1813
|
+
lines.push("");
|
|
1814
|
+
lines.push(" Highest-violation sessions:");
|
|
1815
|
+
for (const s of worst) {
|
|
1816
|
+
lines.push(` ${s.session} ${s.violations.length} across ${s.assistant_turns} turns`);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
const d = report.delivered;
|
|
1820
|
+
lines.push("");
|
|
1821
|
+
lines.push(" Delivered rule text (chars/4, carriers as they stand NOW \u2014 not per-session):");
|
|
1822
|
+
const carrier = (label, c) => c.present ? ` ${label} ${String(c.files).padStart(4)} rules ${String(c.tokens).padStart(7)} tok` : ` ${label} ABSENT \u2014 not a measured zero, this carrier does not exist here`;
|
|
1823
|
+
lines.push(carrier("project", d.project));
|
|
1824
|
+
lines.push(carrier("global ", d.global));
|
|
1825
|
+
lines.push(` union ${String(d.union_tokens).padStart(7)} tok`);
|
|
1826
|
+
lines.push(" A session's own payload is NOT recoverable: the transcript records no system");
|
|
1827
|
+
lines.push(" or tools field, and the carriers change under the sessions. This is one");
|
|
1828
|
+
lines.push(" reading; the series below is what makes it interpretable over time.");
|
|
1829
|
+
lines.push("");
|
|
1830
|
+
lines.push(
|
|
1831
|
+
` language-pin rate ${report.rate.rate_pct.toFixed(1)}% (${report.rate.language_pin} of ${report.rate.assistant_turns} assistant turns)`
|
|
1832
|
+
);
|
|
1833
|
+
if (report.rate.band === "corpus-too-small") {
|
|
1834
|
+
lines.push(
|
|
1835
|
+
` Not compared: ${report.rate.assistant_turns} turns is below the ${BAND_MIN_TURNS} of the`
|
|
1836
|
+
);
|
|
1837
|
+
lines.push(" smallest corpus the band was derived from, so a verdict here would be about");
|
|
1838
|
+
lines.push(" corpus size, not behaviour.");
|
|
1839
|
+
} else if (report.rate.band === "inside") {
|
|
1840
|
+
lines.push(
|
|
1841
|
+
` Inside the observed ${OBSERVED_BAND.low}-${OBSERVED_BAND.high}% band (M5, n=3).`
|
|
1842
|
+
);
|
|
1843
|
+
} else {
|
|
1844
|
+
lines.push(
|
|
1845
|
+
` \u26A0\uFE0F OUTSIDE the observed ${OBSERVED_BAND.low}-${OBSERVED_BAND.high}% band (M5, n=3).`
|
|
1846
|
+
);
|
|
1847
|
+
lines.push(" This is the falsifier both council members named when the volume test was");
|
|
1848
|
+
lines.push(" cancelled: a project outside the band is the signal that the cancellation");
|
|
1849
|
+
lines.push(" was wrong. It is a reason to look, not a defect in this run.");
|
|
1850
|
+
lines.push(" CHECK FIRST that this is a different PROJECT: a git worktree gets its own");
|
|
1851
|
+
lines.push(" transcript store under the same project, so a store-keyed series counts it");
|
|
1852
|
+
lines.push(" as new. Store novelty is not project novelty.");
|
|
1853
|
+
}
|
|
1854
|
+
lines.push("");
|
|
1855
|
+
lines.push(" Scanned checks are exactly the mechanised ones. Ask-shape, session-canary,");
|
|
1856
|
+
lines.push(" promissory closings and checkbox batching are left as prose and NOT measured.");
|
|
1857
|
+
return lines.join("\n");
|
|
1858
|
+
}
|
|
1859
|
+
function main6(argv) {
|
|
1860
|
+
const args = argv ?? process.argv.slice(2);
|
|
1861
|
+
let store = null;
|
|
1862
|
+
let limit = 30;
|
|
1863
|
+
let json = false;
|
|
1864
|
+
let out = null;
|
|
1865
|
+
let record = null;
|
|
1866
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1867
|
+
const a = args[i];
|
|
1868
|
+
if (a === "--store" && args[i + 1] !== void 0) {
|
|
1869
|
+
store = args[++i];
|
|
1870
|
+
} else if (a === "--limit" && args[i + 1] !== void 0) {
|
|
1871
|
+
limit = Number(args[++i]);
|
|
1872
|
+
} else if (a === "--json") {
|
|
1873
|
+
json = true;
|
|
1874
|
+
} else if (a === "--output" && args[i + 1] !== void 0) {
|
|
1875
|
+
out = args[++i];
|
|
1876
|
+
} else if (a === "--record") {
|
|
1877
|
+
record = args[i + 1] !== void 0 && !args[i + 1].startsWith("-") ? args[++i] : DEFAULT_RATE_SERIES;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
const resolved = store ?? defaultStore(process.cwd());
|
|
1881
|
+
if (!fs7.existsSync(resolved)) {
|
|
1882
|
+
process.stderr.write(`conformance:behavior: no transcript store at ${resolved}
|
|
1883
|
+
`);
|
|
1884
|
+
return 1;
|
|
1885
|
+
}
|
|
1886
|
+
const report = scanStore(resolved, Number.isFinite(limit) && limit > 0 ? limit : 30);
|
|
1887
|
+
if (out) {
|
|
1888
|
+
fs7.writeFileSync(out, `${JSON.stringify(report, null, 2)}
|
|
1889
|
+
`, "utf8");
|
|
1890
|
+
}
|
|
1891
|
+
if (record) {
|
|
1892
|
+
recordRate(record, report.scanned_at, report.rate);
|
|
1893
|
+
}
|
|
1894
|
+
fs7.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
|
|
1895
|
+
` : `${render(report)}
|
|
1896
|
+
`);
|
|
1897
|
+
return 0;
|
|
1898
|
+
}
|
|
1899
|
+
function _isCliEntry6() {
|
|
1900
|
+
if (true) {
|
|
1901
|
+
return false;
|
|
1902
|
+
}
|
|
1903
|
+
if (process.argv[1] === void 0) {
|
|
1904
|
+
return false;
|
|
1905
|
+
}
|
|
1906
|
+
const argvUrl = pathToFileURL6(path7.resolve(process.argv[1])).href;
|
|
1907
|
+
if (import.meta.url === argvUrl) {
|
|
1908
|
+
return true;
|
|
1909
|
+
}
|
|
1910
|
+
try {
|
|
1911
|
+
const here = fs7.realpathSync(fileURLToPath6(import.meta.url));
|
|
1912
|
+
const argv = fs7.realpathSync(path7.resolve(process.argv[1]));
|
|
1913
|
+
return here === argv;
|
|
1914
|
+
} catch {
|
|
1915
|
+
return false;
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
if (_isCliEntry6()) {
|
|
1919
|
+
process.exit(main6());
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
// src/scripts/report_carrier_divergence.ts
|
|
1923
|
+
import * as fs8 from "node:fs";
|
|
1924
|
+
import * as os2 from "node:os";
|
|
1925
|
+
import * as path8 from "node:path";
|
|
1926
|
+
import process2 from "node:process";
|
|
1927
|
+
import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL7 } from "node:url";
|
|
1928
|
+
var _HERE = fileURLToPath7(import.meta.url);
|
|
1929
|
+
var REPO_ROOT2 = path8.resolve(path8.dirname(_HERE), "..", "..");
|
|
1930
|
+
var PROJECT_RULES = path8.join(".claude", "rules");
|
|
1931
|
+
var PROJECTION_SOURCE = path8.join("dist", "agent-src", "rules");
|
|
1932
|
+
var GLOBAL_RULES = path8.join(".claude", "rules");
|
|
1933
|
+
function _listRules(dir) {
|
|
1934
|
+
try {
|
|
1935
|
+
return fs8.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
1936
|
+
} catch {
|
|
56
1937
|
return [];
|
|
57
1938
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
1939
|
+
}
|
|
1940
|
+
function _ruleType(file) {
|
|
1941
|
+
let text;
|
|
1942
|
+
try {
|
|
1943
|
+
text = fs8.readFileSync(file, "utf-8");
|
|
1944
|
+
} catch {
|
|
1945
|
+
return "";
|
|
1946
|
+
}
|
|
1947
|
+
if (!text.startsWith("---")) return "";
|
|
1948
|
+
const end = text.indexOf("\n---", 3);
|
|
1949
|
+
const fm = end === -1 ? text : text.slice(0, end);
|
|
1950
|
+
const m = /^type:\s*"?([a-z]+)"?/m.exec(fm);
|
|
1951
|
+
return m === null ? "" : m[1];
|
|
1952
|
+
}
|
|
1953
|
+
function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
1954
|
+
const out = {
|
|
1955
|
+
projectDir,
|
|
1956
|
+
globalDir,
|
|
1957
|
+
globalPresent: fs8.existsSync(globalDir),
|
|
1958
|
+
projectPresent: fs8.existsSync(projectDir),
|
|
1959
|
+
shared: 0,
|
|
1960
|
+
identical: [],
|
|
1961
|
+
provenanceOnly: [],
|
|
1962
|
+
bodyDiff: [],
|
|
1963
|
+
projectOnly: [],
|
|
1964
|
+
globalOnly: [],
|
|
1965
|
+
unreadable: [],
|
|
1966
|
+
manualOnlyGlobal: []
|
|
1967
|
+
};
|
|
1968
|
+
const project = new Set(_listRules(projectDir));
|
|
1969
|
+
const global = new Set(_listRules(globalDir));
|
|
1970
|
+
for (const rule of [.../* @__PURE__ */ new Set([...project, ...global])].sort()) {
|
|
1971
|
+
const inP = project.has(rule);
|
|
1972
|
+
const inG = global.has(rule);
|
|
1973
|
+
if (inP && !inG) {
|
|
1974
|
+
out.projectOnly.push(rule);
|
|
1975
|
+
continue;
|
|
66
1976
|
}
|
|
1977
|
+
if (!inP && inG) {
|
|
1978
|
+
out.globalOnly.push(rule);
|
|
1979
|
+
if (sourceDir !== void 0 && _ruleType(path8.join(sourceDir, rule)) === "manual") {
|
|
1980
|
+
out.manualOnlyGlobal.push(rule);
|
|
1981
|
+
}
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1984
|
+
out.shared += 1;
|
|
1985
|
+
let verdict;
|
|
1986
|
+
try {
|
|
1987
|
+
verdict = comparePair(
|
|
1988
|
+
fs8.readFileSync(path8.join(projectDir, rule)),
|
|
1989
|
+
fs8.readFileSync(path8.join(globalDir, rule))
|
|
1990
|
+
);
|
|
1991
|
+
} catch {
|
|
1992
|
+
out.shared -= 1;
|
|
1993
|
+
out.unreadable.push(rule);
|
|
1994
|
+
continue;
|
|
1995
|
+
}
|
|
1996
|
+
if (verdict === "identical") out.identical.push(rule);
|
|
1997
|
+
else if (verdict === "provenance-only") out.provenanceOnly.push(rule);
|
|
1998
|
+
else out.bodyDiff.push(rule);
|
|
67
1999
|
}
|
|
68
2000
|
return out;
|
|
69
2001
|
}
|
|
70
|
-
function
|
|
2002
|
+
function render2(d) {
|
|
2003
|
+
const lines = [];
|
|
2004
|
+
lines.push("cross-carrier rule divergence \u2014 advisory, gates on nothing");
|
|
2005
|
+
lines.push(` project carrier: ${d.projectDir}`);
|
|
2006
|
+
lines.push(` global carrier: ${d.globalDir}`);
|
|
2007
|
+
lines.push("");
|
|
2008
|
+
if (!d.globalPresent) {
|
|
2009
|
+
lines.push(" The global carrier is absent \u2014 this machine loads the project projection");
|
|
2010
|
+
lines.push(" only, so there is no second copy to diverge from. Nothing to report, and");
|
|
2011
|
+
lines.push(" that is a real answer rather than a skipped check.");
|
|
2012
|
+
return lines.join("\n");
|
|
2013
|
+
}
|
|
2014
|
+
if (!d.projectPresent) {
|
|
2015
|
+
lines.push(" The project rule tree does not exist in this checkout \u2014 it is generated and");
|
|
2016
|
+
lines.push(" gitignored, so a fresh clone has none, and `agents/.agent-tools.yml` may");
|
|
2017
|
+
lines.push(" select zero tools deliberately. Run `task generate-tools` and re-run this");
|
|
2018
|
+
lines.push(" report. Reported rather than silently substituted with the projection");
|
|
2019
|
+
lines.push(" source: dist/ holds rules the project tree omits by design, so reading it");
|
|
2020
|
+
lines.push(" as the project carrier would answer a different question than the one asked.");
|
|
2021
|
+
return lines.join("\n");
|
|
2022
|
+
}
|
|
2023
|
+
lines.push(` shared rule names ${d.shared}`);
|
|
2024
|
+
lines.push(` byte-identical ${d.identical.length}`);
|
|
2025
|
+
lines.push(` differ ONLY in the install stamp ${d.provenanceOnly.length}`);
|
|
2026
|
+
lines.push(` differ in BODY ${d.bodyDiff.length}`);
|
|
2027
|
+
if (d.unreadable.length > 0) {
|
|
2028
|
+
lines.push(` unreadable on one side ${d.unreadable.length} (${d.unreadable.join(", ")})`);
|
|
2029
|
+
lines.push(" Not counted as shared and NOT a body difference \u2014 a dangling entry is");
|
|
2030
|
+
lines.push(" a broken install, not a disagreement about content.");
|
|
2031
|
+
}
|
|
2032
|
+
lines.push(` present at project scope only ${d.projectOnly.length}`);
|
|
2033
|
+
lines.push(` present at global scope only ${d.globalOnly.length}`);
|
|
2034
|
+
lines.push("");
|
|
2035
|
+
if (d.bodyDiff.length > 0) {
|
|
2036
|
+
lines.push(" BODY DIVERGENCE \u2014 the two carriers deliver different text for these rules.");
|
|
2037
|
+
lines.push(" Both copies reach the model, with no precedence marker between them, so a");
|
|
2038
|
+
lines.push(" claim one copy retracts can be re-asserted by the other. PRECEDENCE: the");
|
|
2039
|
+
lines.push(" project projection is generated from src/ at this commit and wins; the");
|
|
2040
|
+
lines.push(" global install is a release snapshot. Close it by reinstalling the global");
|
|
2041
|
+
lines.push(" copy, or by reading the project copy as authoritative.");
|
|
2042
|
+
for (const r of d.bodyDiff) lines.push(` - ${r}`);
|
|
2043
|
+
lines.push("");
|
|
2044
|
+
}
|
|
2045
|
+
if (d.provenanceOnly.length > 0) {
|
|
2046
|
+
lines.push(` ${String(d.provenanceOnly.length)} pair(s) differ only by the installer's package:/source_path: stamp.`);
|
|
2047
|
+
lines.push(" Not a content difference and NOT a defect: install.ts stamps every installed");
|
|
2048
|
+
lines.push(" rule, the in-repo projection stamps nothing. Closing it was decided against");
|
|
2049
|
+
lines.push(" on 2026-07-31 \u2014 agents/settings/contexts/dedup-reachability-refusal.md holds");
|
|
2050
|
+
lines.push(" the analysis and the five reopen conditions. Reported as a count on purpose:");
|
|
2051
|
+
lines.push(" naming every expected pair would bury the class above.");
|
|
2052
|
+
lines.push("");
|
|
2053
|
+
}
|
|
2054
|
+
if (d.projectOnly.length > 0) {
|
|
2055
|
+
lines.push(` project-scope only (${String(d.projectOnly.length)}) \u2014 newer than the installed release:`);
|
|
2056
|
+
lines.push(` ${d.projectOnly.join(", ")}`);
|
|
2057
|
+
}
|
|
2058
|
+
const driftGlobalOnly = d.globalOnly.filter((r) => !d.manualOnlyGlobal.includes(r));
|
|
2059
|
+
if (driftGlobalOnly.length > 0) {
|
|
2060
|
+
lines.push(` global-scope only (${String(driftGlobalOnly.length)}) \u2014 the release carries these, this checkout does not project them:`);
|
|
2061
|
+
lines.push(` ${driftGlobalOnly.join(", ")}`);
|
|
2062
|
+
}
|
|
2063
|
+
if (d.manualOnlyGlobal.length > 0) {
|
|
2064
|
+
lines.push(
|
|
2065
|
+
` delivered by the global carrier ONLY, by design (${String(d.manualOnlyGlobal.length)}, ADR-004 \`type: manual\`):`
|
|
2066
|
+
);
|
|
2067
|
+
lines.push(` ${d.manualOnlyGlobal.join(", ")}`);
|
|
2068
|
+
lines.push(" The project projection filters these out and the global installer does not.");
|
|
2069
|
+
lines.push(" Two writers, different filters \u2014 so on a machine with both carriers these");
|
|
2070
|
+
lines.push(" rules reach the model, and on a project-only machine they do not. Expected,");
|
|
2071
|
+
lines.push(" and worth knowing before citing any project-scope reach figure.");
|
|
2072
|
+
}
|
|
2073
|
+
if (d.projectOnly.length > 0 || d.globalOnly.length > 0) {
|
|
2074
|
+
lines.push("");
|
|
2075
|
+
lines.push(" Asymmetric reach is why a project-scope conformance figure does not transfer");
|
|
2076
|
+
lines.push(" to another machine: the rule set the model was told differs by carrier.");
|
|
2077
|
+
}
|
|
2078
|
+
if (d.bodyDiff.length === 0) {
|
|
2079
|
+
lines.push("");
|
|
2080
|
+
lines.push(" No body divergence. The condition is transient \u2014 it returns whenever the");
|
|
2081
|
+
lines.push(" checkout moves ahead of the installed release \u2014 so this is a reading of");
|
|
2082
|
+
lines.push(" right now, not a property of the repo. Re-run it, do not cite it.");
|
|
2083
|
+
}
|
|
2084
|
+
return lines.join("\n");
|
|
2085
|
+
}
|
|
2086
|
+
function main7(argv) {
|
|
2087
|
+
const args = argv ?? process2.argv.slice(2);
|
|
2088
|
+
let projectDir = path8.join(REPO_ROOT2, PROJECT_RULES);
|
|
2089
|
+
let globalDir = path8.join(os2.homedir(), GLOBAL_RULES);
|
|
2090
|
+
let sourceDir = path8.join(REPO_ROOT2, PROJECTION_SOURCE);
|
|
2091
|
+
const value = (i) => {
|
|
2092
|
+
const v = args[i + 1];
|
|
2093
|
+
return v === void 0 || v.startsWith("-") ? null : v;
|
|
2094
|
+
};
|
|
2095
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2096
|
+
const a = args[i];
|
|
2097
|
+
if (a === "--project" || a === "--global" || a === "--source") {
|
|
2098
|
+
const v = value(i);
|
|
2099
|
+
if (v === null) {
|
|
2100
|
+
process2.stderr.write(`report_carrier_divergence: ${a} needs a directory
|
|
2101
|
+
`);
|
|
2102
|
+
return 1;
|
|
2103
|
+
}
|
|
2104
|
+
const resolved = path8.resolve(v);
|
|
2105
|
+
if (a === "--project") projectDir = resolved;
|
|
2106
|
+
else if (a === "--global") globalDir = resolved;
|
|
2107
|
+
else sourceDir = resolved;
|
|
2108
|
+
i += 1;
|
|
2109
|
+
} else if (a === "--help" || a === "-h") {
|
|
2110
|
+
process2.stdout.write(
|
|
2111
|
+
"usage: report_carrier_divergence [--project DIR] [--global DIR] [--source DIR]\n"
|
|
2112
|
+
);
|
|
2113
|
+
return 0;
|
|
2114
|
+
} else if (a !== void 0 && a.startsWith("--") && a !== "--quiet") {
|
|
2115
|
+
process2.stderr.write(`report_carrier_divergence: unknown flag ${a}
|
|
2116
|
+
`);
|
|
2117
|
+
return 1;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
process2.stdout.write(`${render2(compareCarriers(projectDir, globalDir, sourceDir))}
|
|
2121
|
+
`);
|
|
2122
|
+
return 0;
|
|
2123
|
+
}
|
|
2124
|
+
function _isCliEntry7() {
|
|
2125
|
+
if (process2.argv[1] === void 0) return false;
|
|
2126
|
+
if (import.meta.url === pathToFileURL7(path8.resolve(process2.argv[1])).href) return true;
|
|
71
2127
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
2128
|
+
return fs8.realpathSync(_HERE) === fs8.realpathSync(path8.resolve(process2.argv[1]));
|
|
2129
|
+
} catch {
|
|
2130
|
+
return false;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
if (_isCliEntry7()) {
|
|
2134
|
+
process2.exit(main7());
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
// src/scripts/report_skill_activation.ts
|
|
2138
|
+
import * as fs9 from "node:fs";
|
|
2139
|
+
import * as os3 from "node:os";
|
|
2140
|
+
import * as path9 from "node:path";
|
|
2141
|
+
import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL8 } from "node:url";
|
|
2142
|
+
var _HERE2 = fileURLToPath8(import.meta.url);
|
|
2143
|
+
var REPO_ROOT3 = path9.resolve(path9.dirname(_HERE2), "..", "..");
|
|
2144
|
+
var SKILLS_ROOT = "src/skills";
|
|
2145
|
+
var TRIGGER_KEYS = ["triggers:", "trigger_description:", "file_pattern:", "path_prefix:"];
|
|
2146
|
+
var DETERMINISTIC_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?(MUST|NEVER|ALWAYS)\b/m;
|
|
2147
|
+
function censusSkills(root) {
|
|
2148
|
+
const out = { total: 0, withTriggerKey: [], withDeterministicObligation: [] };
|
|
2149
|
+
if (!fs9.existsSync(root)) {
|
|
2150
|
+
return out;
|
|
2151
|
+
}
|
|
2152
|
+
for (const entry of fs9.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2153
|
+
if (!entry.isDirectory()) {
|
|
2154
|
+
continue;
|
|
2155
|
+
}
|
|
2156
|
+
const file = path9.join(root, entry.name, "SKILL.md");
|
|
2157
|
+
if (!fs9.existsSync(file)) {
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
out.total += 1;
|
|
2161
|
+
const text = fs9.readFileSync(file, "utf8");
|
|
2162
|
+
const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
|
|
2163
|
+
const fm = fmEnd === -1 ? "" : text.slice(0, fmEnd);
|
|
2164
|
+
if (TRIGGER_KEYS.some((k) => new RegExp(`^${k}`, "m").test(fm))) {
|
|
2165
|
+
out.withTriggerKey.push(entry.name);
|
|
2166
|
+
}
|
|
2167
|
+
const body = fmEnd === -1 ? text : text.slice(fmEnd + 4);
|
|
2168
|
+
if (DETERMINISTIC_RE.test(body)) {
|
|
2169
|
+
out.withDeterministicObligation.push(entry.name);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
return out;
|
|
2173
|
+
}
|
|
2174
|
+
function defaultStore2(cwd) {
|
|
2175
|
+
return path9.join(os3.homedir(), ".claude", "projects", cwd.replace(/\//g, "-"));
|
|
2176
|
+
}
|
|
2177
|
+
function measureUsage(store, limit) {
|
|
2178
|
+
const rep = { store, sessions: 0, assistantTurns: 0, invocations: 0, bySkill: {} };
|
|
2179
|
+
if (!fs9.existsSync(store)) {
|
|
2180
|
+
return rep;
|
|
2181
|
+
}
|
|
2182
|
+
const files = fs9.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path9.join(store, f)).sort((a, b) => fs9.statSync(b).mtimeMs - fs9.statSync(a).mtimeMs).slice(0, limit);
|
|
2183
|
+
rep.sessions = files.length;
|
|
2184
|
+
for (const file of files) {
|
|
2185
|
+
for (const line of fs9.readFileSync(file, "utf8").split("\n")) {
|
|
2186
|
+
if (!line.trim()) {
|
|
2187
|
+
continue;
|
|
2188
|
+
}
|
|
2189
|
+
let entry;
|
|
2190
|
+
try {
|
|
2191
|
+
entry = JSON.parse(line);
|
|
2192
|
+
} catch {
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
if (entry["type"] !== "assistant") {
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
rep.assistantTurns += 1;
|
|
2199
|
+
const msg = entry["message"];
|
|
2200
|
+
const content = msg !== null && typeof msg === "object" ? msg["content"] : null;
|
|
2201
|
+
if (!Array.isArray(content)) {
|
|
2202
|
+
continue;
|
|
2203
|
+
}
|
|
2204
|
+
for (const part of content) {
|
|
2205
|
+
if (part === null || typeof part !== "object") {
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
const p = part;
|
|
2209
|
+
if (p["type"] !== "tool_use" || p["name"] !== "Skill") {
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
rep.invocations += 1;
|
|
2213
|
+
const input = p["input"];
|
|
2214
|
+
const name = input !== null && typeof input === "object" ? input["skill"] : null;
|
|
2215
|
+
const key = typeof name === "string" && name !== "" ? name : "<unnamed>";
|
|
2216
|
+
rep.bySkill[key] = (rep.bySkill[key] ?? 0) + 1;
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
return rep;
|
|
2221
|
+
}
|
|
2222
|
+
function _pct(n, d) {
|
|
2223
|
+
return d === 0 ? "n/a" : `${(100 * n / d).toFixed(1)}%`;
|
|
2224
|
+
}
|
|
2225
|
+
function render3(census, usage) {
|
|
2226
|
+
const lines = [];
|
|
2227
|
+
lines.push("skill activation census \u2014 advisory, gates on nothing");
|
|
2228
|
+
lines.push("");
|
|
2229
|
+
lines.push(` skills shipped ${census.total}`);
|
|
2230
|
+
lines.push(
|
|
2231
|
+
` with a machine-matchable trigger key ${census.withTriggerKey.length} (${_pct(census.withTriggerKey.length, census.total)})`
|
|
2232
|
+
);
|
|
2233
|
+
lines.push(
|
|
2234
|
+
` with a deterministic obligation ${census.withDeterministicObligation.length} (${_pct(census.withDeterministicObligation.length, census.total)})`
|
|
2235
|
+
);
|
|
2236
|
+
if (census.withDeterministicObligation.length > 0) {
|
|
2237
|
+
lines.push(` ${census.withDeterministicObligation.join(", ")}`);
|
|
2238
|
+
}
|
|
2239
|
+
lines.push("");
|
|
2240
|
+
let totalInv = 0;
|
|
2241
|
+
const distinct = /* @__PURE__ */ new Set();
|
|
2242
|
+
for (const u of usage) {
|
|
2243
|
+
for (const k of Object.keys(u.bySkill)) {
|
|
2244
|
+
distinct.add(k);
|
|
2245
|
+
}
|
|
2246
|
+
totalInv += u.invocations;
|
|
2247
|
+
lines.push(
|
|
2248
|
+
` ${path9.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)}`
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
lines.push("");
|
|
2252
|
+
lines.push(` invocations total ${totalInv}`);
|
|
2253
|
+
lines.push(
|
|
2254
|
+
` distinct skills invoked ${distinct.size} of ${census.total} (${_pct(distinct.size, census.total)})`
|
|
2255
|
+
);
|
|
2256
|
+
if (distinct.size > 0) {
|
|
2257
|
+
lines.push(` ${[...distinct].sort().join(", ")}`);
|
|
2258
|
+
}
|
|
2259
|
+
lines.push("");
|
|
2260
|
+
if (census.withTriggerKey.length === 0) {
|
|
2261
|
+
lines.push(" VERDICT: activation is not measurable as a rate. No skill declares a");
|
|
2262
|
+
lines.push(" machine-matchable trigger, so a missed-activation detector has nothing to");
|
|
2263
|
+
lines.push(" match against, and matching the description prose is the class this suite");
|
|
2264
|
+
lines.push(" excludes. The counts above are the finding, not an input to a percentage.");
|
|
2265
|
+
}
|
|
2266
|
+
lines.push("");
|
|
2267
|
+
lines.push(" NOT MEASURED: whether each skill reached the model WITH its description.");
|
|
2268
|
+
lines.push(" The host's injected catalogue is not persisted in the transcript, so the");
|
|
2269
|
+
lines.push(" bare-name hypothesis is a single-session observation. Falsifier: log the");
|
|
2270
|
+
lines.push(" catalogue once per session, then count descriptions against bare names.");
|
|
2271
|
+
return lines.join("\n");
|
|
2272
|
+
}
|
|
2273
|
+
function main8(argv) {
|
|
2274
|
+
let limit = 30;
|
|
2275
|
+
const stores = [];
|
|
2276
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
2277
|
+
const a = argv[i];
|
|
2278
|
+
if (a === "--limit" && argv[i + 1] !== void 0) {
|
|
2279
|
+
limit = Number.parseInt(argv[i + 1], 10);
|
|
2280
|
+
i += 1;
|
|
2281
|
+
} else if (a === "--store" && argv[i + 1] !== void 0) {
|
|
2282
|
+
stores.push(argv[i + 1]);
|
|
2283
|
+
i += 1;
|
|
2284
|
+
} else if (a === "--help" || a === "-h") {
|
|
2285
|
+
process.stdout.write("usage: report_skill_activation [--limit N] [--store PATH]...\n");
|
|
2286
|
+
return 0;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
2290
|
+
process.stderr.write("report_skill_activation: --limit must be a positive integer\n");
|
|
2291
|
+
return 1;
|
|
2292
|
+
}
|
|
2293
|
+
if (stores.length === 0) {
|
|
2294
|
+
stores.push(defaultStore2(REPO_ROOT3));
|
|
2295
|
+
}
|
|
2296
|
+
const census = censusSkills(path9.join(REPO_ROOT3, SKILLS_ROOT));
|
|
2297
|
+
const usage = stores.map((s) => measureUsage(s, limit));
|
|
2298
|
+
process.stdout.write(`${render3(census, usage)}
|
|
2299
|
+
`);
|
|
2300
|
+
return 0;
|
|
2301
|
+
}
|
|
2302
|
+
if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL8(path9.resolve(process.argv[1])).href) {
|
|
2303
|
+
process.exit(main8(process.argv.slice(2)));
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
// src/scripts/report_skill_obligation_violations.ts
|
|
2307
|
+
import * as fs10 from "node:fs";
|
|
2308
|
+
import * as os4 from "node:os";
|
|
2309
|
+
import * as path10 from "node:path";
|
|
2310
|
+
import process3 from "node:process";
|
|
2311
|
+
import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL9 } from "node:url";
|
|
2312
|
+
var _HERE3 = fileURLToPath9(import.meta.url);
|
|
2313
|
+
var REPO_ROOT4 = path10.resolve(path10.dirname(_HERE3), "..", "..");
|
|
2314
|
+
var OBLIGATION_LINE_RE = new RegExp(`${DETERMINISTIC_RE.source}.*`, "gm");
|
|
2315
|
+
var PATH_RE = /`([A-Za-z0-9_./-]*\/[A-Za-z0-9_.-]+\.[a-z]{2,6})`/g;
|
|
2316
|
+
var CMD_RE = /`([a-z][a-z0-9-]*(?: [a-z][a-z0-9:._-]*){1,3})`/g;
|
|
2317
|
+
var CMD_HEADS = /* @__PURE__ */ new Set([
|
|
2318
|
+
"cargo",
|
|
2319
|
+
"npm",
|
|
2320
|
+
"pnpm",
|
|
2321
|
+
"yarn",
|
|
2322
|
+
"git",
|
|
2323
|
+
"php",
|
|
2324
|
+
"composer",
|
|
2325
|
+
"task",
|
|
2326
|
+
"make",
|
|
2327
|
+
"npx",
|
|
2328
|
+
"gh",
|
|
2329
|
+
"python3",
|
|
2330
|
+
"docker"
|
|
2331
|
+
]);
|
|
2332
|
+
var PIVOT_RE = /\s—\s|\s–\s|→|;\s|\s—use\b|\buse instead\b/;
|
|
2333
|
+
function _verb(line) {
|
|
2334
|
+
const m = /\b(NEVER|MUST|ALWAYS)\b/.exec(line);
|
|
2335
|
+
return m?.[1] ?? "NEVER";
|
|
2336
|
+
}
|
|
2337
|
+
function _artefacts(line) {
|
|
2338
|
+
const out = [];
|
|
2339
|
+
for (const m of line.matchAll(PATH_RE)) {
|
|
2340
|
+
out.push({ artefact: m[1], kind: "path" });
|
|
2341
|
+
}
|
|
2342
|
+
for (const m of line.matchAll(CMD_RE)) {
|
|
2343
|
+
const cmd = m[1];
|
|
2344
|
+
if (CMD_HEADS.has(cmd.split(" ")[0])) {
|
|
2345
|
+
out.push({ artefact: cmd, kind: "command" });
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
return out;
|
|
2349
|
+
}
|
|
2350
|
+
function extractObligations(repoRoot) {
|
|
2351
|
+
const skillsRoot = path10.join(repoRoot, SKILLS_ROOT);
|
|
2352
|
+
const census = censusSkills(skillsRoot);
|
|
2353
|
+
const out = {
|
|
2354
|
+
skills: census.withDeterministicObligation,
|
|
2355
|
+
totalLines: 0,
|
|
2356
|
+
withArtefact: [],
|
|
2357
|
+
linesWithArtefact: 0,
|
|
2358
|
+
forbidden: [],
|
|
2359
|
+
prescribed: [],
|
|
2360
|
+
required: []
|
|
2361
|
+
};
|
|
2362
|
+
for (const skill of census.withDeterministicObligation) {
|
|
2363
|
+
let text;
|
|
2364
|
+
try {
|
|
2365
|
+
text = fs10.readFileSync(path10.join(skillsRoot, skill, "SKILL.md"), "utf-8");
|
|
2366
|
+
} catch {
|
|
2367
|
+
continue;
|
|
2368
|
+
}
|
|
2369
|
+
const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
|
|
2370
|
+
const body = fmEnd === -1 ? text : text.slice(fmEnd + 4);
|
|
2371
|
+
for (const raw of body.match(OBLIGATION_LINE_RE) ?? []) {
|
|
2372
|
+
const line = raw.trim();
|
|
2373
|
+
out.totalLines += 1;
|
|
2374
|
+
const verb = _verb(line);
|
|
2375
|
+
const pivot = PIVOT_RE.exec(line);
|
|
2376
|
+
const head = pivot === null ? line : line.slice(0, pivot.index);
|
|
2377
|
+
const found = _artefacts(line);
|
|
2378
|
+
if (found.length > 0) {
|
|
2379
|
+
out.linesWithArtefact += 1;
|
|
2380
|
+
}
|
|
2381
|
+
for (const a of found) {
|
|
2382
|
+
const polarity = verb === "NEVER" ? head.includes(`\`${a.artefact}\``) ? "forbidden" : "prescribed" : "required";
|
|
2383
|
+
const ob = { skill, line, verb, artefact: a.artefact, kind: a.kind, polarity };
|
|
2384
|
+
out.withArtefact.push(ob);
|
|
2385
|
+
if (polarity === "forbidden") out.forbidden.push(ob);
|
|
2386
|
+
else if (polarity === "prescribed") out.prescribed.push(ob);
|
|
2387
|
+
else out.required.push(ob);
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
return out;
|
|
2392
|
+
}
|
|
2393
|
+
function _parseSession(lines) {
|
|
2394
|
+
return lines.map((line) => {
|
|
2395
|
+
if (!line.trim()) return null;
|
|
2396
|
+
try {
|
|
2397
|
+
return JSON.parse(line);
|
|
2398
|
+
} catch {
|
|
74
2399
|
return null;
|
|
75
2400
|
}
|
|
76
|
-
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
2404
|
+
"Edit",
|
|
2405
|
+
"Write",
|
|
2406
|
+
"NotebookEdit",
|
|
2407
|
+
"MultiEdit",
|
|
2408
|
+
"str-replace-editor",
|
|
2409
|
+
"str_replace_editor",
|
|
2410
|
+
"save-file",
|
|
2411
|
+
"save_file"
|
|
2412
|
+
]);
|
|
2413
|
+
var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "BashTool", "launch-process", "launch_process"]);
|
|
2414
|
+
var SKILL_BODY_RE = /Base directory for this skill:\s*\S*?\/skills\/([A-Za-z0-9_-]+)/;
|
|
2415
|
+
function loadedSkillsFrom(entries) {
|
|
2416
|
+
const first = /* @__PURE__ */ new Map();
|
|
2417
|
+
const note = (name, at) => {
|
|
2418
|
+
const key = name.replace(/:/g, "-");
|
|
2419
|
+
if (!first.has(key)) first.set(key, at);
|
|
2420
|
+
};
|
|
2421
|
+
entries.forEach((entry, i) => {
|
|
2422
|
+
if (entry === null || isSidechain(entry)) return;
|
|
2423
|
+
if (entry["type"] === "user") {
|
|
2424
|
+
const m = SKILL_BODY_RE.exec(entryText(entry));
|
|
2425
|
+
if (m !== null) note(m[1], i);
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
if (entry["type"] !== "assistant") return;
|
|
2429
|
+
for (const t of toolUses(entry)) {
|
|
2430
|
+
if (t.name !== "Skill") continue;
|
|
2431
|
+
const s = t.input["skill"];
|
|
2432
|
+
if (typeof s === "string" && s !== "") note(s, i);
|
|
2433
|
+
}
|
|
2434
|
+
});
|
|
2435
|
+
return first;
|
|
2436
|
+
}
|
|
2437
|
+
var PATH_FIELDS = ["file_path", "path", "filePath", "notebook_path", "target_file"];
|
|
2438
|
+
var COMMAND_FIELDS = ["command", "cmd"];
|
|
2439
|
+
function _targetText(input, kind) {
|
|
2440
|
+
const fields = kind === "path" ? PATH_FIELDS : COMMAND_FIELDS;
|
|
2441
|
+
return fields.map((f) => input[f]).filter((v) => typeof v === "string").join("\n");
|
|
2442
|
+
}
|
|
2443
|
+
function scanSessionForViolations(session, lines, forbidden, parsed) {
|
|
2444
|
+
const entries = parsed ?? _parseSession(lines);
|
|
2445
|
+
const loaded = loadedSkillsFrom(entries);
|
|
2446
|
+
if (loaded.size === 0) return [];
|
|
2447
|
+
const relevant = forbidden.filter((o) => loaded.has(o.skill));
|
|
2448
|
+
if (relevant.length === 0) return [];
|
|
2449
|
+
const flags = [];
|
|
2450
|
+
entries.forEach((entry, i) => {
|
|
2451
|
+
if (entry === null || isSidechain(entry)) return;
|
|
2452
|
+
if (entry["type"] !== "assistant") return;
|
|
2453
|
+
for (const t of toolUses(entry)) {
|
|
2454
|
+
const isWrite = WRITE_TOOLS.has(t.name);
|
|
2455
|
+
const isShell = SHELL_TOOLS.has(t.name);
|
|
2456
|
+
if (!isWrite && !isShell) continue;
|
|
2457
|
+
for (const o of relevant) {
|
|
2458
|
+
const loadedAt = loaded.get(o.skill);
|
|
2459
|
+
if (i <= loadedAt) continue;
|
|
2460
|
+
if (o.kind === "path" && !isWrite) continue;
|
|
2461
|
+
if (o.kind === "command" && !isShell) continue;
|
|
2462
|
+
const text = _targetText(t.input, o.kind);
|
|
2463
|
+
if (!text.includes(o.artefact)) continue;
|
|
2464
|
+
const at = text.indexOf(o.artefact);
|
|
2465
|
+
flags.push({
|
|
2466
|
+
session,
|
|
2467
|
+
skill: o.skill,
|
|
2468
|
+
artefact: o.artefact,
|
|
2469
|
+
kind: o.kind,
|
|
2470
|
+
at: i,
|
|
2471
|
+
loadedAt,
|
|
2472
|
+
tool: t.name,
|
|
2473
|
+
evidence: text.slice(Math.max(0, at - 40), at + o.artefact.length + 40).replace(/\n/g, " ")
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
});
|
|
2478
|
+
return flags;
|
|
2479
|
+
}
|
|
2480
|
+
function defaultStore3(cwd) {
|
|
2481
|
+
return path10.join(os4.homedir(), ".claude", "projects", cwd.replace(/[/.]/g, "-"));
|
|
2482
|
+
}
|
|
2483
|
+
function scanStore2(repoRoot, store, limit) {
|
|
2484
|
+
const census = extractObligations(repoRoot);
|
|
2485
|
+
const report = {
|
|
2486
|
+
store,
|
|
2487
|
+
sessions: 0,
|
|
2488
|
+
sessionsWithASkill: 0,
|
|
2489
|
+
census,
|
|
2490
|
+
flags: []
|
|
2491
|
+
};
|
|
2492
|
+
let files;
|
|
2493
|
+
try {
|
|
2494
|
+
files = fs10.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path10.join(store, f), m: fs10.statSync(path10.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
|
|
77
2495
|
} catch {
|
|
78
|
-
return
|
|
2496
|
+
return report;
|
|
2497
|
+
}
|
|
2498
|
+
report.sessions = files.length;
|
|
2499
|
+
for (const file of files) {
|
|
2500
|
+
const lines = fs10.readFileSync(file, "utf-8").split("\n");
|
|
2501
|
+
const id = path10.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
|
|
2502
|
+
const entries = _parseSession(lines);
|
|
2503
|
+
if (loadedSkillsFrom(entries).size > 0) report.sessionsWithASkill += 1;
|
|
2504
|
+
report.flags.push(...scanSessionForViolations(id, lines, census.forbidden, entries));
|
|
2505
|
+
}
|
|
2506
|
+
return report;
|
|
2507
|
+
}
|
|
2508
|
+
function _pct2(n, d) {
|
|
2509
|
+
return d === 0 ? "n/a" : `${(100 * n / d).toFixed(1)}%`;
|
|
2510
|
+
}
|
|
2511
|
+
function render4(r) {
|
|
2512
|
+
const c = r.census;
|
|
2513
|
+
const lines = [];
|
|
2514
|
+
lines.push("SK-2 loaded-but-violated \u2014 advisory, gates on nothing");
|
|
2515
|
+
lines.push("");
|
|
2516
|
+
lines.push(" COVERAGE (the headline \u2014 read this before any violation count)");
|
|
2517
|
+
lines.push(` skills with a deterministic obligation ${c.skills.length}`);
|
|
2518
|
+
lines.push(` obligation lines in them ${c.totalLines}`);
|
|
2519
|
+
lines.push(
|
|
2520
|
+
` \u2026LINES naming a concrete artefact ${c.linesWithArtefact} (${_pct2(c.linesWithArtefact, c.totalLines)})`
|
|
2521
|
+
);
|
|
2522
|
+
lines.push(` \u2026artefacts named across those lines ${c.withArtefact.length}`);
|
|
2523
|
+
lines.push(` \u2026of those, the FORBIDDEN artefact ${c.forbidden.length}`);
|
|
2524
|
+
lines.push(` \u2026the PRESCRIBED alternative (excluded) ${c.prescribed.length}`);
|
|
2525
|
+
lines.push(` \u2026REQUIRED by a MUST/ALWAYS (excluded) ${c.required.length}`);
|
|
2526
|
+
lines.push(
|
|
2527
|
+
` testable without judgement ${c.forbidden.length} artefact(s) over ${c.totalLines} obligation line(s)`
|
|
2528
|
+
);
|
|
2529
|
+
lines.push("");
|
|
2530
|
+
lines.push(" The other lines are deterministic in wording and need a reading to test");
|
|
2531
|
+
lines.push(' ("NEVER return `clean` out of politeness"). That is the FC-8 class this suite');
|
|
2532
|
+
lines.push(" excludes, so it is reported as uncovered rather than approximated.");
|
|
2533
|
+
lines.push("");
|
|
2534
|
+
if (c.forbidden.length > 0) {
|
|
2535
|
+
lines.push(" Mechanised obligations:");
|
|
2536
|
+
for (const o of c.forbidden) {
|
|
2537
|
+
lines.push(` [${o.skill}] ${o.kind}: ${o.artefact}`);
|
|
2538
|
+
}
|
|
2539
|
+
lines.push("");
|
|
2540
|
+
}
|
|
2541
|
+
if (c.prescribed.length > 0) {
|
|
2542
|
+
lines.push(" Excluded as prescriptive (the artefact is the remedy, not the offence):");
|
|
2543
|
+
for (const o of c.prescribed) {
|
|
2544
|
+
lines.push(` [${o.skill}] ${o.artefact}`);
|
|
2545
|
+
}
|
|
2546
|
+
lines.push("");
|
|
2547
|
+
}
|
|
2548
|
+
if (c.required.length > 0) {
|
|
2549
|
+
lines.push(" Excluded as REQUIRED \u2014 a MUST/ALWAYS line, so its violation is the");
|
|
2550
|
+
lines.push(" artefact being ABSENT, and a transcript cannot show that something never");
|
|
2551
|
+
lines.push(" happened without also knowing it was due:");
|
|
2552
|
+
for (const o of c.required) {
|
|
2553
|
+
lines.push(` [${o.skill}] ${o.verb} ${o.artefact}`);
|
|
2554
|
+
}
|
|
2555
|
+
lines.push("");
|
|
2556
|
+
}
|
|
2557
|
+
lines.push(
|
|
2558
|
+
` Scanned ${r.sessions} session(s); ${r.sessionsWithASkill} had a skill in context.`
|
|
2559
|
+
);
|
|
2560
|
+
lines.push(` Flags: ${r.flags.length}`);
|
|
2561
|
+
for (const f of r.flags) {
|
|
2562
|
+
lines.push(` ${f.session} [${f.skill}] ${f.artefact} via ${f.tool} (entry ${f.at}, loaded ${f.loadedAt})`);
|
|
2563
|
+
lines.push(` \u2026${f.evidence}\u2026`);
|
|
2564
|
+
}
|
|
2565
|
+
lines.push("");
|
|
2566
|
+
lines.push(" PRECISION: every flag above is hand-read before any number from this script");
|
|
2567
|
+
lines.push(" is cited (Phase 3.3). A detector that cannot state its false-positive rate");
|
|
2568
|
+
lines.push(" ships as detection-only, and this one says so.");
|
|
2569
|
+
return lines.join("\n");
|
|
2570
|
+
}
|
|
2571
|
+
function main9(argv) {
|
|
2572
|
+
const args = argv ?? process3.argv.slice(2);
|
|
2573
|
+
let limit = 30;
|
|
2574
|
+
let json = false;
|
|
2575
|
+
const stores = [];
|
|
2576
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2577
|
+
const a = args[i];
|
|
2578
|
+
const value = (at) => {
|
|
2579
|
+
const v = args[at + 1];
|
|
2580
|
+
return v === void 0 || v.startsWith("-") ? null : v;
|
|
2581
|
+
};
|
|
2582
|
+
if (a === "--limit") {
|
|
2583
|
+
const v = value(i);
|
|
2584
|
+
if (v === null) {
|
|
2585
|
+
process3.stderr.write("report_skill_obligation_violations: --limit needs a number\n");
|
|
2586
|
+
return 1;
|
|
2587
|
+
}
|
|
2588
|
+
limit = Number.parseInt(v, 10);
|
|
2589
|
+
i += 1;
|
|
2590
|
+
} else if (a === "--store") {
|
|
2591
|
+
const v = value(i);
|
|
2592
|
+
if (v === null) {
|
|
2593
|
+
process3.stderr.write("report_skill_obligation_violations: --store needs a path\n");
|
|
2594
|
+
return 1;
|
|
2595
|
+
}
|
|
2596
|
+
stores.push(v);
|
|
2597
|
+
i += 1;
|
|
2598
|
+
} else if (a === "--json") {
|
|
2599
|
+
json = true;
|
|
2600
|
+
} else if (a === "--help" || a === "-h") {
|
|
2601
|
+
process3.stdout.write(
|
|
2602
|
+
"usage: report_skill_obligation_violations [--limit N] [--store PATH]... [--json]\n"
|
|
2603
|
+
);
|
|
2604
|
+
return 0;
|
|
2605
|
+
} else if (a !== void 0 && a.startsWith("--") && a !== "--quiet") {
|
|
2606
|
+
process3.stderr.write(`report_skill_obligation_violations: unknown flag ${a}
|
|
2607
|
+
`);
|
|
2608
|
+
return 1;
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
2612
|
+
process3.stderr.write("report_skill_obligation_violations: --limit must be a positive integer\n");
|
|
2613
|
+
return 1;
|
|
2614
|
+
}
|
|
2615
|
+
if (stores.length === 0) stores.push(defaultStore3(REPO_ROOT4));
|
|
2616
|
+
for (const store of stores) {
|
|
2617
|
+
const r = scanStore2(REPO_ROOT4, store, limit);
|
|
2618
|
+
process3.stdout.write(json ? `${JSON.stringify(r, null, 2)}
|
|
2619
|
+
` : `${render4(r)}
|
|
2620
|
+
`);
|
|
79
2621
|
}
|
|
2622
|
+
return 0;
|
|
2623
|
+
}
|
|
2624
|
+
function _isCliEntry8() {
|
|
2625
|
+
if (process3.argv[1] === void 0) return false;
|
|
2626
|
+
if (import.meta.url === pathToFileURL9(path10.resolve(process3.argv[1])).href) return true;
|
|
2627
|
+
try {
|
|
2628
|
+
return fs10.realpathSync(_HERE3) === fs10.realpathSync(path10.resolve(process3.argv[1]));
|
|
2629
|
+
} catch {
|
|
2630
|
+
return false;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
if (_isCliEntry8()) {
|
|
2634
|
+
process3.exit(main9());
|
|
80
2635
|
}
|
|
81
2636
|
|
|
82
|
-
// src/scripts/
|
|
83
|
-
var
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
2637
|
+
// src/scripts/report_conformance_funnel.ts
|
|
2638
|
+
var _HERE4 = fileURLToPath10(import.meta.url);
|
|
2639
|
+
var REPO_ROOT5 = path11.resolve(path11.dirname(_HERE4), "..", "..");
|
|
2640
|
+
function buildFunnel(opts = {}) {
|
|
2641
|
+
const repoRoot = opts.repoRoot ?? REPO_ROOT5;
|
|
2642
|
+
const projectRulesDir = opts.projectRulesDir ?? path11.join(repoRoot, PROJECT_RULES);
|
|
2643
|
+
const globalRulesDir = opts.globalRulesDir ?? path11.join(os5.homedir(), GLOBAL_RULES);
|
|
2644
|
+
const sourceDir = opts.sourceDir ?? path11.join(repoRoot, PROJECTION_SOURCE);
|
|
2645
|
+
const store = opts.store ?? defaultStore(repoRoot);
|
|
2646
|
+
const limit = opts.limit ?? 30;
|
|
2647
|
+
return {
|
|
2648
|
+
delivered: measureDelivered(projectRulesDir, globalRulesDir),
|
|
2649
|
+
divergence: compareCarriers(projectRulesDir, globalRulesDir, sourceDir),
|
|
2650
|
+
skills: censusSkills(path11.join(repoRoot, SKILLS_ROOT)),
|
|
2651
|
+
usage: measureUsage(store, limit),
|
|
2652
|
+
storePresent: fs11.existsSync(store),
|
|
2653
|
+
sk2: scanStore2(repoRoot, store, limit)
|
|
2654
|
+
};
|
|
100
2655
|
}
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
2656
|
+
function _pct3(n, d) {
|
|
2657
|
+
return d === 0 ? "n/a" : `${(100 * n / d).toFixed(1)}%`;
|
|
2658
|
+
}
|
|
2659
|
+
function _carrierLine(label, c) {
|
|
2660
|
+
return c.present ? ` ${label} ${String(c.files).padStart(4)} rules ${String(c.tokens).padStart(7)} tok` : ` ${label} ABSENT \u2014 not a measured zero, this carrier does not exist here`;
|
|
2661
|
+
}
|
|
2662
|
+
function render5(r) {
|
|
2663
|
+
const lines = [];
|
|
2664
|
+
lines.push("conformance funnel \u2014 delivery \u2192 activation \u2192 compliance (report, not a gate)");
|
|
2665
|
+
lines.push("");
|
|
2666
|
+
lines.push("DELIVERY \u2014 rule text the carriers put in front of the model");
|
|
2667
|
+
lines.push(_carrierLine("project", r.delivered.project));
|
|
2668
|
+
lines.push(_carrierLine("global ", r.delivered.global));
|
|
2669
|
+
lines.push(` union ${String(r.delivered.union_tokens).padStart(7)} tok`);
|
|
2670
|
+
lines.push(" Carriers as they stand NOW \u2014 a session's own payload is NOT recoverable:");
|
|
2671
|
+
lines.push(" the transcript records no system or tools field (conformance_scan).");
|
|
2672
|
+
const d = r.divergence;
|
|
2673
|
+
if (!d.globalPresent) {
|
|
2674
|
+
lines.push(" Divergence: the global carrier is absent \u2014 this machine loads the project");
|
|
2675
|
+
lines.push(" projection only, so there is no second copy to diverge from. A real");
|
|
2676
|
+
lines.push(" answer, not a skipped check.");
|
|
2677
|
+
} else if (!d.projectPresent) {
|
|
2678
|
+
lines.push(" Divergence: the project rule tree does not exist in this checkout \u2014 it is");
|
|
2679
|
+
lines.push(" generated and gitignored. Run `task generate-tools`, then re-run. Not");
|
|
2680
|
+
lines.push(" substituted with dist/, which would answer a different question.");
|
|
2681
|
+
} else {
|
|
2682
|
+
lines.push(
|
|
2683
|
+
` Divergence: ${d.shared} shared \xB7 ${d.bodyDiff.length} body-diff \xB7 ${d.provenanceOnly.length} provenance-only \xB7 ${d.projectOnly.length} project-only \xB7 ${d.globalOnly.length} global-only` + (d.manualOnlyGlobal.length > 0 ? ` (${d.manualOnlyGlobal.length} by design, ADR-004 manual)` : "")
|
|
2684
|
+
);
|
|
2685
|
+
if (d.bodyDiff.length > 0) {
|
|
2686
|
+
lines.push(` BODY DIVERGENCE \u2014 both copies reach the model: ${d.bodyDiff.join(", ")}`);
|
|
2687
|
+
lines.push(" Precedence: the project projection is generated from src/ at this commit");
|
|
2688
|
+
lines.push(" and wins; the global install is a release snapshot.");
|
|
2689
|
+
}
|
|
2690
|
+
if (d.unreadable.length > 0) {
|
|
2691
|
+
lines.push(` Unreadable on one side (${d.unreadable.length}) \u2014 a broken install, not a disagreement.`);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
lines.push("");
|
|
2695
|
+
lines.push("ACTIVATION \u2014 are the delivered skills invoked?");
|
|
2696
|
+
lines.push(` skills shipped ${r.skills.total}`);
|
|
2697
|
+
lines.push(
|
|
2698
|
+
` with a machine-matchable trigger key ${r.skills.withTriggerKey.length} (${_pct3(r.skills.withTriggerKey.length, r.skills.total)})`
|
|
2699
|
+
);
|
|
2700
|
+
if (!r.storePresent) {
|
|
2701
|
+
lines.push(` NO DATA: no transcript store at ${r.usage.store} \u2014 usage is not`);
|
|
2702
|
+
lines.push(" measurable here. An absent store is an answer, not a zero.");
|
|
2703
|
+
} else {
|
|
2704
|
+
const distinct = Object.keys(r.usage.bySkill).length;
|
|
2705
|
+
lines.push(
|
|
2706
|
+
` sessions=${r.usage.sessions} assistant turns=${r.usage.assistantTurns} Skill invocations=${r.usage.invocations} distinct skills=${distinct} of ${r.skills.total} (${_pct3(distinct, r.skills.total)})`
|
|
2707
|
+
);
|
|
2708
|
+
}
|
|
2709
|
+
if (r.skills.withTriggerKey.length === 0) {
|
|
2710
|
+
lines.push(" Honest scope: activation is NOT measurable as a rate \u2014 no skill declares");
|
|
2711
|
+
lines.push(" a machine-matchable trigger, and matching description prose is the class");
|
|
2712
|
+
lines.push(" this suite excludes (report_skill_activation).");
|
|
2713
|
+
}
|
|
2714
|
+
lines.push(" NOT MEASURED: whether each skill reached the model WITH its description \u2014");
|
|
2715
|
+
lines.push(" the host's injected catalogue is not persisted in the transcript, so the");
|
|
2716
|
+
lines.push(" bare-name (truncation) hypothesis stays a single-session observation.");
|
|
2717
|
+
lines.push("");
|
|
2718
|
+
const c = r.sk2.census;
|
|
2719
|
+
lines.push("COMPLIANCE \u2014 SK-2 loaded-but-violated (coverage is the headline)");
|
|
2720
|
+
lines.push(` skills with a deterministic obligation ${c.skills.length}`);
|
|
2721
|
+
lines.push(
|
|
2722
|
+
` testable without judgement ${c.forbidden.length} artefact(s) over ${c.totalLines} obligation line(s)`
|
|
2723
|
+
);
|
|
2724
|
+
lines.push(" The rest are deterministic in WORDING and need a reading to test \u2014 the");
|
|
2725
|
+
lines.push(" FC-8 class this suite excludes, reported as uncovered, not approximated");
|
|
2726
|
+
lines.push(" (report_skill_obligation_violations).");
|
|
2727
|
+
if (!r.storePresent) {
|
|
2728
|
+
lines.push(" NO DATA: no transcript store \u2014 loaded-but-violated cannot be observed");
|
|
2729
|
+
lines.push(" here. The coverage census above is static and still holds.");
|
|
2730
|
+
} else {
|
|
2731
|
+
lines.push(
|
|
2732
|
+
` sessions scanned=${r.sk2.sessions} with a skill in context=${r.sk2.sessionsWithASkill} flags=${r.sk2.flags.length}`
|
|
2733
|
+
);
|
|
2734
|
+
for (const f of r.sk2.flags) {
|
|
2735
|
+
lines.push(` ${f.session} [${f.skill}] ${f.artefact} via ${f.tool}`);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
lines.push(" PRECISION: every flag is hand-read before any number here is cited \u2014 the");
|
|
2739
|
+
lines.push(" detector cannot state its false-positive rate, so it ships detection-only.");
|
|
2740
|
+
lines.push("");
|
|
2741
|
+
lines.push("This is a REPORT. It gates on nothing and must never be wired into a CI");
|
|
2742
|
+
lines.push("workflow without a measured false-positive rate first (locked policy).");
|
|
2743
|
+
return lines.join("\n");
|
|
2744
|
+
}
|
|
2745
|
+
function main10(argv) {
|
|
2746
|
+
const args = argv ?? process4.argv.slice(2);
|
|
2747
|
+
const opts = {};
|
|
2748
|
+
const value = (i) => {
|
|
2749
|
+
const v = args[i + 1];
|
|
2750
|
+
return v === void 0 || v.startsWith("-") ? null : v;
|
|
2751
|
+
};
|
|
2752
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2753
|
+
const a = args[i];
|
|
2754
|
+
if (a === "--store" || a === "--project" || a === "--global" || a === "--source") {
|
|
2755
|
+
const v = value(i);
|
|
2756
|
+
if (v === null) {
|
|
2757
|
+
process4.stderr.write(`report_conformance_funnel: ${a} needs a value
|
|
2758
|
+
`);
|
|
2759
|
+
return 1;
|
|
2760
|
+
}
|
|
2761
|
+
const resolved = path11.resolve(v);
|
|
2762
|
+
if (a === "--store") opts.store = resolved;
|
|
2763
|
+
else if (a === "--project") opts.projectRulesDir = resolved;
|
|
2764
|
+
else if (a === "--global") opts.globalRulesDir = resolved;
|
|
2765
|
+
else opts.sourceDir = resolved;
|
|
2766
|
+
i += 1;
|
|
2767
|
+
} else if (a === "--limit") {
|
|
2768
|
+
const v = value(i);
|
|
2769
|
+
const n = v === null ? Number.NaN : Number.parseInt(v, 10);
|
|
2770
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
2771
|
+
process4.stderr.write("report_conformance_funnel: --limit must be a positive integer\n");
|
|
2772
|
+
return 1;
|
|
2773
|
+
}
|
|
2774
|
+
opts.limit = n;
|
|
2775
|
+
i += 1;
|
|
2776
|
+
} else if (a === "--help" || a === "-h") {
|
|
2777
|
+
process4.stdout.write(
|
|
2778
|
+
"usage: report_conformance_funnel [--store PATH] [--limit N]\n [--project DIR] [--global DIR] [--source DIR]\n"
|
|
2779
|
+
);
|
|
2780
|
+
return 0;
|
|
2781
|
+
} else if (a !== void 0 && a.startsWith("--") && a !== "--quiet") {
|
|
2782
|
+
process4.stderr.write(`report_conformance_funnel: unknown flag ${a}
|
|
2783
|
+
`);
|
|
2784
|
+
return 1;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
process4.stdout.write(`${render5(buildFunnel(opts))}
|
|
2788
|
+
`);
|
|
2789
|
+
return 0;
|
|
2790
|
+
}
|
|
2791
|
+
function _isCliEntry9() {
|
|
2792
|
+
if (process4.argv[1] === void 0) return false;
|
|
2793
|
+
if (import.meta.url === pathToFileURL10(path11.resolve(process4.argv[1])).href) return true;
|
|
2794
|
+
try {
|
|
2795
|
+
return fs11.realpathSync(_HERE4) === fs11.realpathSync(path11.resolve(process4.argv[1]));
|
|
2796
|
+
} catch {
|
|
2797
|
+
return false;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
if (_isCliEntry9()) {
|
|
2801
|
+
process4.exit(main10());
|
|
105
2802
|
}
|
|
106
2803
|
|
|
107
2804
|
// src/scripts/_cli/cmd_conformance.ts
|
|
108
|
-
var
|
|
2805
|
+
var _HERE5 = fileURLToPath11(import.meta.url);
|
|
109
2806
|
var PACKAGE_ROOT = resolvePackageRoot(import.meta.url);
|
|
110
2807
|
var CONFORMANCE_CHECK_IDS = [
|
|
111
2808
|
"txlog-clean",
|
|
@@ -116,29 +2813,29 @@ var CONFORMANCE_CHECK_IDS = [
|
|
|
116
2813
|
];
|
|
117
2814
|
var THIN_STUB_MARKER = "Routed rule \u2014 load the body on trigger-match";
|
|
118
2815
|
function print(s = "") {
|
|
119
|
-
|
|
2816
|
+
process5.stdout.write(`${s}
|
|
120
2817
|
`);
|
|
121
2818
|
}
|
|
122
2819
|
function eprint(s = "") {
|
|
123
|
-
|
|
2820
|
+
process5.stderr.write(`${s}
|
|
124
2821
|
`);
|
|
125
2822
|
}
|
|
126
2823
|
function conformanceLogPath() {
|
|
127
|
-
const override =
|
|
2824
|
+
const override = process5.env["AGENT_CONFIG_CONFORMANCE_LOG"];
|
|
128
2825
|
if (override && override.trim().length > 0) {
|
|
129
2826
|
return override;
|
|
130
2827
|
}
|
|
131
|
-
return
|
|
2828
|
+
return path12.join(os6.homedir(), ".event4u", "agent-config", "conformance-log.jsonl");
|
|
132
2829
|
}
|
|
133
2830
|
function installLogPath() {
|
|
134
|
-
const override =
|
|
2831
|
+
const override = process5.env["AGENT_CONFIG_INSTALL_LOG"];
|
|
135
2832
|
if (override && override.trim().length > 0) {
|
|
136
2833
|
return override;
|
|
137
2834
|
}
|
|
138
|
-
return
|
|
2835
|
+
return path12.join(os6.homedir(), ".event4u", "agent-config", "install-log.jsonl");
|
|
139
2836
|
}
|
|
140
2837
|
function _check_txlog_clean(logPath = installLogPath()) {
|
|
141
|
-
if (!
|
|
2838
|
+
if (!fs12.existsSync(logPath)) {
|
|
142
2839
|
return {
|
|
143
2840
|
id: "txlog-clean",
|
|
144
2841
|
status: "ok",
|
|
@@ -172,11 +2869,11 @@ function _check_txlog_clean(logPath = installLogPath()) {
|
|
|
172
2869
|
};
|
|
173
2870
|
}
|
|
174
2871
|
function routeTargetPaths(target) {
|
|
175
|
-
return routeTargetPathsPosix(target).map((rel) =>
|
|
2872
|
+
return routeTargetPathsPosix(target).map((rel) => path12.join(...rel.split("/")));
|
|
176
2873
|
}
|
|
177
2874
|
function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
178
|
-
const routerPath =
|
|
179
|
-
if (!
|
|
2875
|
+
const routerPath = path12.join(packageRoot, "dist", "router.json");
|
|
2876
|
+
if (!fs12.existsSync(routerPath)) {
|
|
180
2877
|
return {
|
|
181
2878
|
id: "router-pointers",
|
|
182
2879
|
status: "fail",
|
|
@@ -186,7 +2883,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
186
2883
|
}
|
|
187
2884
|
let router;
|
|
188
2885
|
try {
|
|
189
|
-
router = JSON.parse(
|
|
2886
|
+
router = JSON.parse(fs12.readFileSync(routerPath, "utf8"));
|
|
190
2887
|
} catch (exc) {
|
|
191
2888
|
return {
|
|
192
2889
|
id: "router-pointers",
|
|
@@ -195,13 +2892,13 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
195
2892
|
remedy: "reinstall the package \u2014 dist/router.json is corrupt"
|
|
196
2893
|
};
|
|
197
2894
|
}
|
|
198
|
-
const rulesDir =
|
|
2895
|
+
const rulesDir = path12.join(packageRoot, "dist", "agent-src", "rules");
|
|
199
2896
|
const broken = [];
|
|
200
2897
|
let ids = 0;
|
|
201
2898
|
let targets = 0;
|
|
202
2899
|
for (const kernelId of router["kernel"] ?? []) {
|
|
203
2900
|
ids += 1;
|
|
204
|
-
if (!
|
|
2901
|
+
if (!fs12.existsSync(path12.join(rulesDir, `${kernelId}.md`))) {
|
|
205
2902
|
broken.push(`kernel:${kernelId} \u2192 rules/${kernelId}.md missing`);
|
|
206
2903
|
}
|
|
207
2904
|
}
|
|
@@ -209,7 +2906,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
209
2906
|
for (const entry of router[tier] ?? []) {
|
|
210
2907
|
const ruleId = String(entry["id"] ?? "");
|
|
211
2908
|
ids += 1;
|
|
212
|
-
if (!
|
|
2909
|
+
if (!fs12.existsSync(path12.join(rulesDir, `${ruleId}.md`))) {
|
|
213
2910
|
broken.push(`${tier}:${ruleId} \u2192 rules/${ruleId}.md missing`);
|
|
214
2911
|
}
|
|
215
2912
|
for (const target of entry["routes_to"] ?? []) {
|
|
@@ -219,7 +2916,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
219
2916
|
broken.push(`${ruleId} \u2192 unparseable routes_to ${JSON.stringify(target)}`);
|
|
220
2917
|
continue;
|
|
221
2918
|
}
|
|
222
|
-
if (!candidates.some((rel) =>
|
|
2919
|
+
if (!candidates.some((rel) => fs12.existsSync(path12.join(packageRoot, rel)))) {
|
|
223
2920
|
broken.push(`${ruleId} \u2192 ${target} (${candidates[0]} missing)`);
|
|
224
2921
|
}
|
|
225
2922
|
}
|
|
@@ -251,9 +2948,9 @@ function detectSmokePlatform(projectRoot) {
|
|
|
251
2948
|
return "claude";
|
|
252
2949
|
}
|
|
253
2950
|
function dispatcherRunner(packageRoot) {
|
|
254
|
-
const localTsx =
|
|
255
|
-
const script =
|
|
256
|
-
if (
|
|
2951
|
+
const localTsx = path12.join(packageRoot, "node_modules", ".bin", "tsx");
|
|
2952
|
+
const script = path12.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
|
|
2953
|
+
if (fs12.existsSync(localTsx)) {
|
|
257
2954
|
return [localTsx, script];
|
|
258
2955
|
}
|
|
259
2956
|
return ["npx", "tsx", script];
|
|
@@ -303,12 +3000,12 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
|
|
|
303
3000
|
};
|
|
304
3001
|
}
|
|
305
3002
|
function readSettingsMode(projectRoot) {
|
|
306
|
-
const settingsPath =
|
|
307
|
-
if (!
|
|
3003
|
+
const settingsPath = path12.join(projectRoot, ".agent-settings.yml");
|
|
3004
|
+
if (!fs12.existsSync(settingsPath)) {
|
|
308
3005
|
return "eager-all";
|
|
309
3006
|
}
|
|
310
3007
|
try {
|
|
311
|
-
const parsed = YAML.parse(
|
|
3008
|
+
const parsed = YAML.parse(fs12.readFileSync(settingsPath, "utf8"));
|
|
312
3009
|
const lean = parsed?.["lean_projection"] ?? null;
|
|
313
3010
|
const mode = lean?.["mode"];
|
|
314
3011
|
return typeof mode === "string" && mode.length > 0 ? mode : "eager-all";
|
|
@@ -319,7 +3016,7 @@ function readSettingsMode(projectRoot) {
|
|
|
319
3016
|
function kernelIdsFromRouter(packageRoot) {
|
|
320
3017
|
try {
|
|
321
3018
|
const router = JSON.parse(
|
|
322
|
-
|
|
3019
|
+
fs12.readFileSync(path12.join(packageRoot, "dist", "router.json"), "utf8")
|
|
323
3020
|
);
|
|
324
3021
|
return new Set((router["kernel"] ?? []).map(String));
|
|
325
3022
|
} catch {
|
|
@@ -336,8 +3033,8 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
336
3033
|
remedy: "set lean_projection.mode to 'eager-all' or 'thin' in .agent-settings.yml"
|
|
337
3034
|
};
|
|
338
3035
|
}
|
|
339
|
-
const rulesDir = projectedRulesDir ??
|
|
340
|
-
if (!
|
|
3036
|
+
const rulesDir = projectedRulesDir ?? path12.join(projectRoot, ".augment", "rules");
|
|
3037
|
+
if (!fs12.existsSync(rulesDir)) {
|
|
341
3038
|
return {
|
|
342
3039
|
id: "lean-projection",
|
|
343
3040
|
status: "skipped",
|
|
@@ -346,7 +3043,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
346
3043
|
};
|
|
347
3044
|
}
|
|
348
3045
|
const kernel = kernelIdsFromRouter(packageRoot);
|
|
349
|
-
const files =
|
|
3046
|
+
const files = fs12.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
|
|
350
3047
|
const nonKernel = files.filter((f) => !kernel.has(f.replace(/\.md$/, "")));
|
|
351
3048
|
if (nonKernel.length === 0) {
|
|
352
3049
|
return {
|
|
@@ -360,7 +3057,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
360
3057
|
for (const f of nonKernel) {
|
|
361
3058
|
let text = "";
|
|
362
3059
|
try {
|
|
363
|
-
text =
|
|
3060
|
+
text = fs12.readFileSync(path12.join(rulesDir, f), "utf8");
|
|
364
3061
|
} catch {
|
|
365
3062
|
continue;
|
|
366
3063
|
}
|
|
@@ -388,28 +3085,21 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
388
3085
|
remedy: ""
|
|
389
3086
|
};
|
|
390
3087
|
}
|
|
391
|
-
var HOST_MANIFEST_KEYS = /* @__PURE__ */ new Set([
|
|
392
|
-
"schema_version",
|
|
393
|
-
"subagent_spawn",
|
|
394
|
-
"parallel_spawn",
|
|
395
|
-
"status_polling",
|
|
396
|
-
"separate_quota_pool"
|
|
397
|
-
]);
|
|
398
3088
|
function _check_host_manifest(projectRoot) {
|
|
399
3089
|
const detected = Object.entries(detectToolPresence(projectRoot)).filter(([, v]) => v === true).map(([k]) => k);
|
|
400
3090
|
const detectedNote = detected.length > 0 ? `detected surfaces: ${detected.join(", ")}` : "no surfaces detected";
|
|
401
|
-
const settingsPath =
|
|
402
|
-
if (!
|
|
3091
|
+
const settingsPath = path12.join(projectRoot, ".agent-settings.yml");
|
|
3092
|
+
if (!fs12.existsSync(settingsPath)) {
|
|
403
3093
|
return {
|
|
404
3094
|
id: "host-manifest",
|
|
405
3095
|
status: "ok",
|
|
406
|
-
message: `no .agent-settings.yml \u2014
|
|
3096
|
+
message: `no .agent-settings.yml \u2014 host capability is always probe/registry-resolved (${detectedNote})`,
|
|
407
3097
|
remedy: ""
|
|
408
3098
|
};
|
|
409
3099
|
}
|
|
410
3100
|
let parsed = null;
|
|
411
3101
|
try {
|
|
412
|
-
parsed = YAML.parse(
|
|
3102
|
+
parsed = YAML.parse(fs12.readFileSync(settingsPath, "utf8"));
|
|
413
3103
|
} catch (exc) {
|
|
414
3104
|
return {
|
|
415
3105
|
id: "host-manifest",
|
|
@@ -424,49 +3114,19 @@ function _check_host_manifest(projectRoot) {
|
|
|
424
3114
|
return {
|
|
425
3115
|
id: "host-manifest",
|
|
426
3116
|
status: "ok",
|
|
427
|
-
message: `no host_capabilities override \u2014
|
|
3117
|
+
message: `no host_capabilities override \u2014 host capability is always probe/registry-resolved (${detectedNote})`,
|
|
428
3118
|
remedy: ""
|
|
429
3119
|
};
|
|
430
3120
|
}
|
|
431
|
-
if (typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
432
|
-
return {
|
|
433
|
-
id: "host-manifest",
|
|
434
|
-
status: "fail",
|
|
435
|
-
message: "subagents.host_capabilities is not a mapping",
|
|
436
|
-
remedy: "use the four boolean fields from the host-capability manifest contract"
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
const problems = [];
|
|
440
|
-
for (const [key, value] of Object.entries(manifest)) {
|
|
441
|
-
if (!HOST_MANIFEST_KEYS.has(key)) {
|
|
442
|
-
problems.push(`unknown key '${key}'`);
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
if (key === "schema_version") {
|
|
446
|
-
if (value !== 1) problems.push(`schema_version must be 1 (got ${JSON.stringify(value)})`);
|
|
447
|
-
continue;
|
|
448
|
-
}
|
|
449
|
-
if (typeof value !== "boolean") {
|
|
450
|
-
problems.push(`'${key}' must be boolean (got ${JSON.stringify(value)})`);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
if (problems.length > 0) {
|
|
454
|
-
return {
|
|
455
|
-
id: "host-manifest",
|
|
456
|
-
status: "fail",
|
|
457
|
-
message: `host_capabilities malformed: ${problems.slice(0, 4).join("; ")}`,
|
|
458
|
-
remedy: "only schema_version:1 + four boolean capability fields are valid"
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
3121
|
return {
|
|
462
3122
|
id: "host-manifest",
|
|
463
3123
|
status: "ok",
|
|
464
|
-
message: `host_capabilities
|
|
3124
|
+
message: `subagents.host_capabilities is deprecated and ignored \u2014 host capability is always probe/registry-resolved (${detectedNote})`,
|
|
465
3125
|
remedy: ""
|
|
466
3126
|
};
|
|
467
3127
|
}
|
|
468
3128
|
function runConformanceChecks(opts = {}) {
|
|
469
|
-
const projectRoot = opts.projectRoot ??
|
|
3129
|
+
const projectRoot = opts.projectRoot ?? process5.cwd();
|
|
470
3130
|
const packageRoot = opts.packageRoot ?? PACKAGE_ROOT;
|
|
471
3131
|
const checks = [];
|
|
472
3132
|
checks.push(_check_txlog_clean());
|
|
@@ -500,18 +3160,20 @@ function appendConformanceReport(checks, projectRoot, logPath = conformanceLogPa
|
|
|
500
3160
|
} catch {
|
|
501
3161
|
}
|
|
502
3162
|
}
|
|
503
|
-
var USAGE = "usage: agent-config conformance [-h] [--project PROJECT] [--json]\n [--skip-dispatcher]\n";
|
|
3163
|
+
var USAGE = "usage: agent-config conformance [-h] [--project PROJECT] [--json]\n [--skip-dispatcher] [--funnel]\n";
|
|
504
3164
|
function _parse(argv) {
|
|
505
|
-
const opts = { project: null, json: false, skip_dispatcher: false };
|
|
3165
|
+
const opts = { project: null, json: false, skip_dispatcher: false, funnel: false };
|
|
506
3166
|
let i = 0;
|
|
507
3167
|
while (i < argv.length) {
|
|
508
3168
|
const a = argv[i];
|
|
509
3169
|
if (a === "-h" || a === "--help") {
|
|
510
|
-
|
|
3170
|
+
process5.stdout.write(USAGE);
|
|
511
3171
|
throw new ArgparseExit(0);
|
|
512
3172
|
}
|
|
513
3173
|
if (a === "--json") {
|
|
514
3174
|
opts.json = true;
|
|
3175
|
+
} else if (a === "--funnel") {
|
|
3176
|
+
opts.funnel = true;
|
|
515
3177
|
} else if (a === "--skip-dispatcher") {
|
|
516
3178
|
opts.skip_dispatcher = true;
|
|
517
3179
|
} else if (a === "--project" || a.startsWith("--project=")) {
|
|
@@ -519,16 +3181,16 @@ function _parse(argv) {
|
|
|
519
3181
|
opts.project = a.slice(a.indexOf("=") + 1);
|
|
520
3182
|
} else {
|
|
521
3183
|
if (i + 1 >= argv.length) {
|
|
522
|
-
|
|
523
|
-
|
|
3184
|
+
process5.stderr.write(USAGE);
|
|
3185
|
+
process5.stderr.write("agent-config conformance: error: --project expects a value\n");
|
|
524
3186
|
throw new ArgparseExit(2);
|
|
525
3187
|
}
|
|
526
3188
|
opts.project = argv[i + 1];
|
|
527
3189
|
i += 1;
|
|
528
3190
|
}
|
|
529
3191
|
} else {
|
|
530
|
-
|
|
531
|
-
|
|
3192
|
+
process5.stderr.write(USAGE);
|
|
3193
|
+
process5.stderr.write(`agent-config conformance: error: unrecognized argument ${a}
|
|
532
3194
|
`);
|
|
533
3195
|
throw new ArgparseExit(2);
|
|
534
3196
|
}
|
|
@@ -536,8 +3198,12 @@ function _parse(argv) {
|
|
|
536
3198
|
}
|
|
537
3199
|
return opts;
|
|
538
3200
|
}
|
|
539
|
-
function
|
|
540
|
-
const opts = _parse(argv !== null ? Array.from(argv) :
|
|
3201
|
+
function main11(argv = null) {
|
|
3202
|
+
const opts = _parse(argv !== null ? Array.from(argv) : process5.argv.slice(2));
|
|
3203
|
+
if (opts.funnel) {
|
|
3204
|
+
print(render5(buildFunnel()));
|
|
3205
|
+
return 0;
|
|
3206
|
+
}
|
|
541
3207
|
let project_root;
|
|
542
3208
|
try {
|
|
543
3209
|
[project_root] = _resolve_project_root(opts.project);
|
|
@@ -603,28 +3269,28 @@ function main(argv = null) {
|
|
|
603
3269
|
}
|
|
604
3270
|
return failed ? 1 : 0;
|
|
605
3271
|
}
|
|
606
|
-
function
|
|
607
|
-
if (
|
|
3272
|
+
function _isCliEntry10() {
|
|
3273
|
+
if (process5.argv[1] === void 0) {
|
|
608
3274
|
return false;
|
|
609
3275
|
}
|
|
610
|
-
const argvUrl =
|
|
3276
|
+
const argvUrl = pathToFileURL11(path12.resolve(process5.argv[1])).href;
|
|
611
3277
|
if (import.meta.url === argvUrl) {
|
|
612
3278
|
return true;
|
|
613
3279
|
}
|
|
614
3280
|
try {
|
|
615
|
-
const here =
|
|
616
|
-
const argv =
|
|
3281
|
+
const here = fs12.realpathSync(fileURLToPath11(import.meta.url));
|
|
3282
|
+
const argv = fs12.realpathSync(path12.resolve(process5.argv[1]));
|
|
617
3283
|
return here === argv;
|
|
618
3284
|
} catch {
|
|
619
3285
|
return false;
|
|
620
3286
|
}
|
|
621
3287
|
}
|
|
622
|
-
if (
|
|
3288
|
+
if (_isCliEntry10() || process5.argv[1] === _HERE5) {
|
|
623
3289
|
try {
|
|
624
|
-
|
|
3290
|
+
process5.exitCode = main11(process5.argv.slice(2));
|
|
625
3291
|
} catch (e) {
|
|
626
3292
|
if (e instanceof ArgparseExit) {
|
|
627
|
-
|
|
3293
|
+
process5.exitCode = e.code;
|
|
628
3294
|
} else {
|
|
629
3295
|
throw e;
|
|
630
3296
|
}
|
|
@@ -644,7 +3310,7 @@ export {
|
|
|
644
3310
|
conformanceLogPath,
|
|
645
3311
|
detectSmokePlatform,
|
|
646
3312
|
installLogPath,
|
|
647
|
-
main,
|
|
3313
|
+
main11 as main,
|
|
648
3314
|
routeTargetPaths,
|
|
649
3315
|
runConformanceChecks
|
|
650
3316
|
};
|