@event4u/agent-config 14.0.0 → 14.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +53 -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 +112 -8
- 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/scripts/_lib/gate_ledger.ts +5 -1
- 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/git_authorization_hook.ts +186 -1
- package/src/scripts/hooks/block_unauthorized_git.ts +62 -6
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Bootstrap shim for the event4u agent-config suite. This plugin ships ONLY the dispatcher hooks plus one install-pointer skill — ALL content (skills, rules, commands) is installed via `npx -y @event4u/agent-config init`, which writes the file projection AND registers the same hooks in ~/.claude/settings.json. DEPRECATED as a content channel; kept so the marketplace listing stays discoverable and existing installs keep working hooks.",
|
|
9
|
-
"version": "14.
|
|
9
|
+
"version": "14.1.0",
|
|
10
10
|
"keywords": [
|
|
11
11
|
"agent-config",
|
|
12
12
|
"skills",
|
package/CHANGELOG.md
CHANGED
|
@@ -366,6 +366,59 @@ Entry-shape contract: [`docs/contracts/CHANGELOG-conventions.md`](docs/contracts
|
|
|
366
366
|
> that forces a new era split (`# Era: 14.1.x`, etc.) — see
|
|
367
367
|
> [`docs/contracts/CHANGELOG-conventions.md § Era splits`](docs/contracts/CHANGELOG-conventions.md).
|
|
368
368
|
|
|
369
|
+
## [14.1.0](https://github.com/event4u-app/agent-config/compare/14.0.0...14.1.0) (2026-08-18)
|
|
370
|
+
|
|
371
|
+
### Release highlights
|
|
372
|
+
|
|
373
|
+
<!-- Curated head: fill before merge, keep it under 10 lines, and leave `_none_` where it is genuinely the answer. The generated log below is unchanged. -->
|
|
374
|
+
- **Behaviour changes:** _none_
|
|
375
|
+
- **Default changes + migration:** _none_
|
|
376
|
+
- **Security and correctness:** _auto-derived, rewrite before merge:_ security-scoped commits or fixes to executable surface in 2ee0642, d8c14a2, 4a60aeb, 3952d49, c9753b1, aa48d84 +1 more.
|
|
377
|
+
- **Honest nulls:** _auto-derived, rewrite before merge:_ commits recording a null, waived or falsified result in 7ebc802.
|
|
378
|
+
- **Known limitations:** _none_
|
|
379
|
+
|
|
380
|
+
### Features
|
|
381
|
+
|
|
382
|
+
* **gates:** adopt per-target ledgers in two new arrivals ([a0f3326](https://github.com/event4u-app/agent-config/commit/a0f332632ac4769184cf993c8efd58b2063d0e6f))
|
|
383
|
+
|
|
384
|
+
### Bug Fixes
|
|
385
|
+
|
|
386
|
+
* **tests:** a raw NUL byte where a space was meant ([2ee0642](https://github.com/event4u-app/agent-config/commit/2ee0642ddcc99e2eba2a30ba2655263c5048b36a))
|
|
387
|
+
* **hooks:** keep an authorization from being clobbered by a parallel session ([d8c14a2](https://github.com/event4u-app/agent-config/commit/d8c14a2da7238e03b3b34c6ed56cfdcb58ed5525))
|
|
388
|
+
* **review:** unbreak the reference the findings row created, and replicate ([4708792](https://github.com/event4u-app/agent-config/commit/47087927fff242380c03cf95853e9d4106e1b054))
|
|
389
|
+
* **gates:** stop three ledgers reporting targets they never read ([4a60aeb](https://github.com/event4u-app/agent-config/commit/4a60aeb09f429b5fff348b09c5fd5626f7e3f38e))
|
|
390
|
+
* **bench:** give the consumer hook path its own budget, which it never had ([c9753b1](https://github.com/event4u-app/agent-config/commit/c9753b151dfd7a0aa95af08e59cff298c4220318))
|
|
391
|
+
* **gates:** finding 6 — deduplicate the manifest ids before planning ([aa48d84](https://github.com/event4u-app/agent-config/commit/aa48d845a0ec1e81368ad103336b27438c6fcc16))
|
|
392
|
+
* **gates:** apply the R2 review — all 11 findings ([8ec1dbb](https://github.com/event4u-app/agent-config/commit/8ec1dbb65b0a3840865aac0d91c8301eea9cbb1c))
|
|
393
|
+
|
|
394
|
+
### Documentation
|
|
395
|
+
|
|
396
|
+
* **roadmap:** carry the replication into step 0.5 ([1219376](https://github.com/event4u-app/agent-config/commit/1219376feca58df91c76e1f24f99d162ab5f7e95))
|
|
397
|
+
* **roadmap:** re-review the risk register the Phase 0 addition invalidated ([c6ff0ed](https://github.com/event4u-app/agent-config/commit/c6ff0ed14b39621765458d9ec39b654bff684b71))
|
|
398
|
+
* **roadmap:** record the path split in Phase 0, where the question already lived ([9fc8cf9](https://github.com/event4u-app/agent-config/commit/9fc8cf9f9f6651e6872d3026d5707191dcaffcdf))
|
|
399
|
+
* **review:** the measurement settles finding 6 — the dispatcher regressed ([7ebc802](https://github.com/event4u-app/agent-config/commit/7ebc8027aba369b34a500377dc588ac526b2ac4f))
|
|
400
|
+
* **review:** close 18 of 20 round-2 findings, name the two that stay open ([80ba9cc](https://github.com/event4u-app/agent-config/commit/80ba9cc12fbf0e336f912766719d61b421f902cd))
|
|
401
|
+
* **baselines:** describe what the ratchet lowering actually landed ([0ce7a17](https://github.com/event4u-app/agent-config/commit/0ce7a17baefad295c2fcb1849b02cca4a07f6bd9))
|
|
402
|
+
* **review:** record round 2 of the completion review, rows open ([41e8a1e](https://github.com/event4u-app/agent-config/commit/41e8a1e4ffc3b378fc2ff2bb9add9c31c3e4936f))
|
|
403
|
+
* **review:** re-bind the findings to the post-fix scope ([43847c1](https://github.com/event4u-app/agent-config/commit/43847c1e1bd07c3d5ebe2ff38060a761e8552e9b))
|
|
404
|
+
* **review:** all 11 findings terminal, with their fix refs ([957a53e](https://github.com/event4u-app/agent-config/commit/957a53e860a973b87b32bba07479b50f43b4f6f1))
|
|
405
|
+
* **review:** the R2 completion review, 11 findings, all rows open ([f7d9581](https://github.com/event4u-app/agent-config/commit/f7d9581ceac1388786fc6a8e0f9b6192c5f10502))
|
|
406
|
+
* **gates:** exempt check_pr_ci_current from the ledger, with the reason ([080e17c](https://github.com/event4u-app/agent-config/commit/080e17c41dd7904fe1bc76c7556417ffb7f525b4))
|
|
407
|
+
|
|
408
|
+
### CI
|
|
409
|
+
|
|
410
|
+
* **bench:** measure both hook paths on one runner, without gating either ([7d8be6e](https://github.com/event4u-app/agent-config/commit/7d8be6e1bee4f04d902d660b04f05b03ed1109d7))
|
|
411
|
+
|
|
412
|
+
### Chores
|
|
413
|
+
|
|
414
|
+
* **gates:** lower the ledger-adoption baseline 216 to 215 ([6868213](https://github.com/event4u-app/agent-config/commit/686821324b2ad94533a277a43cb857d21bb600c6))
|
|
415
|
+
|
|
416
|
+
### Other
|
|
417
|
+
|
|
418
|
+
* Revert "fix(bench): give the consumer hook path its own budget, which it never had" ([3952d49](https://github.com/event4u-app/agent-config/commit/3952d49bf3a3af770cb522dafc540c1458050f3d))
|
|
419
|
+
|
|
420
|
+
Tests: 14592 (+72 since 14.0.0)
|
|
421
|
+
|
|
369
422
|
## [14.0.0](https://github.com/event4u-app/agent-config/compare/13.0.0...14.0.0) (2026-08-18)
|
|
370
423
|
|
|
371
424
|
### Release highlights
|
|
@@ -39,7 +39,7 @@ schema_version: 1
|
|
|
39
39
|
# CI guard: a release bump of `package.json` must update this value
|
|
40
40
|
# in lockstep — see scripts/check_template_pin_drift.ts (road-to-
|
|
41
41
|
# portable-runtime-and-update-check P3.3).
|
|
42
|
-
agent_config_version: "14.
|
|
42
|
+
agent_config_version: "14.1.0"
|
|
43
43
|
|
|
44
44
|
# --- Project identity ---
|
|
45
45
|
project:
|
|
@@ -508,9 +508,38 @@ import path2 from "node:path";
|
|
|
508
508
|
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
509
509
|
var EXIT_ALLOW2 = 0;
|
|
510
510
|
var STATE_FILE2 = path2.join("agents", "state", "git-authorization.json");
|
|
511
|
+
var STATE_DIR = path2.join("agents", "state", "git-authorization");
|
|
512
|
+
var PENDING_DIR = path2.join("agents", "state", "git-authorization-pending");
|
|
513
|
+
function sessionSlug(session_id) {
|
|
514
|
+
return session_id.replace(/[^A-Za-z0-9._-]/gu, "_").slice(0, 80);
|
|
515
|
+
}
|
|
516
|
+
function ledgerFileFor(session_id) {
|
|
517
|
+
if (!session_id) {
|
|
518
|
+
return STATE_FILE2;
|
|
519
|
+
}
|
|
520
|
+
return path2.join(STATE_DIR, `${sessionSlug(session_id)}.json`);
|
|
521
|
+
}
|
|
522
|
+
function pendingFileFor(session_id) {
|
|
523
|
+
if (!session_id) {
|
|
524
|
+
return PENDING_FILE;
|
|
525
|
+
}
|
|
526
|
+
return path2.join(PENDING_DIR, `${sessionSlug(session_id)}.json`);
|
|
527
|
+
}
|
|
528
|
+
var PENDING_FILE = path2.join("agents", "state", "git-authorization-pending.json");
|
|
529
|
+
var PENDING_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
511
530
|
function _isObject3(v) {
|
|
512
531
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
513
532
|
}
|
|
533
|
+
var ALL_OPS = [
|
|
534
|
+
"commit",
|
|
535
|
+
"push",
|
|
536
|
+
"branch",
|
|
537
|
+
"pr-create",
|
|
538
|
+
"pr-merge",
|
|
539
|
+
"tag",
|
|
540
|
+
"release",
|
|
541
|
+
"publish"
|
|
542
|
+
];
|
|
514
543
|
var PHRASES = [
|
|
515
544
|
{ op: "commit", re: /\b(commit(e|et|te|ten)?|committe|einchecken)\b/i },
|
|
516
545
|
{ op: "push", re: /\b(push(e|en|t)?|hochladen|hochschieben|raufschieben)\b/i },
|
|
@@ -548,6 +577,18 @@ function isInterrogative(prose) {
|
|
|
548
577
|
t
|
|
549
578
|
);
|
|
550
579
|
}
|
|
580
|
+
function isAffirmative(prose) {
|
|
581
|
+
const t = prose.trim().replace(/[.!]+$/u, "");
|
|
582
|
+
if (!t || t.length > 24) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
if (isInterrogative(t)) {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
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(
|
|
589
|
+
t
|
|
590
|
+
);
|
|
591
|
+
}
|
|
551
592
|
var PASTED_COMMANDS = [
|
|
552
593
|
{ op: "publish", re: /\bnpm\s+publish\b/i },
|
|
553
594
|
{ op: "tag", re: /\bgit\s+push\s+[^\n]*--tags\b|\bgit\s+tag\s+-a\b/i },
|
|
@@ -596,6 +637,40 @@ function classifyAuthorization(prompt) {
|
|
|
596
637
|
}
|
|
597
638
|
return { authorized: [...authorized], evidence };
|
|
598
639
|
}
|
|
640
|
+
function takePending(consumer_root, session_id, now) {
|
|
641
|
+
const file = path2.join(consumer_root, pendingFileFor(session_id));
|
|
642
|
+
let raw;
|
|
643
|
+
try {
|
|
644
|
+
raw = fs2.readFileSync(file, "utf8");
|
|
645
|
+
} catch {
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
fs2.rmSync(file, { force: true });
|
|
650
|
+
} catch {
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
const decoded = JSON.parse(raw);
|
|
654
|
+
if (!_isObject3(decoded)) {
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
const op = decoded["op"];
|
|
658
|
+
if (typeof op !== "string" || !ALL_OPS.includes(op)) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
const recorded = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
|
|
662
|
+
if (session_id && recorded && recorded !== session_id) {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
const at = Date.parse(String(decoded["refused_at"] ?? ""));
|
|
666
|
+
if (!Number.isFinite(at) || now - at > PENDING_MAX_AGE_MS) {
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
return { op, session_id: recorded, refused_at: String(decoded["refused_at"]) };
|
|
670
|
+
} catch {
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
599
674
|
function run2(stdin_text, options) {
|
|
600
675
|
let envelope = {};
|
|
601
676
|
if (stdin_text.trim()) {
|
|
@@ -621,6 +696,12 @@ function run2(stdin_text, options) {
|
|
|
621
696
|
return EXIT_ALLOW2;
|
|
622
697
|
}
|
|
623
698
|
const { authorized, evidence } = classifyAuthorization(prompt);
|
|
699
|
+
const session = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
|
|
700
|
+
const pending = takePending(options.consumer_root, session, Date.now());
|
|
701
|
+
if (pending && !authorized.includes(pending.op) && isAffirmative(prompt)) {
|
|
702
|
+
authorized.push(pending.op);
|
|
703
|
+
evidence[pending.op] = `confirmation of the refused \`${pending.op}\` (refused ${pending.refused_at}): "${prompt.trim().slice(0, 40)}"`;
|
|
704
|
+
}
|
|
624
705
|
const ledger = {
|
|
625
706
|
session_id: typeof envelope["session_id"] === "string" ? envelope["session_id"] : "",
|
|
626
707
|
detected_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -629,7 +710,7 @@ function run2(stdin_text, options) {
|
|
|
629
710
|
prompt_chars: prompt.length
|
|
630
711
|
};
|
|
631
712
|
try {
|
|
632
|
-
atomic_write_json(path2.join(options.consumer_root,
|
|
713
|
+
atomic_write_json(path2.join(options.consumer_root, ledgerFileFor(session)), ledger);
|
|
633
714
|
} catch {
|
|
634
715
|
}
|
|
635
716
|
return EXIT_ALLOW2;
|
|
@@ -915,17 +996,30 @@ function commandOp(command) {
|
|
|
915
996
|
}
|
|
916
997
|
var LEDGER_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
917
998
|
function _loadLedger(consumer_root, session_id, now) {
|
|
999
|
+
const candidates = [ledgerFileFor(session_id)];
|
|
1000
|
+
if (!candidates.includes(STATE_FILE2)) {
|
|
1001
|
+
candidates.push(STATE_FILE2);
|
|
1002
|
+
}
|
|
1003
|
+
for (const rel of candidates) {
|
|
1004
|
+
const found = _readLedgerFile(path3.join(consumer_root, rel), session_id, now);
|
|
1005
|
+
if (found !== null) {
|
|
1006
|
+
return found;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return { authorized: /* @__PURE__ */ new Set(), present: false };
|
|
1010
|
+
}
|
|
1011
|
+
function _readLedgerFile(file, session_id, now) {
|
|
918
1012
|
try {
|
|
919
|
-
const raw = fs3.readFileSync(
|
|
1013
|
+
const raw = fs3.readFileSync(file, "utf8");
|
|
920
1014
|
const decoded = JSON.parse(raw);
|
|
921
1015
|
if (_isObject4(decoded) && Array.isArray(decoded["authorized"])) {
|
|
922
1016
|
const ledgerSession = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
|
|
923
1017
|
if (session_id && ledgerSession && ledgerSession !== session_id) {
|
|
924
|
-
return
|
|
1018
|
+
return null;
|
|
925
1019
|
}
|
|
926
1020
|
const at = Date.parse(String(decoded["detected_at"] ?? ""));
|
|
927
1021
|
if (Number.isFinite(at) && now - at > LEDGER_MAX_AGE_MS) {
|
|
928
|
-
return
|
|
1022
|
+
return null;
|
|
929
1023
|
}
|
|
930
1024
|
return {
|
|
931
1025
|
authorized: new Set(decoded["authorized"].filter(Boolean)),
|
|
@@ -934,7 +1028,7 @@ function _loadLedger(consumer_root, session_id, now) {
|
|
|
934
1028
|
}
|
|
935
1029
|
} catch {
|
|
936
1030
|
}
|
|
937
|
-
return
|
|
1031
|
+
return null;
|
|
938
1032
|
}
|
|
939
1033
|
function decide(command, ledger) {
|
|
940
1034
|
if (command === null) {
|
|
@@ -948,7 +1042,7 @@ function decide(command, ledger) {
|
|
|
948
1042
|
if (BLOCK_OPS.has(op)) {
|
|
949
1043
|
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.`;
|
|
950
1044
|
return { exit: EXIT_BLOCK, stdout: "", stderr: `${reason}
|
|
951
|
-
|
|
1045
|
+
`, blockedOp: op };
|
|
952
1046
|
}
|
|
953
1047
|
if (WARN_OPS.has(op)) {
|
|
954
1048
|
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}`;
|
|
@@ -978,6 +1072,16 @@ function run3(stdin_text, options) {
|
|
|
978
1072
|
extractCommand(envelope),
|
|
979
1073
|
_loadLedger(options.consumer_root, session_id, Date.now())
|
|
980
1074
|
);
|
|
1075
|
+
if (decision.blockedOp) {
|
|
1076
|
+
try {
|
|
1077
|
+
atomic_write_json(path3.join(options.consumer_root, pendingFileFor(session_id)), {
|
|
1078
|
+
op: decision.blockedOp,
|
|
1079
|
+
session_id,
|
|
1080
|
+
refused_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1081
|
+
});
|
|
1082
|
+
} catch {
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
981
1085
|
if (decision.stdout) {
|
|
982
1086
|
process.stdout.write(decision.stdout);
|
|
983
1087
|
}
|
|
@@ -1945,12 +2049,12 @@ function resolveDepth(parentRef, open) {
|
|
|
1945
2049
|
if (!parent) return { depth: 2, depth_basis: "asserted-parent" };
|
|
1946
2050
|
return { depth: parent.depth + 1, depth_basis: "observed" };
|
|
1947
2051
|
}
|
|
1948
|
-
function
|
|
2052
|
+
function ledgerFileFor2(root, ts) {
|
|
1949
2053
|
return path6.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
|
|
1950
2054
|
}
|
|
1951
2055
|
function appendLedgerLine(root, ts, line) {
|
|
1952
2056
|
if (is_replay_mode()) return;
|
|
1953
|
-
const file =
|
|
2057
|
+
const file = ledgerFileFor2(root, ts);
|
|
1954
2058
|
fs6.mkdirSync(path6.dirname(file), { recursive: true });
|
|
1955
2059
|
fs6.appendFileSync(file, `${JSON.stringify(line)}
|
|
1956
2060
|
`, "utf8");
|
|
@@ -14578,7 +14578,7 @@
|
|
|
14578
14578
|
"reason": "scaffold template for ticket-bundle tickets (ADR-101); carries no runtime frontmatter — emit-tickets fills it in per ticket"
|
|
14579
14579
|
}
|
|
14580
14580
|
],
|
|
14581
|
-
"generated_at": "2026-08-
|
|
14581
|
+
"generated_at": "2026-08-18T12:03:29Z",
|
|
14582
14582
|
"packs": [
|
|
14583
14583
|
{
|
|
14584
14584
|
"artefact_count": 118,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
c20518e62eeec14688645cf600074ae569563d32a4188ce8ce00d9d022a4631d discovery-manifest.json
|
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
|
}
|
|
@@ -28499,12 +28603,12 @@ function resolveDepth(parentRef, open) {
|
|
|
28499
28603
|
if (!parent) return { depth: 2, depth_basis: "asserted-parent" };
|
|
28500
28604
|
return { depth: parent.depth + 1, depth_basis: "observed" };
|
|
28501
28605
|
}
|
|
28502
|
-
function
|
|
28606
|
+
function ledgerFileFor2(root, ts) {
|
|
28503
28607
|
return path70.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
|
|
28504
28608
|
}
|
|
28505
28609
|
function appendLedgerLine(root, ts, line) {
|
|
28506
28610
|
if (is_replay_mode()) return;
|
|
28507
|
-
const file =
|
|
28611
|
+
const file = ledgerFileFor2(root, ts);
|
|
28508
28612
|
fs69.mkdirSync(path70.dirname(file), { recursive: true });
|
|
28509
28613
|
fs69.appendFileSync(file, `${JSON.stringify(line)}
|
|
28510
28614
|
`, "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.1.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.1.0"
|
|
20
20
|
}
|