@event4u/agent-config 14.0.0 → 14.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +95 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/cmd_conformance.js +112 -8
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +1 -1
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +1 -1
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +1 -1
- package/dist/hooks/dispatch.js +199 -18
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +114 -9
- package/package.json +1 -1
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/hook-token-budget.json +6 -0
- package/src/scripts/_lib/gate_ledger.ts +5 -1
- package/src/scripts/_lib/skill_catalogue_series.ts +91 -0
- package/src/scripts/build_archive_index.ts +62 -3
- package/src/scripts/check_gate_coverage.ts +93 -0
- package/src/scripts/check_pr_ci_current.ts +86 -3
- package/src/scripts/dispatch_r2_reviewer.ts +169 -15
- package/src/scripts/git_authorization_hook.ts +186 -1
- package/src/scripts/hooks/block_unauthorized_git.ts +62 -6
- package/src/scripts/hooks/skill_route_hook.ts +172 -11
- package/src/scripts/install-hooks.sh +26 -0
package/dist/hooks/dispatch.js
CHANGED
|
@@ -23087,9 +23087,38 @@ import path50 from "node:path";
|
|
|
23087
23087
|
import { fileURLToPath as fileURLToPath32, pathToFileURL as pathToFileURL24 } from "node:url";
|
|
23088
23088
|
var EXIT_ALLOW8 = 0;
|
|
23089
23089
|
var STATE_FILE7 = path50.join("agents", "state", "git-authorization.json");
|
|
23090
|
+
var STATE_DIR3 = path50.join("agents", "state", "git-authorization");
|
|
23091
|
+
var PENDING_DIR = path50.join("agents", "state", "git-authorization-pending");
|
|
23092
|
+
function sessionSlug(session_id) {
|
|
23093
|
+
return session_id.replace(/[^A-Za-z0-9._-]/gu, "_").slice(0, 80);
|
|
23094
|
+
}
|
|
23095
|
+
function ledgerFileFor(session_id) {
|
|
23096
|
+
if (!session_id) {
|
|
23097
|
+
return STATE_FILE7;
|
|
23098
|
+
}
|
|
23099
|
+
return path50.join(STATE_DIR3, `${sessionSlug(session_id)}.json`);
|
|
23100
|
+
}
|
|
23101
|
+
function pendingFileFor(session_id) {
|
|
23102
|
+
if (!session_id) {
|
|
23103
|
+
return PENDING_FILE;
|
|
23104
|
+
}
|
|
23105
|
+
return path50.join(PENDING_DIR, `${sessionSlug(session_id)}.json`);
|
|
23106
|
+
}
|
|
23107
|
+
var PENDING_FILE = path50.join("agents", "state", "git-authorization-pending.json");
|
|
23108
|
+
var PENDING_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
23090
23109
|
function _isObject8(v) {
|
|
23091
23110
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
23092
23111
|
}
|
|
23112
|
+
var ALL_OPS = [
|
|
23113
|
+
"commit",
|
|
23114
|
+
"push",
|
|
23115
|
+
"branch",
|
|
23116
|
+
"pr-create",
|
|
23117
|
+
"pr-merge",
|
|
23118
|
+
"tag",
|
|
23119
|
+
"release",
|
|
23120
|
+
"publish"
|
|
23121
|
+
];
|
|
23093
23122
|
var PHRASES = [
|
|
23094
23123
|
{ op: "commit", re: /\b(commit(e|et|te|ten)?|committe|einchecken)\b/i },
|
|
23095
23124
|
{ op: "push", re: /\b(push(e|en|t)?|hochladen|hochschieben|raufschieben)\b/i },
|
|
@@ -23127,6 +23156,18 @@ function isInterrogative(prose) {
|
|
|
23127
23156
|
t
|
|
23128
23157
|
);
|
|
23129
23158
|
}
|
|
23159
|
+
function isAffirmative(prose) {
|
|
23160
|
+
const t = prose.trim().replace(/[.!]+$/u, "");
|
|
23161
|
+
if (!t || t.length > 24) {
|
|
23162
|
+
return false;
|
|
23163
|
+
}
|
|
23164
|
+
if (isInterrogative(t)) {
|
|
23165
|
+
return false;
|
|
23166
|
+
}
|
|
23167
|
+
return /^(?:option\s*)?\d{1,2}[.)]?$/iu.test(t) || /^(ja|jo|jep|yes|yep|yeah|ok|okay|okey|klar|passt|gut|los|go|go ahead|mach|mach das|mach es|leg los|do it|proceed|bitte)$/iu.test(
|
|
23168
|
+
t
|
|
23169
|
+
);
|
|
23170
|
+
}
|
|
23130
23171
|
var PASTED_COMMANDS = [
|
|
23131
23172
|
{ op: "publish", re: /\bnpm\s+publish\b/i },
|
|
23132
23173
|
{ op: "tag", re: /\bgit\s+push\s+[^\n]*--tags\b|\bgit\s+tag\s+-a\b/i },
|
|
@@ -23175,6 +23216,40 @@ function classifyAuthorization(prompt) {
|
|
|
23175
23216
|
}
|
|
23176
23217
|
return { authorized: [...authorized], evidence };
|
|
23177
23218
|
}
|
|
23219
|
+
function takePending(consumer_root, session_id, now) {
|
|
23220
|
+
const file = path50.join(consumer_root, pendingFileFor(session_id));
|
|
23221
|
+
let raw;
|
|
23222
|
+
try {
|
|
23223
|
+
raw = fs49.readFileSync(file, "utf8");
|
|
23224
|
+
} catch {
|
|
23225
|
+
return null;
|
|
23226
|
+
}
|
|
23227
|
+
try {
|
|
23228
|
+
fs49.rmSync(file, { force: true });
|
|
23229
|
+
} catch {
|
|
23230
|
+
}
|
|
23231
|
+
try {
|
|
23232
|
+
const decoded = JSON.parse(raw);
|
|
23233
|
+
if (!_isObject8(decoded)) {
|
|
23234
|
+
return null;
|
|
23235
|
+
}
|
|
23236
|
+
const op = decoded["op"];
|
|
23237
|
+
if (typeof op !== "string" || !ALL_OPS.includes(op)) {
|
|
23238
|
+
return null;
|
|
23239
|
+
}
|
|
23240
|
+
const recorded = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
|
|
23241
|
+
if (session_id && recorded && recorded !== session_id) {
|
|
23242
|
+
return null;
|
|
23243
|
+
}
|
|
23244
|
+
const at = Date.parse(String(decoded["refused_at"] ?? ""));
|
|
23245
|
+
if (!Number.isFinite(at) || now - at > PENDING_MAX_AGE_MS) {
|
|
23246
|
+
return null;
|
|
23247
|
+
}
|
|
23248
|
+
return { op, session_id: recorded, refused_at: String(decoded["refused_at"]) };
|
|
23249
|
+
} catch {
|
|
23250
|
+
return null;
|
|
23251
|
+
}
|
|
23252
|
+
}
|
|
23178
23253
|
function run9(stdin_text, options) {
|
|
23179
23254
|
let envelope = {};
|
|
23180
23255
|
if (stdin_text.trim()) {
|
|
@@ -23200,6 +23275,12 @@ function run9(stdin_text, options) {
|
|
|
23200
23275
|
return EXIT_ALLOW8;
|
|
23201
23276
|
}
|
|
23202
23277
|
const { authorized, evidence } = classifyAuthorization(prompt);
|
|
23278
|
+
const session = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
|
|
23279
|
+
const pending = takePending(options.consumer_root, session, Date.now());
|
|
23280
|
+
if (pending && !authorized.includes(pending.op) && isAffirmative(prompt)) {
|
|
23281
|
+
authorized.push(pending.op);
|
|
23282
|
+
evidence[pending.op] = `confirmation of the refused \`${pending.op}\` (refused ${pending.refused_at}): "${prompt.trim().slice(0, 40)}"`;
|
|
23283
|
+
}
|
|
23203
23284
|
const ledger = {
|
|
23204
23285
|
session_id: typeof envelope["session_id"] === "string" ? envelope["session_id"] : "",
|
|
23205
23286
|
detected_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -23208,7 +23289,7 @@ function run9(stdin_text, options) {
|
|
|
23208
23289
|
prompt_chars: prompt.length
|
|
23209
23290
|
};
|
|
23210
23291
|
try {
|
|
23211
|
-
atomic_write_json(path50.join(options.consumer_root,
|
|
23292
|
+
atomic_write_json(path50.join(options.consumer_root, ledgerFileFor(session)), ledger);
|
|
23212
23293
|
} catch {
|
|
23213
23294
|
}
|
|
23214
23295
|
return EXIT_ALLOW8;
|
|
@@ -23494,17 +23575,30 @@ function commandOp(command) {
|
|
|
23494
23575
|
}
|
|
23495
23576
|
var LEDGER_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
23496
23577
|
function _loadLedger(consumer_root, session_id, now) {
|
|
23578
|
+
const candidates = [ledgerFileFor(session_id)];
|
|
23579
|
+
if (!candidates.includes(STATE_FILE7)) {
|
|
23580
|
+
candidates.push(STATE_FILE7);
|
|
23581
|
+
}
|
|
23582
|
+
for (const rel of candidates) {
|
|
23583
|
+
const found = _readLedgerFile(path51.join(consumer_root, rel), session_id, now);
|
|
23584
|
+
if (found !== null) {
|
|
23585
|
+
return found;
|
|
23586
|
+
}
|
|
23587
|
+
}
|
|
23588
|
+
return { authorized: /* @__PURE__ */ new Set(), present: false };
|
|
23589
|
+
}
|
|
23590
|
+
function _readLedgerFile(file, session_id, now) {
|
|
23497
23591
|
try {
|
|
23498
|
-
const raw = fs50.readFileSync(
|
|
23592
|
+
const raw = fs50.readFileSync(file, "utf8");
|
|
23499
23593
|
const decoded = JSON.parse(raw);
|
|
23500
23594
|
if (_isObject9(decoded) && Array.isArray(decoded["authorized"])) {
|
|
23501
23595
|
const ledgerSession = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
|
|
23502
23596
|
if (session_id && ledgerSession && ledgerSession !== session_id) {
|
|
23503
|
-
return
|
|
23597
|
+
return null;
|
|
23504
23598
|
}
|
|
23505
23599
|
const at = Date.parse(String(decoded["detected_at"] ?? ""));
|
|
23506
23600
|
if (Number.isFinite(at) && now - at > LEDGER_MAX_AGE_MS) {
|
|
23507
|
-
return
|
|
23601
|
+
return null;
|
|
23508
23602
|
}
|
|
23509
23603
|
return {
|
|
23510
23604
|
authorized: new Set(decoded["authorized"].filter(Boolean)),
|
|
@@ -23513,7 +23607,7 @@ function _loadLedger(consumer_root, session_id, now) {
|
|
|
23513
23607
|
}
|
|
23514
23608
|
} catch {
|
|
23515
23609
|
}
|
|
23516
|
-
return
|
|
23610
|
+
return null;
|
|
23517
23611
|
}
|
|
23518
23612
|
function decide(command, ledger) {
|
|
23519
23613
|
if (command === null) {
|
|
@@ -23527,7 +23621,7 @@ function decide(command, ledger) {
|
|
|
23527
23621
|
if (BLOCK_OPS.has(op)) {
|
|
23528
23622
|
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.`;
|
|
23529
23623
|
return { exit: EXIT_BLOCK, stdout: "", stderr: `${reason}
|
|
23530
|
-
|
|
23624
|
+
`, blockedOp: op };
|
|
23531
23625
|
}
|
|
23532
23626
|
if (WARN_OPS.has(op)) {
|
|
23533
23627
|
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}`;
|
|
@@ -23557,6 +23651,16 @@ function run10(stdin_text, options) {
|
|
|
23557
23651
|
extractCommand2(envelope),
|
|
23558
23652
|
_loadLedger(options.consumer_root, session_id, Date.now())
|
|
23559
23653
|
);
|
|
23654
|
+
if (decision.blockedOp) {
|
|
23655
|
+
try {
|
|
23656
|
+
atomic_write_json(path51.join(options.consumer_root, pendingFileFor(session_id)), {
|
|
23657
|
+
op: decision.blockedOp,
|
|
23658
|
+
session_id,
|
|
23659
|
+
refused_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
23660
|
+
});
|
|
23661
|
+
} catch {
|
|
23662
|
+
}
|
|
23663
|
+
}
|
|
23560
23664
|
if (decision.stdout) {
|
|
23561
23665
|
process.stdout.write(decision.stdout);
|
|
23562
23666
|
}
|
|
@@ -27387,6 +27491,51 @@ var OBSERVATION_LOG = path66.join(
|
|
|
27387
27491
|
"metrics",
|
|
27388
27492
|
"skill-catalogue.jsonl"
|
|
27389
27493
|
);
|
|
27494
|
+
function truncationModeOf(record) {
|
|
27495
|
+
return record.truncation_mode ?? "per-entry";
|
|
27496
|
+
}
|
|
27497
|
+
function _supersedes(candidate, incumbent) {
|
|
27498
|
+
if (candidate.observed_at !== incumbent.observed_at) {
|
|
27499
|
+
return candidate.observed_at > incumbent.observed_at;
|
|
27500
|
+
}
|
|
27501
|
+
return (candidate.dropped_count ?? 0) > (incumbent.dropped_count ?? 0);
|
|
27502
|
+
}
|
|
27503
|
+
function headlineRecordPerHost(records) {
|
|
27504
|
+
const out = /* @__PURE__ */ new Map();
|
|
27505
|
+
for (const record of records) {
|
|
27506
|
+
const previous = out.get(record.host);
|
|
27507
|
+
if (previous === void 0 || _supersedes(record, previous)) {
|
|
27508
|
+
out.set(record.host, record);
|
|
27509
|
+
}
|
|
27510
|
+
}
|
|
27511
|
+
return out;
|
|
27512
|
+
}
|
|
27513
|
+
function readObservationLog(logPath) {
|
|
27514
|
+
if (!fs65.existsSync(logPath)) return [];
|
|
27515
|
+
const out = [];
|
|
27516
|
+
for (const line of fs65.readFileSync(logPath, "utf-8").split("\n")) {
|
|
27517
|
+
const trimmed = line.trim();
|
|
27518
|
+
if (trimmed.length === 0) continue;
|
|
27519
|
+
try {
|
|
27520
|
+
out.push(JSON.parse(trimmed));
|
|
27521
|
+
} catch {
|
|
27522
|
+
continue;
|
|
27523
|
+
}
|
|
27524
|
+
}
|
|
27525
|
+
return out;
|
|
27526
|
+
}
|
|
27527
|
+
|
|
27528
|
+
// src/scripts/_lib/skill_catalogue_series.ts
|
|
27529
|
+
function knownBareNames(records, host) {
|
|
27530
|
+
const dated = records.filter(
|
|
27531
|
+
(record) => record.host === host && typeof record.observed_at === "string"
|
|
27532
|
+
);
|
|
27533
|
+
const latest = headlineRecordPerHost(dated).get(host);
|
|
27534
|
+
if (latest === void 0) return null;
|
|
27535
|
+
if (truncationModeOf(latest) !== "per-entry") return null;
|
|
27536
|
+
if (!Array.isArray(latest.bare_names)) return null;
|
|
27537
|
+
return new Set(latest.bare_names.filter((name) => typeof name === "string"));
|
|
27538
|
+
}
|
|
27390
27539
|
|
|
27391
27540
|
// src/scripts/skill_tools/score_skill_relevance.ts
|
|
27392
27541
|
import * as fs66 from "node:fs";
|
|
@@ -27874,22 +28023,45 @@ var TOP_K = 3;
|
|
|
27874
28023
|
function _isObject15(v) {
|
|
27875
28024
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
27876
28025
|
}
|
|
27877
|
-
function
|
|
27878
|
-
if (
|
|
28026
|
+
function knownBareForHost(workspaceRoot2, host) {
|
|
28027
|
+
if (host === null || host === "") return null;
|
|
27879
28028
|
try {
|
|
27880
|
-
|
|
28029
|
+
return knownBareNames(readObservationLog(path68.join(workspaceRoot2, OBSERVATION_LOG)), host);
|
|
27881
28030
|
} catch {
|
|
27882
|
-
return
|
|
28031
|
+
return null;
|
|
28032
|
+
}
|
|
28033
|
+
}
|
|
28034
|
+
function filterKnownBare(rows, bare) {
|
|
28035
|
+
if (bare === null || bare.size === 0) return [...rows];
|
|
28036
|
+
return rows.filter(([name]) => !bare.has(name));
|
|
28037
|
+
}
|
|
28038
|
+
function routeDecision(prompt, skillsDir, bareProvider) {
|
|
28039
|
+
const silent = { rows: [], suppressed: 0 };
|
|
28040
|
+
if (skillsDir === null) return silent;
|
|
28041
|
+
try {
|
|
28042
|
+
if (_tokenize(prompt).size < MIN_TASK_TERMS) return silent;
|
|
28043
|
+
} catch {
|
|
28044
|
+
return silent;
|
|
27883
28045
|
}
|
|
27884
28046
|
let rows;
|
|
27885
28047
|
try {
|
|
27886
28048
|
rows = rank(prompt, skillsDir);
|
|
27887
28049
|
} catch {
|
|
27888
|
-
return
|
|
28050
|
+
return silent;
|
|
27889
28051
|
}
|
|
27890
|
-
|
|
27891
|
-
if (
|
|
27892
|
-
|
|
28052
|
+
let bare = null;
|
|
28053
|
+
if (bareProvider !== void 0) {
|
|
28054
|
+
try {
|
|
28055
|
+
bare = bareProvider();
|
|
28056
|
+
} catch {
|
|
28057
|
+
bare = null;
|
|
28058
|
+
}
|
|
28059
|
+
}
|
|
28060
|
+
const deliverable = filterKnownBare(rows, bare);
|
|
28061
|
+
const suppressed = bare === null ? 0 : rows.slice(0, TOP_K).filter(([name]) => bare.has(name)).length;
|
|
28062
|
+
const top = deliverable[0];
|
|
28063
|
+
if (top === void 0 || top[1] < MIN_TOP_SCORE) return { rows: [], suppressed };
|
|
28064
|
+
return { rows: deliverable.slice(0, TOP_K), suppressed };
|
|
27893
28065
|
}
|
|
27894
28066
|
function buildRouteLine(rows) {
|
|
27895
28067
|
const pointers = rows.map(([name, score]) => `${name} (${score})`).join(", ");
|
|
@@ -27922,12 +28094,21 @@ function main43() {
|
|
|
27922
28094
|
const prompt = _extractPrompt3(payload);
|
|
27923
28095
|
if (!prompt) return EXIT_ALLOW18;
|
|
27924
28096
|
if (isSyntheticPrompt(prompt)) return EXIT_ALLOW18;
|
|
27925
|
-
const
|
|
28097
|
+
const root = _workspaceRoot4(env);
|
|
28098
|
+
const platform = env["platform"];
|
|
28099
|
+
const host = typeof platform === "string" && platform ? platform : null;
|
|
28100
|
+
const { rows, suppressed } = routeDecision(
|
|
28101
|
+
prompt,
|
|
28102
|
+
resolveSkillsRoot(root),
|
|
28103
|
+
() => knownBareForHost(root, host)
|
|
28104
|
+
);
|
|
27926
28105
|
if (rows.length === 0) return EXIT_ALLOW18;
|
|
27927
28106
|
process.stdout.write(
|
|
27928
28107
|
`${JSON.stringify({
|
|
27929
28108
|
decision: "warn",
|
|
27930
|
-
reason: `skill-route: ${rows.length} pointer(s), top ${rows[0][1]}/100
|
|
28109
|
+
reason: `skill-route: ${rows.length} pointer(s), top ${rows[0][1]}/100` + // The suppression metric's numerator. Named only when non-zero, so the
|
|
28110
|
+
// common line keeps its shape and its registered byte budget.
|
|
28111
|
+
(suppressed > 0 ? `, ${suppressed} suppressed as host-bare` : ""),
|
|
27931
28112
|
additional_context: buildRouteLine(rows)
|
|
27932
28113
|
})}
|
|
27933
28114
|
`
|
|
@@ -28499,12 +28680,12 @@ function resolveDepth(parentRef, open) {
|
|
|
28499
28680
|
if (!parent) return { depth: 2, depth_basis: "asserted-parent" };
|
|
28500
28681
|
return { depth: parent.depth + 1, depth_basis: "observed" };
|
|
28501
28682
|
}
|
|
28502
|
-
function
|
|
28683
|
+
function ledgerFileFor2(root, ts) {
|
|
28503
28684
|
return path70.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
|
|
28504
28685
|
}
|
|
28505
28686
|
function appendLedgerLine(root, ts, line) {
|
|
28506
28687
|
if (is_replay_mode()) return;
|
|
28507
|
-
const file =
|
|
28688
|
+
const file = ledgerFileFor2(root, ts);
|
|
28508
28689
|
fs69.mkdirSync(path70.dirname(file), { recursive: true });
|
|
28509
28690
|
fs69.appendFileSync(file, `${JSON.stringify(line)}
|
|
28510
28691
|
`, "utf8");
|
package/dist/mcp/server.json
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"transport": {
|
|
10
10
|
"type": "stdio"
|
|
11
11
|
},
|
|
12
|
-
"version": "14.
|
|
12
|
+
"version": "14.2.0"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"repository": {
|
|
16
16
|
"source": "github",
|
|
17
17
|
"url": "https://github.com/event4u-app/agent-config"
|
|
18
18
|
},
|
|
19
|
-
"version": "14.
|
|
19
|
+
"version": "14.2.0"
|
|
20
20
|
}
|
package/dist/mcp/server.mjs
CHANGED
|
@@ -34092,6 +34092,21 @@ var init_language_mirror_hook = __esm({
|
|
|
34092
34092
|
import fs55 from "node:fs";
|
|
34093
34093
|
import path56 from "node:path";
|
|
34094
34094
|
import { fileURLToPath as fileURLToPath31, pathToFileURL as pathToFileURL20 } from "node:url";
|
|
34095
|
+
function sessionSlug(session_id) {
|
|
34096
|
+
return session_id.replace(/[^A-Za-z0-9._-]/gu, "_").slice(0, 80);
|
|
34097
|
+
}
|
|
34098
|
+
function ledgerFileFor(session_id) {
|
|
34099
|
+
if (!session_id) {
|
|
34100
|
+
return STATE_FILE2;
|
|
34101
|
+
}
|
|
34102
|
+
return path56.join(STATE_DIR, `${sessionSlug(session_id)}.json`);
|
|
34103
|
+
}
|
|
34104
|
+
function pendingFileFor(session_id) {
|
|
34105
|
+
if (!session_id) {
|
|
34106
|
+
return PENDING_FILE;
|
|
34107
|
+
}
|
|
34108
|
+
return path56.join(PENDING_DIR, `${sessionSlug(session_id)}.json`);
|
|
34109
|
+
}
|
|
34095
34110
|
function _isObject4(v) {
|
|
34096
34111
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
34097
34112
|
}
|
|
@@ -34110,6 +34125,18 @@ function isInterrogative(prose) {
|
|
|
34110
34125
|
t
|
|
34111
34126
|
);
|
|
34112
34127
|
}
|
|
34128
|
+
function isAffirmative(prose) {
|
|
34129
|
+
const t = prose.trim().replace(/[.!]+$/u, "");
|
|
34130
|
+
if (!t || t.length > 24) {
|
|
34131
|
+
return false;
|
|
34132
|
+
}
|
|
34133
|
+
if (isInterrogative(t)) {
|
|
34134
|
+
return false;
|
|
34135
|
+
}
|
|
34136
|
+
return /^(?:option\s*)?\d{1,2}[.)]?$/iu.test(t) || /^(ja|jo|jep|yes|yep|yeah|ok|okay|okey|klar|passt|gut|los|go|go ahead|mach|mach das|mach es|leg los|do it|proceed|bitte)$/iu.test(
|
|
34137
|
+
t
|
|
34138
|
+
);
|
|
34139
|
+
}
|
|
34113
34140
|
function splitFences(prompt) {
|
|
34114
34141
|
const fences = [];
|
|
34115
34142
|
const prose = prompt.replace(/```[^\n]*\n([\s\S]*?)```/g, (_m, body) => {
|
|
@@ -34148,6 +34175,40 @@ function classifyAuthorization(prompt) {
|
|
|
34148
34175
|
}
|
|
34149
34176
|
return { authorized: [...authorized], evidence };
|
|
34150
34177
|
}
|
|
34178
|
+
function takePending(consumer_root, session_id, now) {
|
|
34179
|
+
const file = path56.join(consumer_root, pendingFileFor(session_id));
|
|
34180
|
+
let raw;
|
|
34181
|
+
try {
|
|
34182
|
+
raw = fs55.readFileSync(file, "utf8");
|
|
34183
|
+
} catch {
|
|
34184
|
+
return null;
|
|
34185
|
+
}
|
|
34186
|
+
try {
|
|
34187
|
+
fs55.rmSync(file, { force: true });
|
|
34188
|
+
} catch {
|
|
34189
|
+
}
|
|
34190
|
+
try {
|
|
34191
|
+
const decoded = JSON.parse(raw);
|
|
34192
|
+
if (!_isObject4(decoded)) {
|
|
34193
|
+
return null;
|
|
34194
|
+
}
|
|
34195
|
+
const op = decoded["op"];
|
|
34196
|
+
if (typeof op !== "string" || !ALL_OPS.includes(op)) {
|
|
34197
|
+
return null;
|
|
34198
|
+
}
|
|
34199
|
+
const recorded = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
|
|
34200
|
+
if (session_id && recorded && recorded !== session_id) {
|
|
34201
|
+
return null;
|
|
34202
|
+
}
|
|
34203
|
+
const at = Date.parse(String(decoded["refused_at"] ?? ""));
|
|
34204
|
+
if (!Number.isFinite(at) || now - at > PENDING_MAX_AGE_MS) {
|
|
34205
|
+
return null;
|
|
34206
|
+
}
|
|
34207
|
+
return { op, session_id: recorded, refused_at: String(decoded["refused_at"]) };
|
|
34208
|
+
} catch {
|
|
34209
|
+
return null;
|
|
34210
|
+
}
|
|
34211
|
+
}
|
|
34151
34212
|
function run2(stdin_text, options) {
|
|
34152
34213
|
let envelope = {};
|
|
34153
34214
|
if (stdin_text.trim()) {
|
|
@@ -34173,6 +34234,12 @@ function run2(stdin_text, options) {
|
|
|
34173
34234
|
return EXIT_ALLOW2;
|
|
34174
34235
|
}
|
|
34175
34236
|
const { authorized, evidence } = classifyAuthorization(prompt);
|
|
34237
|
+
const session = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
|
|
34238
|
+
const pending = takePending(options.consumer_root, session, Date.now());
|
|
34239
|
+
if (pending && !authorized.includes(pending.op) && isAffirmative(prompt)) {
|
|
34240
|
+
authorized.push(pending.op);
|
|
34241
|
+
evidence[pending.op] = `confirmation of the refused \`${pending.op}\` (refused ${pending.refused_at}): "${prompt.trim().slice(0, 40)}"`;
|
|
34242
|
+
}
|
|
34176
34243
|
const ledger = {
|
|
34177
34244
|
session_id: typeof envelope["session_id"] === "string" ? envelope["session_id"] : "",
|
|
34178
34245
|
detected_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -34181,7 +34248,7 @@ function run2(stdin_text, options) {
|
|
|
34181
34248
|
prompt_chars: prompt.length
|
|
34182
34249
|
};
|
|
34183
34250
|
try {
|
|
34184
|
-
atomic_write_json(path56.join(options.consumer_root,
|
|
34251
|
+
atomic_write_json(path56.join(options.consumer_root, ledgerFileFor(session)), ledger);
|
|
34185
34252
|
} catch {
|
|
34186
34253
|
}
|
|
34187
34254
|
return EXIT_ALLOW2;
|
|
@@ -34219,7 +34286,7 @@ function _isCliEntry18() {
|
|
|
34219
34286
|
return false;
|
|
34220
34287
|
}
|
|
34221
34288
|
}
|
|
34222
|
-
var EXIT_ALLOW2, STATE_FILE2, PHRASES, OUTPUT_LINE, PASTED_COMMANDS;
|
|
34289
|
+
var EXIT_ALLOW2, STATE_FILE2, STATE_DIR, PENDING_DIR, PENDING_FILE, PENDING_MAX_AGE_MS, ALL_OPS, PHRASES, OUTPUT_LINE, PASTED_COMMANDS;
|
|
34223
34290
|
var init_git_authorization_hook = __esm({
|
|
34224
34291
|
"src/scripts/git_authorization_hook.ts"() {
|
|
34225
34292
|
"use strict";
|
|
@@ -34227,6 +34294,20 @@ var init_git_authorization_hook = __esm({
|
|
|
34227
34294
|
init_hook_stdin();
|
|
34228
34295
|
EXIT_ALLOW2 = 0;
|
|
34229
34296
|
STATE_FILE2 = path56.join("agents", "state", "git-authorization.json");
|
|
34297
|
+
STATE_DIR = path56.join("agents", "state", "git-authorization");
|
|
34298
|
+
PENDING_DIR = path56.join("agents", "state", "git-authorization-pending");
|
|
34299
|
+
PENDING_FILE = path56.join("agents", "state", "git-authorization-pending.json");
|
|
34300
|
+
PENDING_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
34301
|
+
ALL_OPS = [
|
|
34302
|
+
"commit",
|
|
34303
|
+
"push",
|
|
34304
|
+
"branch",
|
|
34305
|
+
"pr-create",
|
|
34306
|
+
"pr-merge",
|
|
34307
|
+
"tag",
|
|
34308
|
+
"release",
|
|
34309
|
+
"publish"
|
|
34310
|
+
];
|
|
34230
34311
|
PHRASES = [
|
|
34231
34312
|
{ op: "commit", re: /\b(commit(e|et|te|ten)?|committe|einchecken)\b/i },
|
|
34232
34313
|
{ op: "push", re: /\b(push(e|en|t)?|hochladen|hochschieben|raufschieben)\b/i },
|
|
@@ -34456,17 +34537,30 @@ function commandOp(command) {
|
|
|
34456
34537
|
return found;
|
|
34457
34538
|
}
|
|
34458
34539
|
function _loadLedger(consumer_root, session_id, now) {
|
|
34540
|
+
const candidates = [ledgerFileFor(session_id)];
|
|
34541
|
+
if (!candidates.includes(STATE_FILE2)) {
|
|
34542
|
+
candidates.push(STATE_FILE2);
|
|
34543
|
+
}
|
|
34544
|
+
for (const rel of candidates) {
|
|
34545
|
+
const found = _readLedgerFile(path57.join(consumer_root, rel), session_id, now);
|
|
34546
|
+
if (found !== null) {
|
|
34547
|
+
return found;
|
|
34548
|
+
}
|
|
34549
|
+
}
|
|
34550
|
+
return { authorized: /* @__PURE__ */ new Set(), present: false };
|
|
34551
|
+
}
|
|
34552
|
+
function _readLedgerFile(file, session_id, now) {
|
|
34459
34553
|
try {
|
|
34460
|
-
const raw = fs56.readFileSync(
|
|
34554
|
+
const raw = fs56.readFileSync(file, "utf8");
|
|
34461
34555
|
const decoded = JSON.parse(raw);
|
|
34462
34556
|
if (_isObject5(decoded) && Array.isArray(decoded["authorized"])) {
|
|
34463
34557
|
const ledgerSession = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
|
|
34464
34558
|
if (session_id && ledgerSession && ledgerSession !== session_id) {
|
|
34465
|
-
return
|
|
34559
|
+
return null;
|
|
34466
34560
|
}
|
|
34467
34561
|
const at = Date.parse(String(decoded["detected_at"] ?? ""));
|
|
34468
34562
|
if (Number.isFinite(at) && now - at > LEDGER_MAX_AGE_MS) {
|
|
34469
|
-
return
|
|
34563
|
+
return null;
|
|
34470
34564
|
}
|
|
34471
34565
|
return {
|
|
34472
34566
|
authorized: new Set(decoded["authorized"].filter(Boolean)),
|
|
@@ -34475,7 +34569,7 @@ function _loadLedger(consumer_root, session_id, now) {
|
|
|
34475
34569
|
}
|
|
34476
34570
|
} catch {
|
|
34477
34571
|
}
|
|
34478
|
-
return
|
|
34572
|
+
return null;
|
|
34479
34573
|
}
|
|
34480
34574
|
function decide(command, ledger) {
|
|
34481
34575
|
if (command === null) {
|
|
@@ -34489,7 +34583,7 @@ function decide(command, ledger) {
|
|
|
34489
34583
|
if (BLOCK_OPS.has(op)) {
|
|
34490
34584
|
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.`;
|
|
34491
34585
|
return { exit: EXIT_BLOCK, stdout: "", stderr: `${reason}
|
|
34492
|
-
|
|
34586
|
+
`, blockedOp: op };
|
|
34493
34587
|
}
|
|
34494
34588
|
if (WARN_OPS.has(op)) {
|
|
34495
34589
|
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}`;
|
|
@@ -34519,6 +34613,16 @@ function run3(stdin_text, options) {
|
|
|
34519
34613
|
extractCommand(envelope),
|
|
34520
34614
|
_loadLedger(options.consumer_root, session_id, Date.now())
|
|
34521
34615
|
);
|
|
34616
|
+
if (decision.blockedOp) {
|
|
34617
|
+
try {
|
|
34618
|
+
atomic_write_json(path57.join(options.consumer_root, pendingFileFor(session_id)), {
|
|
34619
|
+
op: decision.blockedOp,
|
|
34620
|
+
session_id,
|
|
34621
|
+
refused_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
34622
|
+
});
|
|
34623
|
+
} catch {
|
|
34624
|
+
}
|
|
34625
|
+
}
|
|
34522
34626
|
if (decision.stdout) {
|
|
34523
34627
|
process.stdout.write(decision.stdout);
|
|
34524
34628
|
}
|
|
@@ -34565,6 +34669,7 @@ var init_block_unauthorized_git = __esm({
|
|
|
34565
34669
|
"src/scripts/hooks/block_unauthorized_git.ts"() {
|
|
34566
34670
|
"use strict";
|
|
34567
34671
|
init_hook_stdin();
|
|
34672
|
+
init_state_io();
|
|
34568
34673
|
init_git_authorization_hook();
|
|
34569
34674
|
EXIT_ALLOW3 = 0;
|
|
34570
34675
|
EXIT_BLOCK = 1;
|
|
@@ -35610,12 +35715,12 @@ function resolveDepth(parentRef, open) {
|
|
|
35610
35715
|
if (!parent) return { depth: 2, depth_basis: "asserted-parent" };
|
|
35611
35716
|
return { depth: parent.depth + 1, depth_basis: "observed" };
|
|
35612
35717
|
}
|
|
35613
|
-
function
|
|
35718
|
+
function ledgerFileFor2(root, ts) {
|
|
35614
35719
|
return path60.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
|
|
35615
35720
|
}
|
|
35616
35721
|
function appendLedgerLine(root, ts, line) {
|
|
35617
35722
|
if (is_replay_mode()) return;
|
|
35618
|
-
const file =
|
|
35723
|
+
const file = ledgerFileFor2(root, ts);
|
|
35619
35724
|
fs59.mkdirSync(path60.dirname(file), { recursive: true });
|
|
35620
35725
|
fs59.appendFileSync(file, `${JSON.stringify(line)}
|
|
35621
35726
|
`, "utf8");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event4u/agent-config",
|
|
3
3
|
"mcpName": "io.github.event4u-app/agent-config",
|
|
4
|
-
"version": "14.
|
|
4
|
+
"version": "14.2.0",
|
|
5
5
|
"description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including \"zero runtime daemon\".",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"note": "Markdown links between source artefacts that cross a pack boundary the source pack's `requires` graph does not reach — revealed when the scan root moved off the deleted packages/ tree to the live corpus (0 -> 753 artefacts scanned). 82 distinct pack pairs across 131 source files; largest single class is engineering-base -> meta (73), skills in a consumer-installable pack citing this package's own maintenance skills. Each is a link that dangles for a consumer who installs the source pack without the target's. Judged real drift rather than an over-strict gate because it is long-tailed — top bucket 22%, no single systematic cause — unlike the lint_artefact_frontmatter case, where one required shape had 0-of-288 adoption. Fix per finding: add the edge to packs.yml requires, retarget the link, or move the artefact."
|
|
35
35
|
},
|
|
36
36
|
"check_gate_completeness": {
|
|
37
|
-
"count":
|
|
38
|
-
"landed": "2026-08-
|
|
39
|
-
"note": "Ledger adoption ratchet.
|
|
37
|
+
"count": 215,
|
|
38
|
+
"landed": "2026-08-17",
|
|
39
|
+
"note": "Ledger adoption ratchet. Gates registered under the CI task roots that carry neither per-target completeness accounting (`_lib/gate_ledger.ts`) nor a `// ledger-exempt:` marker. This is deliberately NOT a sweep: road-to-skill-ecosystem-gate-integrity Phase 1 adopts exactly three gates (lint_framework_leakage, check_references, lint_roadmap_complexity) plus the checker itself to prove the interface, and the remainder migrates as gates are touched. Shrink-only from here — the number may not grow, so a NEW gate must adopt or exempt. Lowered 216 -> 215 on 2026-08-17 by discharging the obligation three NEW arrivals had incurred, which is the standing rule being enforced rather than a drain: the registered population grew 244 -> 258 and three of the fourteen arrivals adopted nothing — `build_archive_index`, `check_gate_coverage`, `check_pr_ci_current`. The reading was 218 against this baseline of 216, and the missing arithmetic is that one PRE-existing gate (`lint_rule_skill_pack_reach`) had adopted in the meantime: 216 + 3 - 1 = 218. All three adopted a real ledger. `check_pr_ci_current` first took a reasoned exemption claiming no target population; the completion review refuted it from the file itself — the check rows ARE a population, and rows whose `state` is not a string were being dropped before any verdict ran, so a dropped row could be the failing one. The exemption was withdrawn and the ledger adopted, counting kept rows as read, dropped rows as skips, and rows a pre-row verdict never reached as out of scope. The denominator in this note was 14 stale (it said 244) and is now expressed without one, because a count pinned to a population that grows weekly rots faster than the count itself. Note what this lowering does NOT buy: headroom is zero either way, so the next un-adopted arrival reds this gate immediately — which is the intended behaviour and the reason the number is not padded. The refreshed `landed` is load-bearing rather than bookkeeping: `checkRatchet` returns `improved` BEFORE it evaluates staleness, so an entry left at 216 while the tree measures 215 would never expire at any age (pinned by tests/lib/gate_baseline.test.ts, which asserts `improved` at landed + STALE_AFTER_DAYS + 400 days) — the greenest available state and the worst, since it switches off the anti-fossilization clause exactly when someone has done real work.",
|
|
40
40
|
"falsifier": "./scripts-run src/scripts/check_gate_completeness"
|
|
41
41
|
},
|
|
42
42
|
"check_depth_budget": {
|
|
@@ -56,6 +56,12 @@
|
|
|
56
56
|
"instrument": "rule-trips.json warn counters for the skill-route concern (fires) + conformance audits for whether the pointed-at skill was opened (adoption); HONEST GAP: no deterministic counter exists for 'the agent acted on the pointer' - the numerator is audit-carried and registered as such rather than implied to be automatic",
|
|
57
57
|
"threshold": "none committed before data - baseline first, threshold at review date (2026-11-10)"
|
|
58
58
|
},
|
|
59
|
+
"skill_route_bare_suppression_rate": {
|
|
60
|
+
"definition": "road-to-catalogue-host-fit Phase 3 step 3.2: of the prompts where skill-route FIRES, the share whose warn reason carries `N suppressed as host-bare` - i.e. how often a skill the ranker wanted to POINT AT is one the current host is KNOWN to have delivered without its description. `N` counts the pointer window (TOP_K of the unfiltered ranking), never the whole ranked list: `rank` returns every non-zero-scoring skill, so counting the tail would let a bare name at rank 40 - never pointable, never a loss - bump the count on nearly every fire (R2 finding 1). Registered beside skill_route_pointer_rate because it measures the same concern's behaviour under the same owner and review date. NO THRESHOLD IS COMMITTED, and the refusal is inherited from the sibling rather than restated: the FILTER is derived from a measurement (all 16 bare entries in the 2026-08-12 claude observation are in the ranker's catalogue, `capture_skill_catalogue --pointable-bare`), the OUTCOME - whether suppressing them changes what the agent does - is not.",
|
|
61
|
+
"instrument": "the dispatcher's warn reason string for the skill-route concern (`, N suppressed as host-bare`, appended only when non-zero) + agents/evidence/metrics/skill-catalogue.jsonl for the population; TWO HONEST GAPS, both registered as gaps rather than implied to be automatic. (1) rule-trips.json counts fires per concern and does NOT retain the reason, so the numerator is log-carried, not counter-carried - the same posture the sibling metric takes for its own audit-carried numerator. (2) TOTAL suppression is INVISIBLE here by construction (R2 finding 4): when the filter empties the pointer set the concern returns before writing stdout, so no reason line exists for the one case the roadmap calls load-bearing. `RouteDecision.suppressed` carries it at the API boundary and nothing reads that in production.",
|
|
62
|
+
"threshold": "none committed before data - baseline first, threshold at the shared review date (2026-11-10)",
|
|
63
|
+
"falsifiers": "For the FILTER half only, never for the outcome. UPPER: a live rate far above the share of the pointer window the host degrades means the ranker preferentially ranks exactly what the host drops - a finding about the projection, which belongs to Phase 2. Name the populations rather than a bare ratio, because three different denominators are in play and an earlier draft of this row conflated them (R2 finding 11): the 2026-08-12 claude observation carries `entries_total` 336, of which only 35 were enumerated at all (16 bare + 19 described), against a ranker catalogue of 338 entries at probe time. The comparable share is 16 of the 35 ENUMERATED entries; 16/336 and 16/338 describe different things and neither is this metric's denominator. LOWER: a rate of 0 does NOT establish that the ranker and the host's bare set fail to intersect - gap (2) above makes complete suppression indistinguishable from no suppression in this instrument. `capture_skill_catalogue --pointable-bare` is the instrument that separates them, and it must be read before any zero here is interpreted."
|
|
64
|
+
},
|
|
59
65
|
"full_rewrite_small_diff_rate": {
|
|
60
66
|
"definition": "edit-shape fires / Write calls per session (the full-rewrite-with-small-diff rate). Kill criterion per the shipped nudge standard: a nudge whose verdicts are measurably ignored (adoption near zero over the review window, from conformance audits) gets its trigger tightened or the injected line removed.",
|
|
61
67
|
"instrument": "rule-trips.json warn counters (the dispatcher already counts every warn per concern) + conformance audits for adoption"
|
|
@@ -57,7 +57,9 @@ export type GateSkipReason =
|
|
|
57
57
|
| 'generated_artifact'
|
|
58
58
|
| 'dead_scan_root'
|
|
59
59
|
| 'declared_exemption'
|
|
60
|
-
| 'not_applicable_kind'
|
|
60
|
+
| 'not_applicable_kind'
|
|
61
|
+
| 'precondition_unmet'
|
|
62
|
+
| 'check_did_not_run';
|
|
61
63
|
|
|
62
64
|
/**
|
|
63
65
|
* One sentence per skip code, printed verbatim in the ledger's own output.
|
|
@@ -79,6 +81,8 @@ export const SKIP_REASON_MESSAGE: Record<GateSkipReason, string> = {
|
|
|
79
81
|
dead_scan_root: 'the root this target was enumerated from no longer exists',
|
|
80
82
|
declared_exemption: 'the target carries an explicit, reviewable exemption from this check',
|
|
81
83
|
not_applicable_kind: 'the target is a kind of artifact this check does not apply to',
|
|
84
|
+
precondition_unmet: 'a precondition of this run settled the verdict before the target could be inspected',
|
|
85
|
+
check_did_not_run: 'the check itself could not be executed, so the target was never read',
|
|
82
86
|
};
|
|
83
87
|
|
|
84
88
|
/** Terminal outcomes a planned target can reach. Exactly one, exactly once. */
|