@event4u/agent-config 14.14.0 → 14.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +63 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/pr/create.md +16 -1
- package/dist/agent-src/contexts/execution/terminal-states.md +25 -4
- package/dist/agent-src/rules/missing-skill-recovery.md +2 -2
- package/dist/agent-src/rules/token-budget-discipline.md +2 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli-delegate/{chunk-ILMUPLGW.js → chunk-7RXIKF62.js} +159 -86
- package/dist/cli-delegate/cmd_conformance.js +513 -17
- package/dist/cli-delegate/cmd_doctor.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +5 -5
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +1043 -390
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +703 -115
- package/package.json +1 -1
- package/src/config/assurance-capability-registry.json +1 -1
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +62 -2
- package/src/config/gate-reachability-exemptions.json +1 -0
- package/src/config/gate-violation-baselines.json +4 -3
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/config/web-launch-readiness.json +4 -2
- package/src/scripts/_cli/_doctor_emit.ts +71 -0
- package/src/scripts/_cli/_doctor_strict.ts +163 -0
- package/src/scripts/_cli/cmd_doctor.ts +16 -37
- package/src/scripts/_dispatch.bash +11 -0
- package/src/scripts/_lib/branch_convergence.ts +186 -0
- package/src/scripts/_lib/context_observation.ts +132 -0
- package/src/scripts/_lib/continuation_ladder.ts +188 -0
- package/src/scripts/_lib/experiment_binding.ts +95 -0
- package/src/scripts/_lib/ignored_blocker.ts +8 -1
- package/src/scripts/_lib/outcome_envelope.ts +6 -1
- package/src/scripts/_lib/outcome_vocabularies.ts +135 -5
- package/src/scripts/_lib/release_highlights.ts +75 -10
- package/src/scripts/_lib/repeated_failure.ts +19 -2
- package/src/scripts/_lib/runtime_journal.ts +11 -4
- package/src/scripts/check_agent_artifact_location.ts +337 -0
- package/src/scripts/check_artefact_count_messaging.ts +74 -8
- package/src/scripts/check_enforcement_coverage.ts +40 -1
- package/src/scripts/check_release_highlights.ts +17 -1
- package/src/scripts/check_web_launch_readiness.ts +310 -20
- package/src/scripts/evolution_lab.ts +53 -1
- package/src/scripts/generate_capability_matrix.ts +16 -9
- package/src/scripts/git_authorization_hook.ts +476 -6
- package/src/scripts/grade_target_readiness.ts +221 -2
- package/src/scripts/hook_effect_doctor.ts +47 -4
- package/src/scripts/hooks/block_unauthorized_git.ts +238 -9
- package/src/scripts/hooks/merge_impact.ts +207 -0
- package/src/scripts/hooks/run_continuation_hook.ts +78 -88
- package/src/scripts/hooks/session_eol_hook.ts +12 -1
- package/src/scripts/install-hooks.sh +18 -0
- package/src/scripts/lint_code_comments.ts +106 -1
- package/src/scripts/release.ts +5 -17
- package/src/scripts/release_drill.ts +76 -1
- package/src/scripts/release_env.ts +31 -0
- package/src/scripts/release_publication.ts +125 -1
- package/src/scripts/roadmap_context.ts +15 -0
- package/src/scripts/skill_scout.ts +633 -0
- package/src/scripts/sync_pr_branch.ts +521 -42
package/dist/mcp/server.mjs
CHANGED
|
@@ -33747,6 +33747,115 @@ var init_rule_scope = __esm({
|
|
|
33747
33747
|
}
|
|
33748
33748
|
});
|
|
33749
33749
|
|
|
33750
|
+
// src/scripts/_cli/_doctor_strict.ts
|
|
33751
|
+
function eprint(msg) {
|
|
33752
|
+
process.stderr.write(`${msg}
|
|
33753
|
+
`);
|
|
33754
|
+
}
|
|
33755
|
+
function strictTripping(level) {
|
|
33756
|
+
return level === "warn" ? /* @__PURE__ */ new Set(["fail", "warn"]) : /* @__PURE__ */ new Set(["fail"]);
|
|
33757
|
+
}
|
|
33758
|
+
function canProceed(driftPresent, checks) {
|
|
33759
|
+
return !driftPresent && !checks.some((c) => c["status"] === "fail");
|
|
33760
|
+
}
|
|
33761
|
+
function validateStrictLevel(level) {
|
|
33762
|
+
if (!STRICT_LEVELS.has(level)) {
|
|
33763
|
+
throw new StrictLevelError(
|
|
33764
|
+
`argument --strict-level: invalid choice: '${level}' (choose from 'fail', 'warn')`
|
|
33765
|
+
);
|
|
33766
|
+
}
|
|
33767
|
+
}
|
|
33768
|
+
function strictExit(opts, checks, driftPresent) {
|
|
33769
|
+
if (!opts.strict) {
|
|
33770
|
+
return null;
|
|
33771
|
+
}
|
|
33772
|
+
const tripping = strictTripping(opts.strict_level);
|
|
33773
|
+
const tripped = checks.filter((c) => tripping.has(c["status"]));
|
|
33774
|
+
if (!driftPresent && tripped.length === 0) {
|
|
33775
|
+
return 0;
|
|
33776
|
+
}
|
|
33777
|
+
if (process.env[STRICT_KILL_SWITCH] === "1") {
|
|
33778
|
+
eprint(
|
|
33779
|
+
`\u26A0\uFE0F doctor --strict: ${String(tripped.length)} finding(s) at or above '${opts.strict_level}'` + (driftPresent ? " plus manifest drift" : "") + `, but ${STRICT_KILL_SWITCH}=1 suppressed the failing exit. The findings above still stand; unset it to restore the gate.`
|
|
33780
|
+
);
|
|
33781
|
+
return 0;
|
|
33782
|
+
}
|
|
33783
|
+
return 1;
|
|
33784
|
+
}
|
|
33785
|
+
function attachProceedFields(payload, driftPresent, checks) {
|
|
33786
|
+
const proceed = canProceed(driftPresent, checks);
|
|
33787
|
+
payload["can_proceed"] = proceed;
|
|
33788
|
+
payload["status"] = proceed ? checks.some((c) => c["status"] === "warn") ? "warn" : "ok" : "fail";
|
|
33789
|
+
}
|
|
33790
|
+
function validateStrictLevelOr(level, onError) {
|
|
33791
|
+
try {
|
|
33792
|
+
validateStrictLevel(level);
|
|
33793
|
+
} catch (e) {
|
|
33794
|
+
if (e instanceof StrictLevelError) onError(e.message);
|
|
33795
|
+
throw e;
|
|
33796
|
+
}
|
|
33797
|
+
}
|
|
33798
|
+
var StrictLevelError, STRICT_DEFAULT_LEVEL, STRICT_LEVELS, STRICT_KILL_SWITCH, STRICT_STORE_TRUE, STRICT_VALUE_FLAGS, STRICT_DEFAULTS, STRICT_USAGE;
|
|
33799
|
+
var init_doctor_strict = __esm({
|
|
33800
|
+
"src/scripts/_cli/_doctor_strict.ts"() {
|
|
33801
|
+
"use strict";
|
|
33802
|
+
StrictLevelError = class extends Error {
|
|
33803
|
+
};
|
|
33804
|
+
STRICT_DEFAULT_LEVEL = "fail";
|
|
33805
|
+
STRICT_LEVELS = /* @__PURE__ */ new Set(["fail", "warn"]);
|
|
33806
|
+
STRICT_KILL_SWITCH = "AGENT_CONFIG_DOCTOR_NO_FAIL";
|
|
33807
|
+
STRICT_STORE_TRUE = { "--strict": "strict" };
|
|
33808
|
+
STRICT_VALUE_FLAGS = { "--strict-level": "strict_level" };
|
|
33809
|
+
STRICT_DEFAULTS = { strict: false, strict_level: STRICT_DEFAULT_LEVEL };
|
|
33810
|
+
STRICT_USAGE = " [--strict] [--strict-level {fail,warn}]\n";
|
|
33811
|
+
}
|
|
33812
|
+
});
|
|
33813
|
+
|
|
33814
|
+
// src/scripts/_cli/_doctor_emit.ts
|
|
33815
|
+
function print(line = "") {
|
|
33816
|
+
process.stdout.write(`${line}
|
|
33817
|
+
`);
|
|
33818
|
+
}
|
|
33819
|
+
function _emit_text(project_root, missing, modified, foreign, tag_drift) {
|
|
33820
|
+
const total = missing.length + modified.length + foreign.length + tag_drift.length;
|
|
33821
|
+
if (total === 0) {
|
|
33822
|
+
print(`\u2705 doctor: manifest matches filesystem under ${project_root}`);
|
|
33823
|
+
return;
|
|
33824
|
+
}
|
|
33825
|
+
print(`\u26A0\uFE0F doctor: ${total} drift item(s) under ${project_root}`);
|
|
33826
|
+
const groups = [
|
|
33827
|
+
["missing", missing],
|
|
33828
|
+
["modified", modified],
|
|
33829
|
+
["foreign", foreign],
|
|
33830
|
+
["tag-drift", tag_drift]
|
|
33831
|
+
];
|
|
33832
|
+
for (const [label, items] of groups) {
|
|
33833
|
+
if (items.length === 0) {
|
|
33834
|
+
continue;
|
|
33835
|
+
}
|
|
33836
|
+
print(`
|
|
33837
|
+
${label} (${items.length}):`);
|
|
33838
|
+
for (const it of items) {
|
|
33839
|
+
const tool = it["tool"] || "?";
|
|
33840
|
+
print(` \xB7 [${tool}] ${it["path"]}`);
|
|
33841
|
+
if (label === "tag-drift") {
|
|
33842
|
+
const found = it["found"] || "(missing)";
|
|
33843
|
+
const expected = "expected" in it ? it["expected"] : PACKAGE_TAG_ID;
|
|
33844
|
+
print(` expected: ${expected}`);
|
|
33845
|
+
print(` found: ${found}`);
|
|
33846
|
+
}
|
|
33847
|
+
print(` fix: ${it["fix"]}`);
|
|
33848
|
+
}
|
|
33849
|
+
}
|
|
33850
|
+
}
|
|
33851
|
+
var PACKAGE_TAG_ID;
|
|
33852
|
+
var init_doctor_emit = __esm({
|
|
33853
|
+
"src/scripts/_cli/_doctor_emit.ts"() {
|
|
33854
|
+
"use strict";
|
|
33855
|
+
PACKAGE_TAG_ID = "event4u/agent-config";
|
|
33856
|
+
}
|
|
33857
|
+
});
|
|
33858
|
+
|
|
33750
33859
|
// src/scripts/_cli/cmd_doctor.ts
|
|
33751
33860
|
var cmd_doctor_exports = {};
|
|
33752
33861
|
__export(cmd_doctor_exports, {
|
|
@@ -33758,7 +33867,7 @@ __export(cmd_doctor_exports, {
|
|
|
33758
33867
|
MCP_BETA_GATES: () => MCP_BETA_GATES,
|
|
33759
33868
|
MIN_PYTHON: () => MIN_PYTHON,
|
|
33760
33869
|
NO_FRONTMATTER: () => NO_FRONTMATTER,
|
|
33761
|
-
PACKAGE_TAG_ID: () =>
|
|
33870
|
+
PACKAGE_TAG_ID: () => PACKAGE_TAG_ID2,
|
|
33762
33871
|
REPAIR_IDS: () => REPAIR_IDS,
|
|
33763
33872
|
STATUS_SYMBOLS: () => STATUS_SYMBOLS,
|
|
33764
33873
|
_check_bridge_drift: () => _check_bridge_drift,
|
|
@@ -33956,10 +34065,10 @@ function yamlSafeLoad2(text) {
|
|
|
33956
34065
|
return void 0;
|
|
33957
34066
|
}
|
|
33958
34067
|
}
|
|
33959
|
-
function
|
|
34068
|
+
function print2(line = "") {
|
|
33960
34069
|
process16.stdout.write(line + "\n");
|
|
33961
34070
|
}
|
|
33962
|
-
function
|
|
34071
|
+
function eprint2(line = "") {
|
|
33963
34072
|
process16.stderr.write(line + "\n");
|
|
33964
34073
|
}
|
|
33965
34074
|
function _resolve_project_root(arg) {
|
|
@@ -34011,22 +34120,22 @@ function _detect_wrapper(project_root) {
|
|
|
34011
34120
|
};
|
|
34012
34121
|
}
|
|
34013
34122
|
function _emit_trace_text(root, anchor, trace, origin) {
|
|
34014
|
-
|
|
34015
|
-
|
|
34123
|
+
print2(` \u{1F4CD} start: ${process16.cwd()}`);
|
|
34124
|
+
print2(` \u{1F4CD} origin: ${origin}`);
|
|
34016
34125
|
if (trace.length > 0) {
|
|
34017
|
-
|
|
34126
|
+
print2(" trace:");
|
|
34018
34127
|
for (const record of trace) {
|
|
34019
34128
|
const hit = record.hit;
|
|
34020
34129
|
const symbol = hit ? "\u2705" : "\xB7";
|
|
34021
34130
|
const tag = `[${record.pass}]`;
|
|
34022
34131
|
const anchor_str = hit ? ` \u2192 ${hit}` : "";
|
|
34023
|
-
|
|
34132
|
+
print2(` ${symbol} ${tag} ${record.ancestor}${anchor_str} (${record.reason})`);
|
|
34024
34133
|
}
|
|
34025
34134
|
}
|
|
34026
34135
|
if (root !== null) {
|
|
34027
|
-
|
|
34136
|
+
print2(` \u{1F4CD} resolved root: ${root} (anchor: ${anchor || "n/a"})`);
|
|
34028
34137
|
} else {
|
|
34029
|
-
|
|
34138
|
+
print2(" \u26A0\uFE0F no anchor found in chain");
|
|
34030
34139
|
}
|
|
34031
34140
|
}
|
|
34032
34141
|
function _emit_trace_json(root, anchor, trace, origin) {
|
|
@@ -34037,32 +34146,32 @@ function _emit_trace_json(root, anchor, trace, origin) {
|
|
|
34037
34146
|
anchor,
|
|
34038
34147
|
trace
|
|
34039
34148
|
};
|
|
34040
|
-
|
|
34149
|
+
print2(_jsonDumpsIndentAscii(payload, 2));
|
|
34041
34150
|
}
|
|
34042
34151
|
function _emit_context_text(ctx) {
|
|
34043
|
-
|
|
34044
|
-
|
|
34152
|
+
print2(` \u{1F4CD} project_root: ${ctx["project_root"]} (origin: ${ctx["origin"]})`);
|
|
34153
|
+
print2(` \u{1F4E6} install_mode: ${ctx["install_mode"]} (source: ${ctx["install_mode_source"]})`);
|
|
34045
34154
|
const env_pin = ctx["env_pin"];
|
|
34046
34155
|
if (env_pin) {
|
|
34047
34156
|
const marker = ctx["root_override"] ? " (--root override)" : "";
|
|
34048
|
-
|
|
34157
|
+
print2(` \u{1F512} env_pin: ${env_pin}${marker}`);
|
|
34049
34158
|
} else {
|
|
34050
|
-
|
|
34159
|
+
print2(" \u{1F512} env_pin: (unset)");
|
|
34051
34160
|
}
|
|
34052
34161
|
const layers = ctx["settings_layers"] || [];
|
|
34053
34162
|
if (layers.length > 0) {
|
|
34054
|
-
|
|
34163
|
+
print2(` \u{1F4D1} settings layers (${layers.length}):`);
|
|
34055
34164
|
for (const layer of layers) {
|
|
34056
|
-
|
|
34165
|
+
print2(` - ${layer}`);
|
|
34057
34166
|
}
|
|
34058
34167
|
} else {
|
|
34059
|
-
|
|
34168
|
+
print2(" \u{1F4D1} settings layers: (none)");
|
|
34060
34169
|
}
|
|
34061
34170
|
const wrapper = ctx["wrapper"] || {};
|
|
34062
34171
|
if (wrapper["exists"]) {
|
|
34063
|
-
|
|
34172
|
+
print2(` \u{1F9E9} wrapper: ${wrapper["path"]} (embedded root: ${wrapper["embedded_root"]})`);
|
|
34064
34173
|
} else {
|
|
34065
|
-
|
|
34174
|
+
print2(` \u{1F9E9} wrapper: (not present at ${wrapper["path"]})`);
|
|
34066
34175
|
}
|
|
34067
34176
|
}
|
|
34068
34177
|
function _resolve_path(project_root, raw) {
|
|
@@ -34245,12 +34354,12 @@ function _classify2(records) {
|
|
|
34245
34354
|
continue;
|
|
34246
34355
|
}
|
|
34247
34356
|
const tag = _read_inline_package_tag(target);
|
|
34248
|
-
if (!(tag instanceof _Sentinel) && tag !==
|
|
34357
|
+
if (!(tag instanceof _Sentinel) && tag !== PACKAGE_TAG_ID2) {
|
|
34249
34358
|
tag_drift.push({
|
|
34250
34359
|
tool: tool_id,
|
|
34251
34360
|
path: String(target),
|
|
34252
34361
|
kind,
|
|
34253
|
-
expected:
|
|
34362
|
+
expected: PACKAGE_TAG_ID2,
|
|
34254
34363
|
found: tag === null ? "" : tag,
|
|
34255
34364
|
fix: _fix_hint("tag-drift", kind)
|
|
34256
34365
|
});
|
|
@@ -34857,7 +34966,7 @@ function _check_stale_orphans() {
|
|
|
34857
34966
|
continue;
|
|
34858
34967
|
}
|
|
34859
34968
|
const tag = _read_inline_package_tag(md);
|
|
34860
|
-
if (tag instanceof _Sentinel || tag !==
|
|
34969
|
+
if (tag instanceof _Sentinel || tag !== PACKAGE_TAG_ID2) {
|
|
34861
34970
|
continue;
|
|
34862
34971
|
}
|
|
34863
34972
|
orphan_count += 1;
|
|
@@ -35885,17 +35994,17 @@ function _run_no_manifest(opts, project_root, origin, bridge_present) {
|
|
|
35885
35994
|
if (opts.json) {
|
|
35886
35995
|
_emit_json(project_root, [], [], [], [], checks, origin);
|
|
35887
35996
|
} else if (opts.check === null) {
|
|
35888
|
-
|
|
35997
|
+
print2(` \u{1F4CD} project_root: ${project_root} (origin: ${origin})`);
|
|
35889
35998
|
if (bridge_present) {
|
|
35890
|
-
|
|
35999
|
+
print2(
|
|
35891
36000
|
" \u2139\uFE0F global-only consumer: install marker present, no project lockfile (expected under ADR-020)"
|
|
35892
36001
|
);
|
|
35893
|
-
|
|
36002
|
+
print2(
|
|
35894
36003
|
" project-manifest checks are skipped \u2014 they apply only to project-local distributed tools"
|
|
35895
36004
|
);
|
|
35896
36005
|
} else {
|
|
35897
|
-
|
|
35898
|
-
|
|
36006
|
+
eprint2(` \u26A0\uFE0F no project lockfile and no consumer install marker at ${project_root}`);
|
|
36007
|
+
eprint2(
|
|
35899
36008
|
" run `agent-config init` (project install) or `agent-config refresh --project` (global-only consumer)"
|
|
35900
36009
|
);
|
|
35901
36010
|
}
|
|
@@ -35911,6 +36020,8 @@ function _run_no_manifest(opts, project_root, origin, bridge_present) {
|
|
|
35911
36020
|
}
|
|
35912
36021
|
return fail_check ? 1 : 0;
|
|
35913
36022
|
}
|
|
36023
|
+
const sx = strictExit(opts, checks, false);
|
|
36024
|
+
if (sx !== null) return bridge_present ? sx : 2;
|
|
35914
36025
|
if (opts.ci && bridge_present) {
|
|
35915
36026
|
return fail_check ? 1 : 0;
|
|
35916
36027
|
}
|
|
@@ -35929,66 +36040,36 @@ function _emit_json(project_root, missing, modified, foreign, tag_drift, checks
|
|
|
35929
36040
|
}
|
|
35930
36041
|
if (checks !== null) {
|
|
35931
36042
|
payload["checks"] = checks;
|
|
36043
|
+
const drift = missing.length + modified.length + foreign.length + tag_drift.length;
|
|
36044
|
+
attachProceedFields(payload, drift > 0, checks);
|
|
35932
36045
|
if (checks.some((c) => c["id"] === "detection")) {
|
|
35933
36046
|
payload["detection"] = detectionJson(_detection_report(project_root));
|
|
35934
36047
|
}
|
|
35935
36048
|
}
|
|
35936
|
-
|
|
36049
|
+
print2(_jsonDumpsIndentAscii(payload, 2));
|
|
35937
36050
|
}
|
|
35938
36051
|
function _emit_checks_text(checks) {
|
|
35939
36052
|
if (checks.length === 0) {
|
|
35940
36053
|
return;
|
|
35941
36054
|
}
|
|
35942
|
-
|
|
36055
|
+
print2("checks:");
|
|
35943
36056
|
for (const c of checks) {
|
|
35944
36057
|
const sym = STATUS_SYMBOLS[c["status"]] ?? "?";
|
|
35945
|
-
|
|
36058
|
+
print2(` ${sym} ${c["id"]}: ${c["message"]}`);
|
|
35946
36059
|
if (c["status"] !== "ok" && c["remedy"]) {
|
|
35947
|
-
|
|
36060
|
+
print2(` fix: ${c["remedy"]}`);
|
|
35948
36061
|
}
|
|
35949
36062
|
}
|
|
35950
|
-
|
|
36063
|
+
print2("");
|
|
35951
36064
|
}
|
|
35952
36065
|
function _emit_detection_text(project_root, checks) {
|
|
35953
36066
|
if (!checks.some((c) => c["id"] === "detection")) {
|
|
35954
36067
|
return;
|
|
35955
36068
|
}
|
|
35956
36069
|
for (const line of renderDetectionLines(_detection_report(project_root))) {
|
|
35957
|
-
|
|
35958
|
-
}
|
|
35959
|
-
print("");
|
|
35960
|
-
}
|
|
35961
|
-
function _emit_text(project_root, missing, modified, foreign, tag_drift) {
|
|
35962
|
-
const total = missing.length + modified.length + foreign.length + tag_drift.length;
|
|
35963
|
-
if (total === 0) {
|
|
35964
|
-
print(`\u2705 doctor: manifest matches filesystem under ${project_root}`);
|
|
35965
|
-
return;
|
|
35966
|
-
}
|
|
35967
|
-
print(`\u26A0\uFE0F doctor: ${total} drift item(s) under ${project_root}`);
|
|
35968
|
-
const groups = [
|
|
35969
|
-
["missing", missing],
|
|
35970
|
-
["modified", modified],
|
|
35971
|
-
["foreign", foreign],
|
|
35972
|
-
["tag-drift", tag_drift]
|
|
35973
|
-
];
|
|
35974
|
-
for (const [label, items] of groups) {
|
|
35975
|
-
if (items.length === 0) {
|
|
35976
|
-
continue;
|
|
35977
|
-
}
|
|
35978
|
-
print(`
|
|
35979
|
-
${label} (${items.length}):`);
|
|
35980
|
-
for (const it of items) {
|
|
35981
|
-
const tool = it["tool"] || "?";
|
|
35982
|
-
print(` \xB7 [${tool}] ${it["path"]}`);
|
|
35983
|
-
if (label === "tag-drift") {
|
|
35984
|
-
const found = it["found"] || "(missing)";
|
|
35985
|
-
const expected = "expected" in it ? it["expected"] : PACKAGE_TAG_ID;
|
|
35986
|
-
print(` expected: ${expected}`);
|
|
35987
|
-
print(` found: ${found}`);
|
|
35988
|
-
}
|
|
35989
|
-
print(` fix: ${it["fix"]}`);
|
|
35990
|
-
}
|
|
36070
|
+
print2(line);
|
|
35991
36071
|
}
|
|
36072
|
+
print2("");
|
|
35992
36073
|
}
|
|
35993
36074
|
function _argError9(msg) {
|
|
35994
36075
|
process16.stderr.write(USAGE2);
|
|
@@ -36001,6 +36082,7 @@ function _parse3(argv) {
|
|
|
36001
36082
|
project: null,
|
|
36002
36083
|
json: false,
|
|
36003
36084
|
ci: false,
|
|
36085
|
+
...STRICT_DEFAULTS,
|
|
36004
36086
|
check: null,
|
|
36005
36087
|
trace_root: false,
|
|
36006
36088
|
context: false,
|
|
@@ -36098,7 +36180,7 @@ function _run_context(opts) {
|
|
|
36098
36180
|
[project_root, origin] = _resolve_project_root(opts.project);
|
|
36099
36181
|
} catch (exc) {
|
|
36100
36182
|
if (exc instanceof ProjectRootError) {
|
|
36101
|
-
|
|
36183
|
+
eprint2(`\u274C doctor: ${exc.message}`);
|
|
36102
36184
|
return 2;
|
|
36103
36185
|
}
|
|
36104
36186
|
throw exc;
|
|
@@ -36115,7 +36197,7 @@ function _run_context(opts) {
|
|
|
36115
36197
|
wrapper: _detect_wrapper(project_root)
|
|
36116
36198
|
};
|
|
36117
36199
|
if (opts.json) {
|
|
36118
|
-
|
|
36200
|
+
print2(_jsonDumpsIndentAscii(ctx, 2));
|
|
36119
36201
|
} else {
|
|
36120
36202
|
_emit_context_text(ctx);
|
|
36121
36203
|
}
|
|
@@ -36137,7 +36219,7 @@ function _run_anatomy(opts) {
|
|
|
36137
36219
|
economyError = err instanceof Error ? err.message : String(err);
|
|
36138
36220
|
}
|
|
36139
36221
|
if (opts.json) {
|
|
36140
|
-
|
|
36222
|
+
print2(
|
|
36141
36223
|
_jsonDumpsIndentAscii(
|
|
36142
36224
|
{
|
|
36143
36225
|
preamble_byte_census: byteCensus,
|
|
@@ -36149,10 +36231,10 @@ function _run_anatomy(opts) {
|
|
|
36149
36231
|
);
|
|
36150
36232
|
return 0;
|
|
36151
36233
|
}
|
|
36152
|
-
|
|
36153
|
-
|
|
36154
|
-
|
|
36155
|
-
|
|
36234
|
+
print2("\u2500\u2500 injection anatomy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
36235
|
+
print2(census.renderText(byteCensus));
|
|
36236
|
+
print2("");
|
|
36237
|
+
print2(
|
|
36156
36238
|
economyReport !== null ? economy.renderText(economyReport) : `dispatch-economy report unavailable: ${economyError}`
|
|
36157
36239
|
);
|
|
36158
36240
|
return 0;
|
|
@@ -36174,29 +36256,30 @@ function _run_repair(opts) {
|
|
|
36174
36256
|
payload["action"] = "error";
|
|
36175
36257
|
payload["error"] = osErrorStr2(exc);
|
|
36176
36258
|
if (opts.json) {
|
|
36177
|
-
|
|
36259
|
+
print2(_jsonDumpsIndentAscii(payload, 2));
|
|
36178
36260
|
} else {
|
|
36179
|
-
|
|
36261
|
+
eprint2(`\u274C doctor: could not remove ${state_pth}: ${osErrorStr2(exc)}`);
|
|
36180
36262
|
}
|
|
36181
36263
|
return 2;
|
|
36182
36264
|
}
|
|
36183
36265
|
}
|
|
36184
36266
|
if (opts.json) {
|
|
36185
|
-
|
|
36267
|
+
print2(_jsonDumpsIndentAscii(payload, 2));
|
|
36186
36268
|
} else {
|
|
36187
36269
|
if (payload["action"] === "remove") {
|
|
36188
|
-
|
|
36270
|
+
print2(`\u2705 doctor: reset wizard-state (${state_pth})`);
|
|
36189
36271
|
} else {
|
|
36190
|
-
|
|
36272
|
+
print2(`\u2705 doctor: nothing to repair (no wizard-state at ${state_pth})`);
|
|
36191
36273
|
}
|
|
36192
36274
|
}
|
|
36193
36275
|
return 0;
|
|
36194
36276
|
}
|
|
36195
|
-
|
|
36277
|
+
eprint2(`\u274C doctor: unknown repair target ${pyRepr2(target)}`);
|
|
36196
36278
|
return 2;
|
|
36197
36279
|
}
|
|
36198
36280
|
function main21(argv = null) {
|
|
36199
36281
|
const opts = _parse3(argv !== null ? Array.from(argv) : process16.argv.slice(2));
|
|
36282
|
+
validateStrictLevelOr(opts.strict_level, _argError9);
|
|
36200
36283
|
if (opts.ci) {
|
|
36201
36284
|
opts.json = true;
|
|
36202
36285
|
}
|
|
@@ -36218,7 +36301,7 @@ function main21(argv = null) {
|
|
|
36218
36301
|
[project_root, origin] = _resolve_project_root(opts.project);
|
|
36219
36302
|
} catch (exc) {
|
|
36220
36303
|
if (exc instanceof ProjectRootError) {
|
|
36221
|
-
|
|
36304
|
+
eprint2(`\u274C doctor: ${exc.message}`);
|
|
36222
36305
|
return 2;
|
|
36223
36306
|
}
|
|
36224
36307
|
throw exc;
|
|
@@ -36244,7 +36327,7 @@ function main21(argv = null) {
|
|
|
36244
36327
|
_emit_json(project_root, missing, modified, foreign, tag_drift, checks, origin);
|
|
36245
36328
|
} else {
|
|
36246
36329
|
if (opts.check === null) {
|
|
36247
|
-
|
|
36330
|
+
print2(` \u{1F4CD} project_root: ${project_root} (origin: ${origin})`);
|
|
36248
36331
|
}
|
|
36249
36332
|
_emit_checks_text(checks);
|
|
36250
36333
|
_emit_detection_text(project_root, checks);
|
|
@@ -36261,6 +36344,8 @@ function main21(argv = null) {
|
|
|
36261
36344
|
if (opts.ci) {
|
|
36262
36345
|
return drift_present || fail_check ? 1 : 0;
|
|
36263
36346
|
}
|
|
36347
|
+
const strict = strictExit(opts, checks, drift_present);
|
|
36348
|
+
if (strict !== null) return strict;
|
|
36264
36349
|
return drift_present ? 1 : 0;
|
|
36265
36350
|
}
|
|
36266
36351
|
function _isCliEntry17() {
|
|
@@ -36289,7 +36374,7 @@ function _isCliEntry17() {
|
|
|
36289
36374
|
return false;
|
|
36290
36375
|
}
|
|
36291
36376
|
}
|
|
36292
|
-
var YAML6, _HERE18, ArgparseExit4, _Sentinel, NO_FRONTMATTER, _INSTALL_MODE_MARKER_REL,
|
|
36377
|
+
var YAML6, _HERE18, ArgparseExit4, _Sentinel, NO_FRONTMATTER, _INSTALL_MODE_MARKER_REL, PACKAGE_TAG_ID2, _FRONTMATTER_PACKAGE_RE, CHECK_IDS, GLOBAL_CHECK_IDS, MANIFEST_REQUIRED_CHECK_IDS, REPAIR_IDS, MCP_BETA_GATES, STATUS_SYMBOLS, MIN_PYTHON, _PLACEHOLDER_EMAIL_RE, _PLACEHOLDER_NAME_RE, _RULE_PROJECTION_DIRS, _check_python_runtime, _check_humanizer_runtime, _CLI_PROVIDER_META, _detection_memo, PROG4, USAGE2, _STORE_TRUE_FLAGS, _VALUE_FLAGS;
|
|
36293
36378
|
var init_cmd_doctor = __esm({
|
|
36294
36379
|
"src/scripts/_cli/cmd_doctor.ts"() {
|
|
36295
36380
|
"use strict";
|
|
@@ -36320,6 +36405,8 @@ var init_cmd_doctor = __esm({
|
|
|
36320
36405
|
init_review_gate();
|
|
36321
36406
|
init_package_root();
|
|
36322
36407
|
init_rule_scope();
|
|
36408
|
+
init_doctor_strict();
|
|
36409
|
+
init_doctor_emit();
|
|
36323
36410
|
_HERE18 = fileURLToPath31(import.meta.url);
|
|
36324
36411
|
ArgparseExit4 = class extends Error {
|
|
36325
36412
|
constructor(code) {
|
|
@@ -36332,7 +36419,7 @@ var init_cmd_doctor = __esm({
|
|
|
36332
36419
|
};
|
|
36333
36420
|
NO_FRONTMATTER = new _Sentinel();
|
|
36334
36421
|
_INSTALL_MODE_MARKER_REL = "agents/.agent-state/install-mode.txt";
|
|
36335
|
-
|
|
36422
|
+
PACKAGE_TAG_ID2 = "event4u/agent-config";
|
|
36336
36423
|
_FRONTMATTER_PACKAGE_RE = /^package:\s*(.+?)\s*$/m;
|
|
36337
36424
|
CHECK_IDS = [
|
|
36338
36425
|
"scope",
|
|
@@ -36427,17 +36514,17 @@ var init_cmd_doctor = __esm({
|
|
|
36427
36514
|
_detection_memo = null;
|
|
36428
36515
|
PROG4 = "agent-config doctor";
|
|
36429
36516
|
USAGE2 = `usage: ${PROG4} [-h] [--project PROJECT] [--json] [--ci] [--check ID]
|
|
36430
|
-
[--trace-root] [--context] [--anatomy]
|
|
36431
|
-
[--repair ID]
|
|
36432
|
-
`;
|
|
36517
|
+
` + STRICT_USAGE + " [--trace-root] [--context] [--anatomy]\n [--repair ID]\n";
|
|
36433
36518
|
_STORE_TRUE_FLAGS = {
|
|
36434
36519
|
"--json": "json",
|
|
36435
36520
|
"--ci": "ci",
|
|
36521
|
+
...STRICT_STORE_TRUE,
|
|
36436
36522
|
"--trace-root": "trace_root",
|
|
36437
36523
|
"--context": "context",
|
|
36438
36524
|
"--anatomy": "anatomy"
|
|
36439
36525
|
};
|
|
36440
36526
|
_VALUE_FLAGS = {
|
|
36527
|
+
...STRICT_VALUE_FLAGS,
|
|
36441
36528
|
"--project": "project",
|
|
36442
36529
|
"--check": "check",
|
|
36443
36530
|
"--repair": "repair"
|
|
@@ -37202,6 +37289,65 @@ function splitFences(prompt) {
|
|
|
37202
37289
|
function _commandLines(fence) {
|
|
37203
37290
|
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));
|
|
37204
37291
|
}
|
|
37292
|
+
function extractMergeTargets(prompt) {
|
|
37293
|
+
const out = /* @__PURE__ */ new Set();
|
|
37294
|
+
const re = /(?:#|\bpr[- ]?|\bpull[- ]request\s+#?)(\d{1,7})\b/giu;
|
|
37295
|
+
let m;
|
|
37296
|
+
while ((m = re.exec(prompt)) !== null) {
|
|
37297
|
+
const n = Number.parseInt(m[1], 10);
|
|
37298
|
+
if (Number.isSafeInteger(n) && n > 0) {
|
|
37299
|
+
out.add(n);
|
|
37300
|
+
}
|
|
37301
|
+
}
|
|
37302
|
+
return [...out].sort((a, b) => a - b);
|
|
37303
|
+
}
|
|
37304
|
+
function isRevocation(prompt) {
|
|
37305
|
+
return REVOKE_RE.test(prompt);
|
|
37306
|
+
}
|
|
37307
|
+
function _grantId(session_id, at, targets) {
|
|
37308
|
+
return `${sessionSlug(session_id) || "nosession"}:${at}:${targets.join(",")}`;
|
|
37309
|
+
}
|
|
37310
|
+
function foldGrants(prior, prompt, authorized, session_id, now) {
|
|
37311
|
+
if (isRevocation(prompt)) {
|
|
37312
|
+
return [];
|
|
37313
|
+
}
|
|
37314
|
+
const carried = prior.filter((g) => g.consumed.length < g.targets.length);
|
|
37315
|
+
if (!authorized.includes("pr-merge")) {
|
|
37316
|
+
return carried;
|
|
37317
|
+
}
|
|
37318
|
+
const targets = extractMergeTargets(prompt);
|
|
37319
|
+
if (targets.length === 0) {
|
|
37320
|
+
return carried;
|
|
37321
|
+
}
|
|
37322
|
+
const at = now.toISOString();
|
|
37323
|
+
const already = new Set(carried.flatMap((g) => g.targets));
|
|
37324
|
+
const fresh = targets.filter((t) => !already.has(t));
|
|
37325
|
+
if (fresh.length === 0) {
|
|
37326
|
+
return carried;
|
|
37327
|
+
}
|
|
37328
|
+
return [
|
|
37329
|
+
...carried,
|
|
37330
|
+
{
|
|
37331
|
+
id: _grantId(session_id, at, fresh),
|
|
37332
|
+
op: "pr-merge",
|
|
37333
|
+
targets: fresh,
|
|
37334
|
+
consumed: [],
|
|
37335
|
+
granted_at: at,
|
|
37336
|
+
evidence: prompt.trim().slice(0, 120)
|
|
37337
|
+
}
|
|
37338
|
+
];
|
|
37339
|
+
}
|
|
37340
|
+
function negatedBefore(text, index, matched = "") {
|
|
37341
|
+
let start = 0;
|
|
37342
|
+
for (const mark of [".", "!", "?", "\n"]) {
|
|
37343
|
+
const at = text.lastIndexOf(mark, index - 1);
|
|
37344
|
+
if (at + 1 > start) {
|
|
37345
|
+
start = at + 1;
|
|
37346
|
+
}
|
|
37347
|
+
}
|
|
37348
|
+
const before = text.slice(Math.max(start, index - 30), index);
|
|
37349
|
+
return NEGATION_WORD.test(before) || NEGATION_WORD.test(matched);
|
|
37350
|
+
}
|
|
37205
37351
|
function classifyAuthorization(prompt) {
|
|
37206
37352
|
const { prose, fences } = splitFences(prompt);
|
|
37207
37353
|
const authorized = /* @__PURE__ */ new Set();
|
|
@@ -37209,7 +37355,7 @@ function classifyAuthorization(prompt) {
|
|
|
37209
37355
|
const instruction = prose.split("\n").filter((l) => !OUTPUT_LINE.test(l)).join("\n");
|
|
37210
37356
|
for (const { op, re } of isInterrogative(instruction) ? [] : PHRASES) {
|
|
37211
37357
|
const m = re.exec(instruction);
|
|
37212
|
-
if (m) {
|
|
37358
|
+
if (m && !negatedBefore(instruction, m.index, m[0])) {
|
|
37213
37359
|
authorized.add(op);
|
|
37214
37360
|
evidence[op] = `prose: "${m[0]}"`;
|
|
37215
37361
|
}
|
|
@@ -37231,6 +37377,68 @@ function classifyAuthorization(prompt) {
|
|
|
37231
37377
|
}
|
|
37232
37378
|
return { authorized: [...authorized], evidence };
|
|
37233
37379
|
}
|
|
37380
|
+
function readGrants(consumer_root, session_id) {
|
|
37381
|
+
try {
|
|
37382
|
+
const raw = fs65.readFileSync(path66.join(consumer_root, ledgerFileFor(session_id)), "utf8");
|
|
37383
|
+
const decoded = JSON.parse(raw);
|
|
37384
|
+
if (!_isObject4(decoded) || !Array.isArray(decoded["grants"])) {
|
|
37385
|
+
return [];
|
|
37386
|
+
}
|
|
37387
|
+
const owner = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
|
|
37388
|
+
if (session_id && owner && owner !== session_id) {
|
|
37389
|
+
return [];
|
|
37390
|
+
}
|
|
37391
|
+
return decoded["grants"].flatMap((g) => {
|
|
37392
|
+
if (!_isObject4(g)) return [];
|
|
37393
|
+
const op = g["op"];
|
|
37394
|
+
if (typeof op !== "string" || !ALL_OPS.includes(op)) return [];
|
|
37395
|
+
const targets = Array.isArray(g["targets"]) ? g["targets"] : [];
|
|
37396
|
+
const consumed = Array.isArray(g["consumed"]) ? g["consumed"] : [];
|
|
37397
|
+
return [
|
|
37398
|
+
{
|
|
37399
|
+
id: String(g["id"] ?? ""),
|
|
37400
|
+
op,
|
|
37401
|
+
targets: targets.filter((t) => typeof t === "number"),
|
|
37402
|
+
consumed: consumed.filter((t) => typeof t === "number"),
|
|
37403
|
+
granted_at: String(g["granted_at"] ?? ""),
|
|
37404
|
+
evidence: String(g["evidence"] ?? "")
|
|
37405
|
+
}
|
|
37406
|
+
];
|
|
37407
|
+
});
|
|
37408
|
+
} catch {
|
|
37409
|
+
return [];
|
|
37410
|
+
}
|
|
37411
|
+
}
|
|
37412
|
+
function consumeGrantTarget(consumer_root, session_id, target) {
|
|
37413
|
+
const file = path66.join(consumer_root, ledgerFileFor(session_id));
|
|
37414
|
+
let decoded;
|
|
37415
|
+
try {
|
|
37416
|
+
decoded = JSON.parse(fs65.readFileSync(file, "utf8"));
|
|
37417
|
+
} catch {
|
|
37418
|
+
return;
|
|
37419
|
+
}
|
|
37420
|
+
if (!_isObject4(decoded) || !Array.isArray(decoded["grants"])) {
|
|
37421
|
+
return;
|
|
37422
|
+
}
|
|
37423
|
+
let touched = false;
|
|
37424
|
+
for (const g of decoded["grants"]) {
|
|
37425
|
+
if (!_isObject4(g) || !Array.isArray(g["targets"]) || !Array.isArray(g["consumed"])) {
|
|
37426
|
+
continue;
|
|
37427
|
+
}
|
|
37428
|
+
const targets = g["targets"];
|
|
37429
|
+
const consumed = g["consumed"];
|
|
37430
|
+
if (targets.includes(target) && !consumed.includes(target)) {
|
|
37431
|
+
consumed.push(target);
|
|
37432
|
+
touched = true;
|
|
37433
|
+
}
|
|
37434
|
+
}
|
|
37435
|
+
if (touched) {
|
|
37436
|
+
try {
|
|
37437
|
+
atomic_write_json(file, decoded);
|
|
37438
|
+
} catch {
|
|
37439
|
+
}
|
|
37440
|
+
}
|
|
37441
|
+
}
|
|
37234
37442
|
function takePending(consumer_root, session_id, now) {
|
|
37235
37443
|
const file = path66.join(consumer_root, pendingFileFor(session_id));
|
|
37236
37444
|
let raw;
|
|
@@ -37299,12 +37507,24 @@ function run2(stdin_text, options) {
|
|
|
37299
37507
|
authorized.push(pending.op);
|
|
37300
37508
|
evidence[pending.op] = `confirmation of the refused \`${pending.op}\` (refused ${pending.refused_at}): "${prompt.trim().slice(0, 40)}"`;
|
|
37301
37509
|
}
|
|
37510
|
+
const now = /* @__PURE__ */ new Date();
|
|
37302
37511
|
const ledger = {
|
|
37303
37512
|
session_id: typeof envelope["session_id"] === "string" ? envelope["session_id"] : "",
|
|
37304
|
-
detected_at:
|
|
37513
|
+
detected_at: now.toISOString(),
|
|
37305
37514
|
authorized,
|
|
37306
37515
|
evidence,
|
|
37307
|
-
prompt_chars: prompt.length
|
|
37516
|
+
prompt_chars: prompt.length,
|
|
37517
|
+
// Grants survive this write; `authorized` does not. That asymmetry IS the
|
|
37518
|
+
// decision. A bare operation name stays one-shot exactly as `commit-policy`
|
|
37519
|
+
// requires, and only an authorization that also froze its objects earns a
|
|
37520
|
+
// life longer than the turn. See ADR-252.
|
|
37521
|
+
grants: foldGrants(
|
|
37522
|
+
readGrants(options.consumer_root, session),
|
|
37523
|
+
prompt,
|
|
37524
|
+
authorized,
|
|
37525
|
+
session,
|
|
37526
|
+
now
|
|
37527
|
+
)
|
|
37308
37528
|
};
|
|
37309
37529
|
try {
|
|
37310
37530
|
atomic_write_json(path66.join(options.consumer_root, ledgerFileFor(session)), ledger);
|
|
@@ -37345,7 +37565,7 @@ function _isCliEntry19() {
|
|
|
37345
37565
|
return false;
|
|
37346
37566
|
}
|
|
37347
37567
|
}
|
|
37348
|
-
var EXIT_ALLOW2, STATE_FILE2, STATE_DIR2, PENDING_DIR, PENDING_FILE, PENDING_MAX_AGE_MS, ALL_OPS, PHRASES, OUTPUT_LINE, PASTED_COMMANDS;
|
|
37568
|
+
var EXIT_ALLOW2, STATE_FILE2, STATE_DIR2, PENDING_DIR, PENDING_FILE, PENDING_MAX_AGE_MS, ALL_OPS, PHRASES, OUTPUT_LINE, PASTED_COMMANDS, REVOKE_RE, NEGATION_WORD;
|
|
37349
37569
|
var init_git_authorization_hook = __esm({
|
|
37350
37570
|
"src/scripts/git_authorization_hook.ts"() {
|
|
37351
37571
|
"use strict";
|
|
@@ -37368,12 +37588,39 @@ var init_git_authorization_hook = __esm({
|
|
|
37368
37588
|
"pr-merge-auto",
|
|
37369
37589
|
"tag",
|
|
37370
37590
|
"release",
|
|
37371
|
-
"publish"
|
|
37591
|
+
"publish",
|
|
37592
|
+
"unpublish",
|
|
37593
|
+
"deprecate",
|
|
37594
|
+
"release-asset",
|
|
37595
|
+
"protection",
|
|
37596
|
+
"workflow-toggle",
|
|
37597
|
+
"repo-lifecycle",
|
|
37598
|
+
"review-approve",
|
|
37599
|
+
"force-push",
|
|
37600
|
+
"worktree-remove",
|
|
37601
|
+
"clean-ignored",
|
|
37602
|
+
"tag-force",
|
|
37603
|
+
"rebase",
|
|
37604
|
+
"reset-hard",
|
|
37605
|
+
"clean",
|
|
37606
|
+
"stash-drop",
|
|
37607
|
+
"branch-delete",
|
|
37608
|
+
"close"
|
|
37372
37609
|
];
|
|
37373
37610
|
PHRASES = [
|
|
37374
37611
|
{ op: "commit", re: /\b(commit(e|et|te|ten)?|committe|einchecken)\b/i },
|
|
37375
37612
|
{ op: "push", re: /\b(push(e|en|t)?|hochladen|hochschieben|raufschieben)\b/i },
|
|
37376
|
-
|
|
37613
|
+
// A CREATION VERB IS REQUIRED, as for `pr-create` above and for the same
|
|
37614
|
+
// reason. The bare noun could not tell an authorization from a mention, and
|
|
37615
|
+
// the mention is the dangerous half: "den branch nicht löschen" names the
|
|
37616
|
+
// object precisely in order to forbid acting on it, yet authorized branch
|
|
37617
|
+
// creation — the negation trails a bare-noun match, so neither the
|
|
37618
|
+
// look-behind nor the span check in `negatedBefore` can reach it. Widened
|
|
37619
|
+
// 2026-09-03 alongside the destructive-coverage work that measured it.
|
|
37620
|
+
{
|
|
37621
|
+
op: "branch",
|
|
37622
|
+
re: /\b(erstell(e|en)?|mach(e|en)?|leg(e|en)?\s+an|anleg(e|en)?|(er|be)?(ö|oe)ffne|open|create|new|neuer?|starte?|checkout|wechsl?e|switch)\b[^.\n]{0,30}\b(branch|feature-branch|zweig)\b|\b(branch|feature-branch|zweig)\b[^.\n]{0,25}\b(erstellen|anlegen|aufmachen|(ö|oe)ffnen|starten)\b/i
|
|
37623
|
+
},
|
|
37377
37624
|
// A creation verb is required — "schau dir den PR an" is not "open a PR".
|
|
37378
37625
|
{
|
|
37379
37626
|
op: "pr-create",
|
|
@@ -37410,7 +37657,111 @@ var init_git_authorization_hook = __esm({
|
|
|
37410
37657
|
op: "release",
|
|
37411
37658
|
re: /\b(releasen?|ver(ö|oe)ffentlich(e|en))\b(?!\s*[- ]?(notes?|branch|candidate|pr\b|datum|date|zweig))/i
|
|
37412
37659
|
},
|
|
37413
|
-
{ op: "publish", re: /\b(publish(e|en)?|publiziere[n]?)\b/i }
|
|
37660
|
+
{ op: "publish", re: /\b(publish(e|en)?|publiziere[n]?)\b/i },
|
|
37661
|
+
// ── Authorization vocabulary for the ops added 2026-09-03.
|
|
37662
|
+
//
|
|
37663
|
+
// NON-NEGOTIABLE, and the reason it ships in the same change as the new BLOCK
|
|
37664
|
+
// ops: a blocked operation with no phrase that authorizes it is a dead end.
|
|
37665
|
+
// The guard refuses, the user says "doch, mach das", the classifier records
|
|
37666
|
+
// nothing, and the refusal repeats forever. Every op the guard can block must
|
|
37667
|
+
// have a sentence that unblocks it, or the user's override is a claim rather
|
|
37668
|
+
// than a mechanism.
|
|
37669
|
+
//
|
|
37670
|
+
// Tight on purpose. Under-matching costs one confirmation; over-matching
|
|
37671
|
+
// authorizes something the user did not ask for, so each pattern demands the
|
|
37672
|
+
// object noun rather than a bare verb wherever the verb is ordinary German.
|
|
37673
|
+
//
|
|
37674
|
+
// `unpublish` before nothing in particular — `\bpublish\b` does not match
|
|
37675
|
+
// inside "unpublish" (no word boundary between `n` and `p`), so the two never
|
|
37676
|
+
// collide.
|
|
37677
|
+
// TWO SHAPES EVERY GERMAN PATTERN BELOW MUST CARRY, both learned from tests
|
|
37678
|
+
// that went red rather than from inspection:
|
|
37679
|
+
//
|
|
37680
|
+
// 1. THE IMPERATIVE DROPS ITS `-e`. "entfern die worktree", "archivier das
|
|
37681
|
+
// repo", "deaktivier den workflow" are what people type; `entfern(e|en)`
|
|
37682
|
+
// matches none of them. Every verb ending is therefore `(e|en)?`.
|
|
37683
|
+
// 2. WORD ORDER GOES BOTH WAYS. "archivier das repo" and "das repo
|
|
37684
|
+
// archivieren" are one instruction, and a pattern anchored on the noun
|
|
37685
|
+
// first sees only the second.
|
|
37686
|
+
//
|
|
37687
|
+
// The object noun stays REQUIRED in every pattern that has one. That is what
|
|
37688
|
+
// keeps the loosened verb endings from over-authorizing: "lösch" alone
|
|
37689
|
+
// authorizes nothing, "lösch den branch" authorizes a branch deletion.
|
|
37690
|
+
{ op: "unpublish", re: /\bunpublish(e|en)?\b/i },
|
|
37691
|
+
{
|
|
37692
|
+
op: "deprecate",
|
|
37693
|
+
re: /\bdeprecate(d|n)?\b|\bals\s+veraltet\s+markier(e|en)?\b/i
|
|
37694
|
+
},
|
|
37695
|
+
{
|
|
37696
|
+
op: "release-asset",
|
|
37697
|
+
re: /\brelease[-\s]?asset\b|\b(clobber|(ü|ue)berschreib(e|en)?|ersetz(e|en)?|replace|austausch(e|en)?)\b[^.\n]{0,25}\b(asset|artefakt|artifact|tarball|binary)\b|\b(asset|artefakt|artifact)\b[^.\n]{0,25}\b((ü|ue)berschreiben|ersetzen|austauschen|replace)\b/i
|
|
37698
|
+
},
|
|
37699
|
+
// A VERB IS REQUIRED, like `tag` and `release` above and for the same reason.
|
|
37700
|
+
// A bare noun cannot tell an authorization from a mention, and here the
|
|
37701
|
+
// mention is the dangerous half: "die branch-protection nicht anfassen" names
|
|
37702
|
+
// the object precisely in order to forbid touching it. Measured — this was the
|
|
37703
|
+
// one row of the negation corpus that neither the look-behind nor the
|
|
37704
|
+
// span check could reach, because the negation trails a bare-noun match.
|
|
37705
|
+
{
|
|
37706
|
+
op: "protection",
|
|
37707
|
+
re: /\b(entfern(e|en)?|l(ö|oe)sch(e|en)?|deaktivier(e|en)?|nimm|weg|remove|delete|disable|drop|lift)\b[^.\n]{0,30}\b(branch[-\s]?protection|protection[-\s]?rule|schutzregel|ruleset)\b|\b(branch[-\s]?protection|protection[-\s]?rule|schutzregel|ruleset)\b[^.\n]{0,30}\b(entfernen|l(ö|oe)schen|deaktivieren|abschalten|remove|delete|disable|drop|lift|weg)\b/i
|
|
37708
|
+
},
|
|
37709
|
+
{
|
|
37710
|
+
op: "workflow-toggle",
|
|
37711
|
+
re: /\bworkflow\b[^.\n]{0,25}\b(deaktivier(e|en)?|aktivier(e|en)?|disable|enable|abschalt(e|en)?|ausschalt(e|en)?)\b|\b(deaktivier(e|en)?|aktivier(e|en)?|disable|enable|abschalt(e|en)?|ausschalt(e|en)?)\b[^.\n]{0,25}\bworkflow\b/i
|
|
37712
|
+
},
|
|
37713
|
+
{
|
|
37714
|
+
op: "repo-lifecycle",
|
|
37715
|
+
re: /\b(repo|repository)\b[^.\n]{0,30}\b(archivier(e|en)?|archive|l(ö|oe)sch(e|en)?|delete)\b|\b(archivier(e|en)?|archive|l(ö|oe)sch(e|en)?|delete)\b[^.\n]{0,25}\b(repo|repository)\b|\b(repo|repository)\b[^.\n]{0,30}\bauf\s+(privat|public|öffentlich|private)\b/i
|
|
37716
|
+
},
|
|
37717
|
+
{
|
|
37718
|
+
op: "review-approve",
|
|
37719
|
+
re: /\b(approve|genehmig(e|en)?|freigeb(e|en)?|abnehm(e|en)?)\b[^.\n]{0,30}\b(review|pr|pull[-\s]request)\b|\b(review|pr|pull[-\s]request)\b[^.\n]{0,20}\b(approve|approven|genehmigen|freigeben)\b/i
|
|
37720
|
+
},
|
|
37721
|
+
{
|
|
37722
|
+
op: "force-push",
|
|
37723
|
+
re: /\bforce[-\s]?push(e|en|st)?\b|--force(-with-lease)?\b|\bmit\s+gewalt\s+push(e|en)?\b/i
|
|
37724
|
+
},
|
|
37725
|
+
{
|
|
37726
|
+
op: "worktree-remove",
|
|
37727
|
+
re: /\bworktree\b[^.\n]{0,25}\b(entfern(e|en)?|l(ö|oe)sch(e|en)?|remove|delete|weg(werfen|r(ä|ae)umen))\b|\b(entfern(e|en)?|l(ö|oe)sch(e|en)?|remove|delete)\b[^.\n]{0,25}\bworktree\b/i
|
|
37728
|
+
},
|
|
37729
|
+
// The `-x` flag is the whole distinction: without it `git clean` spares
|
|
37730
|
+
// ignored files, with it `.env` and local certificates go too.
|
|
37731
|
+
{
|
|
37732
|
+
op: "clean-ignored",
|
|
37733
|
+
re: /\bgit\s+clean\b[^.\n]{0,20}-[A-Za-z]*x|\b(ignorierte|ignored)\b[^.\n]{0,25}\b(dateien|files)\b[^.\n]{0,25}\b(l(ö|oe)sch(e|en)?|entfern(e|en)?|remove|delete)\b/i
|
|
37734
|
+
},
|
|
37735
|
+
{
|
|
37736
|
+
op: "tag-force",
|
|
37737
|
+
re: /\btag\b[^.\n]{0,25}\b(verschieb(e|en)?|(ü|ue)berschreib(e|en)?|umh(ä|ae)ng(e|en)?|force|move)\b|\b(verschieb(e|en)?|(ü|ue)berschreib(e|en)?)\b[^.\n]{0,20}\btag\b/i
|
|
37738
|
+
},
|
|
37739
|
+
{ op: "rebase", re: /\brebase(n|st|d)?\b/i },
|
|
37740
|
+
{
|
|
37741
|
+
op: "reset-hard",
|
|
37742
|
+
re: /\breset\s+--hard\b|\bhard\s+reset\b|\bhart\s+zur(ü|ue)cksetz(e|en)?\b/i
|
|
37743
|
+
},
|
|
37744
|
+
{ op: "clean", re: /\bgit\s+clean\b/i },
|
|
37745
|
+
{
|
|
37746
|
+
op: "stash-drop",
|
|
37747
|
+
re: /\bstash\b[^.\n]{0,25}\b(drop|clear|verwerf(e|en)?|l(ö|oe)sch(e|en)?)\b|\b(verwerf(e|en)?|l(ö|oe)sch(e|en)?|drop)\b[^.\n]{0,20}\bstash\b/i
|
|
37748
|
+
},
|
|
37749
|
+
{
|
|
37750
|
+
op: "branch-delete",
|
|
37751
|
+
re: /\b(l(ö|oe)sch(e|en)?|entfern(e|en)?|delete|remove)\b[^.\n]{0,25}\b(branch|branches|zweig)\b|\b(branch|branches|zweig)\b[^.\n]{0,25}\b(l(ö|oe)schen|entfernen|delete|remove)\b/i
|
|
37752
|
+
},
|
|
37753
|
+
{
|
|
37754
|
+
op: "close",
|
|
37755
|
+
re: /\b(schlie(ß|ss)(e|en)?|close)\b[^.\n]{0,30}\b(pr|pull[-\s]request|issue|ticket)\b|\b(pr|pull[-\s]request|issue|ticket)\b[^.\n]{0,20}\b(schlie(ß|ss)en|close)\b/i
|
|
37756
|
+
},
|
|
37757
|
+
// Enabling auto-merge is its own op on the guard side, so it needs its own
|
|
37758
|
+
// sentence. Without one it is the dead end this block exists to prevent: the
|
|
37759
|
+
// plain merge phrase authorizes `pr-merge` and never `pr-merge-auto`, so the
|
|
37760
|
+
// refusal would have repeated forever.
|
|
37761
|
+
{
|
|
37762
|
+
op: "pr-merge-auto",
|
|
37763
|
+
re: /\bauto[-\s]?merge\b|\bmerge\b[^.\n]{0,20}\bautomatisch\b|\bautomatisch\b[^.\n]{0,20}\bmerg(e|en)\b/i
|
|
37764
|
+
}
|
|
37414
37765
|
];
|
|
37415
37766
|
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;
|
|
37416
37767
|
PASTED_COMMANDS = [
|
|
@@ -37433,12 +37784,107 @@ var init_git_authorization_hook = __esm({
|
|
|
37433
37784
|
{ op: "commit", re: /\bgit\s+commit\b/i },
|
|
37434
37785
|
{ op: "branch", re: /\bgit\s+(checkout\s+-b|switch\s+-c|branch)\b/i }
|
|
37435
37786
|
];
|
|
37787
|
+
REVOKE_RE = /\b(stop|stopp|halt|abbrechen|abbruch|cancel|revoke|widerruf(en)?|zur(ü|ue)ck(nehmen|ziehen)|hold\s+(everything|on|the\s+merge)|warte|wait)\b|(?:\b(nicht|kein(e|en)?|no|not|dont|don't|never|niemals|nie)\b[^.!?\n]{0,30}\b(merg(e|en|ing)|zusammenf(ü|ue)hren)\b)/iu;
|
|
37788
|
+
NEGATION_WORD = /\b(nicht|nichts|kein(e|en|em|er|es)?|niemals|nie|no|not|dont|don't|never|without|ohne)\b/i;
|
|
37436
37789
|
if (_isCliEntry19()) {
|
|
37437
37790
|
process.exit(main23());
|
|
37438
37791
|
}
|
|
37439
37792
|
}
|
|
37440
37793
|
});
|
|
37441
37794
|
|
|
37795
|
+
// src/scripts/hooks/merge_impact.ts
|
|
37796
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
37797
|
+
function classifyDiff(patch) {
|
|
37798
|
+
if (patch.trim() === "") {
|
|
37799
|
+
return { verdict: "undecidable", markers: [], reason: "empty diff" };
|
|
37800
|
+
}
|
|
37801
|
+
const markers = [];
|
|
37802
|
+
for (const { label, re } of DESTRUCTIVE_MARKERS) {
|
|
37803
|
+
if (re.test(patch)) {
|
|
37804
|
+
markers.push(label);
|
|
37805
|
+
}
|
|
37806
|
+
}
|
|
37807
|
+
const bump = MAJOR_BUMP.exec(patch);
|
|
37808
|
+
if (bump && bump[1] !== bump[2]) {
|
|
37809
|
+
markers.push(`major version bump ${bump[1]}.x to ${bump[2]}.x`);
|
|
37810
|
+
}
|
|
37811
|
+
const filesChanged = (patch.match(/^diff --git /gm) ?? []).length;
|
|
37812
|
+
if (markers.length > 0) {
|
|
37813
|
+
return { verdict: "destructive", markers, filesChanged };
|
|
37814
|
+
}
|
|
37815
|
+
return { verdict: "additive", markers: [], filesChanged };
|
|
37816
|
+
}
|
|
37817
|
+
function fetchPatch(pr, opts = {}) {
|
|
37818
|
+
const run6 = opts.exec ?? execFileSync2;
|
|
37819
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
37820
|
+
const capture = (file, args) => run6(file, args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 15e3 });
|
|
37821
|
+
for (const ref of [`refs/pull/${pr}/head`, `pull/${pr}/head`]) {
|
|
37822
|
+
try {
|
|
37823
|
+
const base = capture("git", ["merge-base", "HEAD", ref]).trim();
|
|
37824
|
+
if (base) {
|
|
37825
|
+
const patch = capture("git", ["diff", `${base}...${ref}`]);
|
|
37826
|
+
if (patch.trim() !== "") {
|
|
37827
|
+
return { patch, source: "local" };
|
|
37828
|
+
}
|
|
37829
|
+
}
|
|
37830
|
+
} catch {
|
|
37831
|
+
}
|
|
37832
|
+
}
|
|
37833
|
+
if (opts.allowNetwork === false) {
|
|
37834
|
+
return { patch: "", source: "none", reason: "no local ref and network reads disabled" };
|
|
37835
|
+
}
|
|
37836
|
+
try {
|
|
37837
|
+
const patch = capture("gh", ["pr", "diff", String(pr), "--patch"]);
|
|
37838
|
+
if (patch.trim() !== "") {
|
|
37839
|
+
return { patch, source: "gh" };
|
|
37840
|
+
}
|
|
37841
|
+
return { patch: "", source: "none", reason: "gh returned an empty diff" };
|
|
37842
|
+
} catch {
|
|
37843
|
+
return { patch: "", source: "none", reason: "no local ref, and gh pr diff failed" };
|
|
37844
|
+
}
|
|
37845
|
+
}
|
|
37846
|
+
function analyseMergeImpact(pr, opts = {}) {
|
|
37847
|
+
const got = fetchPatch(pr, opts);
|
|
37848
|
+
if (got.source === "none") {
|
|
37849
|
+
return { verdict: "undecidable", markers: [], ...got.reason ? { reason: got.reason } : {} };
|
|
37850
|
+
}
|
|
37851
|
+
return classifyDiff(got.patch);
|
|
37852
|
+
}
|
|
37853
|
+
function describeImpact(pr, impact) {
|
|
37854
|
+
const say = `Say "merge #${pr}" to authorize it.`;
|
|
37855
|
+
switch (impact.verdict) {
|
|
37856
|
+
case "additive":
|
|
37857
|
+
return `Impact scan: PR #${pr} touches ${impact.filesChanged ?? 0} file(s) and trips no destructive marker \u2014 no schema drop, no removed export or route, no major bump, no BREAKING CHANGE. Additive as far as the diff can show, which is not the same as safe to merge: the base has moved since review and merging may trigger a deploy. ${say}`;
|
|
37858
|
+
case "destructive":
|
|
37859
|
+
return `Impact scan: PR #${pr} is NOT additive \u2014 ${impact.markers.join("; ")}. Read the diff before authorizing. ${say}`;
|
|
37860
|
+
default:
|
|
37861
|
+
return `Impact scan: could not read the diff for PR #${pr} (${impact.reason ?? "unknown"}), so nothing is known about what it changes. ${say}`;
|
|
37862
|
+
}
|
|
37863
|
+
}
|
|
37864
|
+
var DESTRUCTIVE_MARKERS, MAJOR_BUMP;
|
|
37865
|
+
var init_merge_impact = __esm({
|
|
37866
|
+
"src/scripts/hooks/merge_impact.ts"() {
|
|
37867
|
+
"use strict";
|
|
37868
|
+
DESTRUCTIVE_MARKERS = [
|
|
37869
|
+
// Schema removal. Matched on ADDED lines only — a migration that *removes* a
|
|
37870
|
+
// dropColumn call is the opposite of destructive, and matching the raw patch
|
|
37871
|
+
// would score it the same way.
|
|
37872
|
+
{ label: "migration drops a column or table", re: /^\+.*\b(dropColumn|dropTable|dropIfExists|removeColumn|drop_column|drop_table)\b/im },
|
|
37873
|
+
{ label: "raw DROP or TRUNCATE in a migration", re: /^\+.*\b(DROP\s+(TABLE|COLUMN|INDEX|CONSTRAINT)|TRUNCATE\s+TABLE)\b/im },
|
|
37874
|
+
// The author said so themselves. Conventional Commits marks a breaking change
|
|
37875
|
+
// with a `!` before the colon or a body trailer.
|
|
37876
|
+
{ label: "author marked a BREAKING CHANGE", re: /^\+.*BREAKING[ -]CHANGE/m },
|
|
37877
|
+
// A removed export is the shape most likely to break an importer.
|
|
37878
|
+
{ label: "an exported symbol was removed", re: /^-\s*export\s+(default\s+|const\s+|function\s+|class\s+|interface\s+|type\s+|async\s+)/m },
|
|
37879
|
+
{ label: "a public PHP or Python symbol was removed", re: /^-\s*(public\s+function|def\s+[a-z_]+\s*\()/m },
|
|
37880
|
+
// Route removal breaks callers that never imported anything.
|
|
37881
|
+
{ label: "a route or endpoint was removed", re: /^-.*\b(Route::(get|post|put|patch|delete)|@(Get|Post|Put|Patch|Delete)Mapping|app\.(get|post|put|patch|delete)\s*\()/m },
|
|
37882
|
+
{ label: "a whole file was deleted", re: /^deleted file mode /m }
|
|
37883
|
+
];
|
|
37884
|
+
MAJOR_BUMP = /^-\s*"version"\s*:\s*"(\d+)\.[^"]*"[\s\S]{0,200}?^\+\s*"version"\s*:\s*"(\d+)\./m;
|
|
37885
|
+
}
|
|
37886
|
+
});
|
|
37887
|
+
|
|
37442
37888
|
// src/scripts/hooks/block_unauthorized_git.ts
|
|
37443
37889
|
import fs66 from "node:fs";
|
|
37444
37890
|
import path67 from "node:path";
|
|
@@ -37629,7 +38075,24 @@ function commandOp(command) {
|
|
|
37629
38075
|
}
|
|
37630
38076
|
return found;
|
|
37631
38077
|
}
|
|
38078
|
+
function mergeTargetOf(command) {
|
|
38079
|
+
const gh = /\bgh\s+pr\s+merge\s+(?:-{1,2}\S+\s+)*?(\d{1,7})\b/i.exec(command);
|
|
38080
|
+
if (gh) {
|
|
38081
|
+
return Number.parseInt(gh[1], 10);
|
|
38082
|
+
}
|
|
38083
|
+
const api = /\/pulls\/(\d{1,7})\/merge\b/i.exec(command);
|
|
38084
|
+
return api ? Number.parseInt(api[1], 10) : null;
|
|
38085
|
+
}
|
|
38086
|
+
function grantCovers(grants, op, target) {
|
|
38087
|
+
if (op !== "pr-merge" || target === null) {
|
|
38088
|
+
return null;
|
|
38089
|
+
}
|
|
38090
|
+
return grants.find(
|
|
38091
|
+
(g) => g.op === "pr-merge" && g.targets.includes(target) && !g.consumed.includes(target)
|
|
38092
|
+
) ?? null;
|
|
38093
|
+
}
|
|
37632
38094
|
function _loadLedger(consumer_root, session_id, now) {
|
|
38095
|
+
const grants = readGrants(consumer_root, session_id);
|
|
37633
38096
|
const candidates = [ledgerFileFor(session_id)];
|
|
37634
38097
|
if (!candidates.includes(STATE_FILE2)) {
|
|
37635
38098
|
candidates.push(STATE_FILE2);
|
|
@@ -37637,10 +38100,10 @@ function _loadLedger(consumer_root, session_id, now) {
|
|
|
37637
38100
|
for (const rel of candidates) {
|
|
37638
38101
|
const found = _readLedgerFile(path67.join(consumer_root, rel), session_id, now);
|
|
37639
38102
|
if (found !== null) {
|
|
37640
|
-
return found;
|
|
38103
|
+
return { ...found, grants };
|
|
37641
38104
|
}
|
|
37642
38105
|
}
|
|
37643
|
-
return { authorized: /* @__PURE__ */ new Set(), present: false };
|
|
38106
|
+
return { authorized: /* @__PURE__ */ new Set(), present: false, grants };
|
|
37644
38107
|
}
|
|
37645
38108
|
function _readLedgerFile(file, session_id, now) {
|
|
37646
38109
|
try {
|
|
@@ -37664,7 +38127,7 @@ function _readLedgerFile(file, session_id, now) {
|
|
|
37664
38127
|
}
|
|
37665
38128
|
return null;
|
|
37666
38129
|
}
|
|
37667
|
-
function decide(command, ledger) {
|
|
38130
|
+
function decide(command, ledger, impact) {
|
|
37668
38131
|
if (command === null) {
|
|
37669
38132
|
return { exit: EXIT_ALLOW3, stdout: "", stderr: "" };
|
|
37670
38133
|
}
|
|
@@ -37672,9 +38135,14 @@ function decide(command, ledger) {
|
|
|
37672
38135
|
if (op === null || ledger.authorized.has(op)) {
|
|
37673
38136
|
return { exit: EXIT_ALLOW3, stdout: "", stderr: "" };
|
|
37674
38137
|
}
|
|
38138
|
+
const target = mergeTargetOf(command);
|
|
38139
|
+
if (grantCovers(ledger.grants ?? [], op, target) && target !== null) {
|
|
38140
|
+
return { exit: EXIT_ALLOW3, stdout: "", stderr: "", consumedTarget: target };
|
|
38141
|
+
}
|
|
37675
38142
|
const shown = command.length > 120 ? `${command.slice(0, 117)}\u2026` : command;
|
|
37676
38143
|
if (BLOCK_OPS.has(op)) {
|
|
37677
|
-
const
|
|
38144
|
+
const scan2 = op === "pr-merge" && target !== null && impact ? ` ${impact}` : "";
|
|
38145
|
+
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}.${scan2} Ask the user, in one numbered-options block, and run it only after they answer this turn.`;
|
|
37678
38146
|
return { exit: EXIT_BLOCK, stdout: "", stderr: `${reason}
|
|
37679
38147
|
`, blockedOp: op };
|
|
37680
38148
|
}
|
|
@@ -37702,10 +38170,24 @@ function run3(stdin_text, options) {
|
|
|
37702
38170
|
}
|
|
37703
38171
|
}
|
|
37704
38172
|
const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
|
|
37705
|
-
const
|
|
37706
|
-
|
|
37707
|
-
|
|
37708
|
-
);
|
|
38173
|
+
const command = extractCommand(envelope);
|
|
38174
|
+
const ledger = _loadLedger(options.consumer_root, session_id, Date.now());
|
|
38175
|
+
let impact;
|
|
38176
|
+
const stage1 = command === null ? null : commandOp(command);
|
|
38177
|
+
const stage2Target = command === null ? null : mergeTargetOf(command);
|
|
38178
|
+
if (stage1 === "pr-merge" && stage2Target !== null && !ledger.authorized.has("pr-merge") && !grantCovers(ledger.grants ?? [], "pr-merge", stage2Target)) {
|
|
38179
|
+
try {
|
|
38180
|
+
impact = describeImpact(
|
|
38181
|
+
stage2Target,
|
|
38182
|
+
analyseMergeImpact(stage2Target, { cwd: options.consumer_root })
|
|
38183
|
+
);
|
|
38184
|
+
} catch {
|
|
38185
|
+
}
|
|
38186
|
+
}
|
|
38187
|
+
const decision = decide(command, ledger, impact);
|
|
38188
|
+
if (decision.consumedTarget !== void 0) {
|
|
38189
|
+
consumeGrantTarget(options.consumer_root, session_id, decision.consumedTarget);
|
|
38190
|
+
}
|
|
37709
38191
|
if (decision.blockedOp) {
|
|
37710
38192
|
try {
|
|
37711
38193
|
atomic_write_json(path67.join(options.consumer_root, pendingFileFor(session_id)), {
|
|
@@ -37762,6 +38244,7 @@ var init_block_unauthorized_git = __esm({
|
|
|
37762
38244
|
"src/scripts/hooks/block_unauthorized_git.ts"() {
|
|
37763
38245
|
"use strict";
|
|
37764
38246
|
init_hook_stdin();
|
|
38247
|
+
init_merge_impact();
|
|
37765
38248
|
init_state_io();
|
|
37766
38249
|
init_git_authorization_hook();
|
|
37767
38250
|
EXIT_ALLOW3 = 0;
|
|
@@ -37773,13 +38256,52 @@ var init_block_unauthorized_git = __esm({
|
|
|
37773
38256
|
"pr-merge",
|
|
37774
38257
|
// Enabling auto-merge commits the outcome to a condition the agent does not
|
|
37775
38258
|
// control, so it is blocked on the same terms as the merge it schedules.
|
|
37776
|
-
"pr-merge-auto"
|
|
38259
|
+
"pr-merge-auto",
|
|
38260
|
+
// ── Added 2026-09-03. Every one is already never-autonomous in the
|
|
38261
|
+
// `non-destructive-by-default` prose; the measurement that drove it is in the
|
|
38262
|
+
// `GitOp` union's own note — 17 of 25 borderline operations classified as
|
|
38263
|
+
// NOTHING, including three worse than anything the guard already caught.
|
|
38264
|
+
/** The undo that is more destructive than the act: lockfiles break. */
|
|
38265
|
+
"unpublish",
|
|
38266
|
+
/** Metadata only, and every install on earth starts warning. */
|
|
38267
|
+
"deprecate",
|
|
38268
|
+
/** Replaces bytes consumers already hold a checksum for. */
|
|
38269
|
+
"release-asset",
|
|
38270
|
+
/** Deletes the guard rather than the data — everything after is unguarded. */
|
|
38271
|
+
"protection",
|
|
38272
|
+
/** No data lost; the gate that protects everything downstream is off. */
|
|
38273
|
+
"workflow-toggle",
|
|
38274
|
+
/** Reach outside the repository, and mostly one-way. */
|
|
38275
|
+
"repo-lifecycle",
|
|
38276
|
+
/** No code moves, and an armed auto-merge is released. */
|
|
38277
|
+
"review-approve",
|
|
38278
|
+
/** Discards commits that landed after the last fetch, on a shared ref. */
|
|
38279
|
+
"force-push",
|
|
38280
|
+
/** Destroys uncommitted work belonging to a session this one cannot see. */
|
|
38281
|
+
"worktree-remove",
|
|
38282
|
+
/** `-x` is the flag that takes `.env` and local certificates. */
|
|
38283
|
+
"clean-ignored"
|
|
37777
38284
|
]);
|
|
37778
38285
|
WARN_OPS = /* @__PURE__ */ new Set([
|
|
37779
38286
|
"commit",
|
|
37780
38287
|
"push",
|
|
37781
38288
|
"pr-create",
|
|
37782
|
-
"branch"
|
|
38289
|
+
"branch",
|
|
38290
|
+
// ── Added 2026-09-03, deliberately at warn rather than block.
|
|
38291
|
+
//
|
|
38292
|
+
// Each is destructive in some reading, and each is also routine enough that
|
|
38293
|
+
// blocking it would be the friction that gets the whole guard routed around —
|
|
38294
|
+
// the failure mode this estate has already recorded twice. `reset-hard` is the
|
|
38295
|
+
// sharpest case and stays here for a decidable reason: the Hard Floor's
|
|
38296
|
+
// qualifier is "past unpushed work", and the command text cannot say whether
|
|
38297
|
+
// the work was pushed.
|
|
38298
|
+
"tag-force",
|
|
38299
|
+
"rebase",
|
|
38300
|
+
"reset-hard",
|
|
38301
|
+
"clean",
|
|
38302
|
+
"stash-drop",
|
|
38303
|
+
"branch-delete",
|
|
38304
|
+
"close"
|
|
37783
38305
|
]);
|
|
37784
38306
|
G = "(?:-[A-Za-z-]+(?:=\\S+)?(?:\\s+\\S+)?\\s+)*";
|
|
37785
38307
|
P = "^(?:\\S*\\/)?";
|
|
@@ -37819,6 +38341,72 @@ var init_block_unauthorized_git = __esm({
|
|
|
37819
38341
|
op: "pr-create",
|
|
37820
38342
|
re: new RegExp(`${P}gh\\s+pr\\s+create\\b|${ghApiWrite("\\/pulls\\b").source}`, "i")
|
|
37821
38343
|
},
|
|
38344
|
+
// ── Added 2026-09-03. Placement inside this table is as load-bearing as the
|
|
38345
|
+
// patterns: the loop takes the MOST SEVERE match per segment, but within one
|
|
38346
|
+
// op the first pattern wins, so anything more specific than a plain `git push`
|
|
38347
|
+
// has to sit above it.
|
|
38348
|
+
// `npm unpublish` is NOT reached by the publish pattern — `\bpublish\b` needs
|
|
38349
|
+
// a word boundary that `unpublish` does not provide between `n` and `p`. That
|
|
38350
|
+
// is why it classified as nothing for as long as it did.
|
|
38351
|
+
{ op: "unpublish", re: new RegExp(`${P}(npm|pnpm|yarn)\\s+${G}unpublish\\b`, "i") },
|
|
38352
|
+
{ op: "deprecate", re: new RegExp(`${P}(npm|pnpm|yarn)\\s+${G}deprecate\\b`, "i") },
|
|
38353
|
+
{
|
|
38354
|
+
op: "release-asset",
|
|
38355
|
+
re: new RegExp(`${P}gh\\s+release\\s+(upload|edit|delete)\\b`, "i")
|
|
38356
|
+
},
|
|
38357
|
+
{
|
|
38358
|
+
op: "protection",
|
|
38359
|
+
re: new RegExp(
|
|
38360
|
+
`${P}gh\\s+api\\b(?=[^\\n]*(?:protection|rulesets?)\\b)(?=[^\\n]*(?:-X|--method)\\s+(?:DELETE|PUT|PATCH|POST)\\b)|${P}gh\\s+ruleset\\s+delete\\b`,
|
|
38361
|
+
"i"
|
|
38362
|
+
)
|
|
38363
|
+
},
|
|
38364
|
+
{
|
|
38365
|
+
op: "workflow-toggle",
|
|
38366
|
+
re: new RegExp(`${P}gh\\s+workflow\\s+(disable|enable)\\b`, "i")
|
|
38367
|
+
},
|
|
38368
|
+
{
|
|
38369
|
+
op: "repo-lifecycle",
|
|
38370
|
+
re: new RegExp(
|
|
38371
|
+
`${P}gh\\s+repo\\s+(archive|delete|unarchive)\\b|${P}gh\\s+repo\\s+edit\\b(?=[^\\n]*--visibility\\b)`,
|
|
38372
|
+
"i"
|
|
38373
|
+
)
|
|
38374
|
+
},
|
|
38375
|
+
{
|
|
38376
|
+
op: "review-approve",
|
|
38377
|
+
re: new RegExp(`${P}gh\\s+pr\\s+review\\b(?=[^\\n]*--approve\\b)`, "i")
|
|
38378
|
+
},
|
|
38379
|
+
// Above `push`, and both spellings: `--force` and `--force-with-lease`. The
|
|
38380
|
+
// "safe force" is in the same class here because what it is safe against is a
|
|
38381
|
+
// stale local view, not a collaborator who pushed after your fetch.
|
|
38382
|
+
{
|
|
38383
|
+
op: "force-push",
|
|
38384
|
+
re: new RegExp(`${P}git\\s+${G}push\\b[^\\n;|&]*(--force\\b|--force-with-lease\\b|\\s-f\\b)`, "i")
|
|
38385
|
+
},
|
|
38386
|
+
{
|
|
38387
|
+
op: "worktree-remove",
|
|
38388
|
+
re: new RegExp(`${P}git\\s+worktree\\s+remove\\b[^\\n;|&]*(--force\\b|\\s-f\\b)`, "i")
|
|
38389
|
+
},
|
|
38390
|
+
// `-x` (or `-X`) anywhere in the flag cluster is what reaches ignored files.
|
|
38391
|
+
{
|
|
38392
|
+
op: "clean-ignored",
|
|
38393
|
+
re: new RegExp(`${P}git\\s+clean\\b[^\\n;|&]*-[A-Za-z]*x`, "i")
|
|
38394
|
+
},
|
|
38395
|
+
{ op: "tag-force", re: new RegExp(`${P}git\\s+tag\\b[^\\n;|&]*(-f\\b|--force\\b)`, "i") },
|
|
38396
|
+
{ op: "rebase", re: new RegExp(`${P}git\\s+${G}rebase\\b`, "i") },
|
|
38397
|
+
{ op: "reset-hard", re: new RegExp(`${P}git\\s+${G}reset\\b[^\\n;|&]*--hard\\b`, "i") },
|
|
38398
|
+
{ op: "clean", re: new RegExp(`${P}git\\s+clean\\b`, "i") },
|
|
38399
|
+
{ op: "stash-drop", re: new RegExp(`${P}git\\s+stash\\s+(drop|clear)\\b`, "i") },
|
|
38400
|
+
// Remote deletion and the local `-D` are one op: both remove a ref, and the
|
|
38401
|
+
// remote form additionally closes any open PR pointing at it.
|
|
38402
|
+
{
|
|
38403
|
+
op: "branch-delete",
|
|
38404
|
+
re: new RegExp(
|
|
38405
|
+
`${P}git\\s+branch\\b[^\\n;|&]*(-D\\b|--delete\\b)|${P}git\\s+${G}push\\b[^\\n;|&]*(--delete\\b|\\s:\\S)`,
|
|
38406
|
+
"i"
|
|
38407
|
+
)
|
|
38408
|
+
},
|
|
38409
|
+
{ op: "close", re: new RegExp(`${P}gh\\s+(pr|issue)\\s+close\\b`, "i") },
|
|
37822
38410
|
{ op: "push", re: new RegExp(`${P}git\\s+${G}push\\b`, "i") },
|
|
37823
38411
|
{ op: "commit", re: new RegExp(`${P}git\\s+${G}commit\\b`, "i") },
|
|
37824
38412
|
{ op: "branch", re: new RegExp(`${P}git\\s+${G}(checkout\\s+-b|switch\\s+-c)\\b`, "i") }
|
|
@@ -42161,11 +42749,11 @@ import * as path80 from "node:path";
|
|
|
42161
42749
|
import process21 from "node:process";
|
|
42162
42750
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
42163
42751
|
import { fileURLToPath as fileURLToPath46, pathToFileURL as pathToFileURL36 } from "node:url";
|
|
42164
|
-
function
|
|
42752
|
+
function print3(s = "") {
|
|
42165
42753
|
process21.stdout.write(`${s}
|
|
42166
42754
|
`);
|
|
42167
42755
|
}
|
|
42168
|
-
function
|
|
42756
|
+
function eprint3(s = "") {
|
|
42169
42757
|
process21.stderr.write(`${s}
|
|
42170
42758
|
`);
|
|
42171
42759
|
}
|
|
@@ -42549,14 +43137,14 @@ function _parse4(argv) {
|
|
|
42549
43137
|
function main36(argv = null) {
|
|
42550
43138
|
const opts = _parse4(argv !== null ? Array.from(argv) : process21.argv.slice(2));
|
|
42551
43139
|
if (opts.funnel) {
|
|
42552
|
-
|
|
43140
|
+
print3(render6(buildFunnel()));
|
|
42553
43141
|
return 0;
|
|
42554
43142
|
}
|
|
42555
43143
|
let project_root;
|
|
42556
43144
|
try {
|
|
42557
43145
|
[project_root] = _resolve_project_root(opts.project);
|
|
42558
43146
|
} catch (exc) {
|
|
42559
|
-
|
|
43147
|
+
eprint3(`\u274C conformance: ${exc.message}`);
|
|
42560
43148
|
return 2;
|
|
42561
43149
|
}
|
|
42562
43150
|
const manifest_pth = manifest_path(project_root);
|
|
@@ -42585,7 +43173,7 @@ function main36(argv = null) {
|
|
|
42585
43173
|
const failed = allChecks.some((c) => c["status"] === "fail") || driftCount > 0;
|
|
42586
43174
|
appendConformanceReport(conformanceChecks, project_root);
|
|
42587
43175
|
if (opts.json) {
|
|
42588
|
-
|
|
43176
|
+
print3(
|
|
42589
43177
|
_jsonDumpsIndentAscii(
|
|
42590
43178
|
{
|
|
42591
43179
|
project_root,
|
|
@@ -42598,20 +43186,20 @@ function main36(argv = null) {
|
|
|
42598
43186
|
)
|
|
42599
43187
|
);
|
|
42600
43188
|
} else {
|
|
42601
|
-
|
|
42602
|
-
|
|
43189
|
+
print3(` \u{1F4CD} project_root: ${project_root}`);
|
|
43190
|
+
print3("conformance:");
|
|
42603
43191
|
for (const c of allChecks) {
|
|
42604
43192
|
const sym = STATUS_SYMBOLS[c["status"]] ?? "?";
|
|
42605
|
-
|
|
43193
|
+
print3(` ${sym} ${c["id"]}: ${c["message"]}`);
|
|
42606
43194
|
if (c["status"] !== "ok" && c["remedy"]) {
|
|
42607
|
-
|
|
43195
|
+
print3(` fix: ${c["remedy"]}`);
|
|
42608
43196
|
}
|
|
42609
43197
|
}
|
|
42610
43198
|
if (driftCount > 0) {
|
|
42611
|
-
|
|
43199
|
+
print3(` \u274C drift: ${driftCount} drifted file(s) \u2014 run \`agent-config doctor\` for detail`);
|
|
42612
43200
|
}
|
|
42613
|
-
|
|
42614
|
-
|
|
43201
|
+
print3("");
|
|
43202
|
+
print3(
|
|
42615
43203
|
failed ? "\u274C conformance: red \u2014 the OS is not fully installed/firing here" : "\u2705 conformance: green \u2014 installed and firing"
|
|
42616
43204
|
);
|
|
42617
43205
|
}
|