@event4u/agent-config 9.33.0 → 9.34.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 +85 -167
- package/dist/agent-src/commands/agent-handoff.md +15 -3
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +35 -1
- package/dist/agent-src/skills/corpus-grounding/SKILL.md +21 -12
- package/dist/agent-src/skills/design-tokens/SKILL.md +15 -0
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +24 -0
- package/dist/agent-src/skills/rule-writing/SKILL.md +23 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +80 -2
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +5 -3
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +24 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/{chunk-2WELHO3P.js → chunk-I5EAFRII.js} +35 -0
- package/dist/cli-delegate/{chunk-PMQ6Z3T3.js → chunk-REMEZSVP.js} +9 -2
- package/dist/cli-delegate/{chunk-3EWYC3YK.js → chunk-UEZ7D35A.js} +4 -3
- package/dist/cli-delegate/cmd_conformance.js +1 -1
- package/dist/cli-delegate/cmd_doctor.js +1 -1
- package/dist/cli-delegate/cmd_handoff.js +2 -2
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +7 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +11 -11
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +2 -2
- package/dist/discovery/dormancy-report.md +17 -0
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +3 -3
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +3 -3
- package/dist/hooks/dispatch.js +96 -7
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +118 -10
- package/docs/guidelines/agent-infra/failure-signatures.md +47 -12
- package/package.json +2 -2
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +35 -1
- package/src/config/quorum-attendance-budget.json +1 -1
- package/src/scripts/_cli/cmd_session_recycle.ts +6 -1
- package/src/scripts/_lib/env_kill_switch.ts +37 -0
- package/src/scripts/_lib/security_lint.ts +18 -4
- package/src/scripts/_lib/self_repair.ts +60 -0
- package/src/scripts/_lib/self_repair_store.ts +65 -5
- package/src/scripts/_lib/subagent_capsule.ts +78 -1
- package/src/scripts/ai_council/events_log.ts +2 -2
- package/src/scripts/ai_council/orchestrator.ts +38 -0
- package/src/scripts/ai_council/prompts.ts +165 -3
- package/src/scripts/ai_council/quorum.ts +10 -0
- package/src/scripts/ai_team/review_gate.ts +2 -2
- package/src/scripts/build_discovery_manifest.ts +165 -0
- package/src/scripts/check_claims.ts +51 -2
- package/src/scripts/ci_time_ratio.ts +3 -1
- package/src/scripts/discovery_graph.ts +201 -27
- package/src/scripts/dispatch_r2_reviewer.ts +102 -1
- package/src/scripts/lint_handoffs.ts +92 -2
- package/src/scripts/lint_hedge_words.ts +317 -0
- package/src/scripts/lint_mcp_config_security.ts +1 -1
- package/src/scripts/lint_skill_frontmatter_safety.ts +1 -1
- package/src/scripts/self_repair_cli.ts +26 -1
- package/src/scripts/skill_linter.ts +58 -2
package/dist/hooks/dispatch.js
CHANGED
|
@@ -7993,7 +7993,7 @@ function* iter_corpus(roots = DEFAULT_SCAN_ROOTS, exts = [".md"]) {
|
|
|
7993
7993
|
function report(findings, opts) {
|
|
7994
7994
|
const check_label = opts.check_label;
|
|
7995
7995
|
if (findings.length === 0) {
|
|
7996
|
-
process.stdout.write(`\u2705 ${check_label}: clean (${_corpus_note()}).
|
|
7996
|
+
process.stdout.write(`\u2705 ${check_label}: clean (${_corpus_note(opts.scanned_roots)}).
|
|
7997
7997
|
`);
|
|
7998
7998
|
return 0;
|
|
7999
7999
|
}
|
|
@@ -8033,8 +8033,9 @@ function _pyG(n) {
|
|
|
8033
8033
|
}
|
|
8034
8034
|
return s;
|
|
8035
8035
|
}
|
|
8036
|
-
function _corpus_note() {
|
|
8037
|
-
|
|
8036
|
+
function _corpus_note(scanned_roots) {
|
|
8037
|
+
const roots = scanned_roots && scanned_roots.length > 0 ? scanned_roots : DEFAULT_SCAN_ROOTS;
|
|
8038
|
+
return "scanned " + roots.join(", ");
|
|
8038
8039
|
}
|
|
8039
8040
|
var PyFloat = class {
|
|
8040
8041
|
constructor(value) {
|
|
@@ -16205,6 +16206,38 @@ function hasBoundaryMarker(block) {
|
|
|
16205
16206
|
// outside the datamarking while still riding in the same block.
|
|
16206
16207
|
block.trimEnd().endsWith(ENVELOPE_BOUNDARY_CLOSE);
|
|
16207
16208
|
}
|
|
16209
|
+
var DECISION_REVERSIBILITY_TAGS = ["reversible", "irreversible"];
|
|
16210
|
+
function decisionTagErrors(decisions) {
|
|
16211
|
+
if (!Array.isArray(decisions)) {
|
|
16212
|
+
return [];
|
|
16213
|
+
}
|
|
16214
|
+
const errors = [];
|
|
16215
|
+
for (const line of decisions) {
|
|
16216
|
+
if (typeof line !== "string") {
|
|
16217
|
+
continue;
|
|
16218
|
+
}
|
|
16219
|
+
const m = /\[([^\]]*)\]\s*$/.exec(line.trimEnd());
|
|
16220
|
+
if (m === null) {
|
|
16221
|
+
continue;
|
|
16222
|
+
}
|
|
16223
|
+
const inner = (m[1] ?? "").trim();
|
|
16224
|
+
const lowered = inner.toLowerCase();
|
|
16225
|
+
if (DECISION_REVERSIBILITY_TAGS.includes(lowered)) {
|
|
16226
|
+
if (inner !== lowered) {
|
|
16227
|
+
errors.push(
|
|
16228
|
+
`decisions: reversibility tag must be lower-case \u2014 got \`[${inner}]\`, expected \`[${lowered}]\``
|
|
16229
|
+
);
|
|
16230
|
+
}
|
|
16231
|
+
continue;
|
|
16232
|
+
}
|
|
16233
|
+
if (/^i?rr?ever[a-z]*[^a-z]*$/i.test(lowered)) {
|
|
16234
|
+
errors.push(
|
|
16235
|
+
`decisions: \`[${inner}]\` is not a reversibility tag \u2014 use exactly ` + DECISION_REVERSIBILITY_TAGS.map((t) => `\`[${t}]\``).join(" or ") + ", or drop the bracket"
|
|
16236
|
+
);
|
|
16237
|
+
}
|
|
16238
|
+
}
|
|
16239
|
+
return errors;
|
|
16240
|
+
}
|
|
16208
16241
|
var RECYCLE_ENVELOPE_KEYS = /* @__PURE__ */ new Set([
|
|
16209
16242
|
"capsule_version",
|
|
16210
16243
|
"variant",
|
|
@@ -16217,6 +16250,7 @@ var RECYCLE_ENVELOPE_KEYS = /* @__PURE__ */ new Set([
|
|
|
16217
16250
|
"not_carried_forward",
|
|
16218
16251
|
"decisions",
|
|
16219
16252
|
"constraints",
|
|
16253
|
+
"do_not_touch",
|
|
16220
16254
|
"open_worker_envelopes",
|
|
16221
16255
|
"artifact_paths",
|
|
16222
16256
|
"assumptions",
|
|
@@ -16269,7 +16303,9 @@ function validateRecycleEnvelope(input) {
|
|
|
16269
16303
|
checkList(errors, "remaining", e["remaining"], MAX_LINE_CHARS, true);
|
|
16270
16304
|
checkList(errors, "not_carried_forward", e["not_carried_forward"], MAX_LINE_CHARS, true);
|
|
16271
16305
|
checkList(errors, "decisions", e["decisions"], MAX_LINE_CHARS, false);
|
|
16306
|
+
errors.push(...decisionTagErrors(e["decisions"]));
|
|
16272
16307
|
checkList(errors, "constraints", e["constraints"], MAX_LINE_CHARS, false);
|
|
16308
|
+
checkList(errors, "do_not_touch", e["do_not_touch"], MAX_REF_CHARS, false);
|
|
16273
16309
|
checkList(errors, "open_worker_envelopes", e["open_worker_envelopes"], MAX_REF_CHARS, false);
|
|
16274
16310
|
checkList(errors, "artifact_paths", e["artifact_paths"], MAX_REF_CHARS, false);
|
|
16275
16311
|
checkList(errors, "suggested_skills", e["suggested_skills"], MAX_REF_CHARS, false);
|
|
@@ -18839,6 +18875,12 @@ function claude_config_dir() {
|
|
|
18839
18875
|
return env !== "" ? env : path32.join(os4.homedir(), ".claude");
|
|
18840
18876
|
}
|
|
18841
18877
|
|
|
18878
|
+
// src/scripts/_lib/env_kill_switch.ts
|
|
18879
|
+
var _OFF_VALUES = /* @__PURE__ */ new Set(["", "0", "false", "False"]);
|
|
18880
|
+
function isEnvKillSwitchActive(name) {
|
|
18881
|
+
return !_OFF_VALUES.has(process.env[name] ?? "");
|
|
18882
|
+
}
|
|
18883
|
+
|
|
18842
18884
|
// src/scripts/ai_team/config.ts
|
|
18843
18885
|
var TeamConfigError = class extends Error {
|
|
18844
18886
|
constructor(message) {
|
|
@@ -19048,8 +19090,7 @@ function circuit_breaker_notice(bound) {
|
|
|
19048
19090
|
return `\u26A0\uFE0F team review-gate circuit breaker: ${bound} consecutive BLOCK verdict(s) this session (bound: \`ai_team.review_gate.max_consecutive_blocks\`). The managed gate stops re-blocking now \u2014 you decide: review the last BLOCK reason via \`/team:status\`, fix what is real, ignore what is not. An ALLOW verdict resets the counter.`;
|
|
19049
19091
|
}
|
|
19050
19092
|
function _kill_switch_active() {
|
|
19051
|
-
|
|
19052
|
-
return !(value === "" || value === "0" || value === "false" || value === "False");
|
|
19093
|
+
return isEnvKillSwitchActive(_KILL_SWITCH_ENV);
|
|
19053
19094
|
}
|
|
19054
19095
|
function _iso_seconds_z(d) {
|
|
19055
19096
|
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
@@ -19930,6 +19971,28 @@ function fingerprint2(defect_class, evidence) {
|
|
|
19930
19971
|
const shape = evidence.toLowerCase().replace(/\d+/g, "#").replace(/[`"'‚'„"»«]/g, "").replace(/[^a-zà-ÿ#\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
19931
19972
|
return createHash4("sha256").update(`${defect_class}|${shape}`).digest("hex").slice(0, 16);
|
|
19932
19973
|
}
|
|
19974
|
+
var NEW_RECORDS_PER_SOURCE_PER_WINDOW = 20;
|
|
19975
|
+
var CREATION_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
19976
|
+
function recentCreations(existing, source, now) {
|
|
19977
|
+
const end = Date.parse(now);
|
|
19978
|
+
if (Number.isNaN(end)) {
|
|
19979
|
+
return 0;
|
|
19980
|
+
}
|
|
19981
|
+
return existing.filter((r) => {
|
|
19982
|
+
if (r.source !== source) {
|
|
19983
|
+
return false;
|
|
19984
|
+
}
|
|
19985
|
+
const t = Date.parse(r.first_seen);
|
|
19986
|
+
return Number.isNaN(t) || end - t < CREATION_WINDOW_MS && end - t >= 0;
|
|
19987
|
+
}).length;
|
|
19988
|
+
}
|
|
19989
|
+
function creationCapReached(existing, finding, now) {
|
|
19990
|
+
const fp = fingerprint2(finding.defect_class, finding.evidence);
|
|
19991
|
+
if (existing.some((r) => r.fingerprint === fp)) {
|
|
19992
|
+
return false;
|
|
19993
|
+
}
|
|
19994
|
+
return recentCreations(existing, finding.source, now) >= NEW_RECORDS_PER_SOURCE_PER_WINDOW;
|
|
19995
|
+
}
|
|
19933
19996
|
function mergeRecord(existing, finding, now) {
|
|
19934
19997
|
const fp = fingerprint2(finding.defect_class, finding.evidence);
|
|
19935
19998
|
if (existing !== null && existing.fingerprint === fp) {
|
|
@@ -19995,7 +20058,7 @@ function listRecords(root) {
|
|
|
19995
20058
|
}
|
|
19996
20059
|
const out = [];
|
|
19997
20060
|
for (const n of names.sort()) {
|
|
19998
|
-
if (!n.endsWith(".json")) {
|
|
20061
|
+
if (!n.endsWith(".json") || n === OVERFLOW_FILE) {
|
|
19999
20062
|
continue;
|
|
20000
20063
|
}
|
|
20001
20064
|
const rec = readRecord(root, n.slice(0, -".json".length));
|
|
@@ -20009,9 +20072,35 @@ function listRecords(root) {
|
|
|
20009
20072
|
function openRecords(root) {
|
|
20010
20073
|
return listRecords(root).filter((r) => r.status === "open");
|
|
20011
20074
|
}
|
|
20075
|
+
var OVERFLOW_FILE = "_overflow.json";
|
|
20076
|
+
function overflowPath(root) {
|
|
20077
|
+
return path38.join(storeDir(root), OVERFLOW_FILE);
|
|
20078
|
+
}
|
|
20079
|
+
function readOverflow(root) {
|
|
20080
|
+
try {
|
|
20081
|
+
const parsed = JSON.parse(fs37.readFileSync(overflowPath(root), "utf-8"));
|
|
20082
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
20083
|
+
} catch {
|
|
20084
|
+
return {};
|
|
20085
|
+
}
|
|
20086
|
+
}
|
|
20087
|
+
function bumpOverflow(root, source, now) {
|
|
20088
|
+
const counts = readOverflow(root);
|
|
20089
|
+
const prev = counts[source]?.dropped ?? 0;
|
|
20090
|
+
counts[source] = { dropped: prev + 1, last_seen: now };
|
|
20091
|
+
const dir = storeDir(root);
|
|
20092
|
+
fs37.mkdirSync(dir, { recursive: true });
|
|
20093
|
+
fs37.writeFileSync(overflowPath(root), `${JSON.stringify(counts, null, 2)}
|
|
20094
|
+
`, "utf-8");
|
|
20095
|
+
}
|
|
20012
20096
|
function upsertFinding(root, finding, now) {
|
|
20013
20097
|
const fp = fingerprint2(finding.defect_class, finding.evidence);
|
|
20014
|
-
const
|
|
20098
|
+
const existing = readRecord(root, fp);
|
|
20099
|
+
if (existing === null && creationCapReached(listRecords(root), finding, now)) {
|
|
20100
|
+
bumpOverflow(root, finding.source, now);
|
|
20101
|
+
return null;
|
|
20102
|
+
}
|
|
20103
|
+
const merged = mergeRecord(existing, finding, now);
|
|
20015
20104
|
writeRecord(root, merged);
|
|
20016
20105
|
return merged;
|
|
20017
20106
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"discovery": {
|
|
3
3
|
"artefact_count": 603,
|
|
4
|
-
"scanner_version": "
|
|
4
|
+
"scanner_version": "1eaf68b6e9c9"
|
|
5
5
|
},
|
|
6
6
|
"generated_at": "2026-08-11",
|
|
7
7
|
"package": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"homepage": "https://event4u-app.github.io/agent-config",
|
|
10
10
|
"name": "@event4u/agent-config",
|
|
11
11
|
"repository": "https://github.com/event4u-app/agent-config",
|
|
12
|
-
"version": "9.
|
|
12
|
+
"version": "9.34.0"
|
|
13
13
|
},
|
|
14
14
|
"registries": [
|
|
15
15
|
{
|
package/dist/mcp/server.json
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"transport": {
|
|
10
10
|
"type": "stdio"
|
|
11
11
|
},
|
|
12
|
-
"version": "9.
|
|
12
|
+
"version": "9.34.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": "9.
|
|
19
|
+
"version": "9.34.0"
|
|
20
20
|
}
|
package/dist/mcp/server.mjs
CHANGED
|
@@ -9241,7 +9241,7 @@ function* iter_corpus(roots = DEFAULT_SCAN_ROOTS, exts = [".md"]) {
|
|
|
9241
9241
|
function report(findings, opts) {
|
|
9242
9242
|
const check_label = opts.check_label;
|
|
9243
9243
|
if (findings.length === 0) {
|
|
9244
|
-
process.stdout.write(`\u2705 ${check_label}: clean (${_corpus_note()}).
|
|
9244
|
+
process.stdout.write(`\u2705 ${check_label}: clean (${_corpus_note(opts.scanned_roots)}).
|
|
9245
9245
|
`);
|
|
9246
9246
|
return 0;
|
|
9247
9247
|
}
|
|
@@ -9281,8 +9281,9 @@ function _pyG(n) {
|
|
|
9281
9281
|
}
|
|
9282
9282
|
return s;
|
|
9283
9283
|
}
|
|
9284
|
-
function _corpus_note() {
|
|
9285
|
-
|
|
9284
|
+
function _corpus_note(scanned_roots) {
|
|
9285
|
+
const roots = scanned_roots && scanned_roots.length > 0 ? scanned_roots : DEFAULT_SCAN_ROOTS;
|
|
9286
|
+
return "scanned " + roots.join(", ");
|
|
9286
9287
|
}
|
|
9287
9288
|
function _isPlainObject(v) {
|
|
9288
9289
|
return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof PyFloat);
|
|
@@ -15361,7 +15362,7 @@ function lint_execution_metadata(execution) {
|
|
|
15361
15362
|
}
|
|
15362
15363
|
return issues;
|
|
15363
15364
|
}
|
|
15364
|
-
function lint_skill(p, text) {
|
|
15365
|
+
function lint_skill(p, text, absPath = null) {
|
|
15365
15366
|
const issues = [];
|
|
15366
15367
|
const suggestions = [];
|
|
15367
15368
|
const sections = extract_sections(text);
|
|
@@ -15371,6 +15372,18 @@ function lint_skill(p, text) {
|
|
|
15371
15372
|
issues.push(new Issue("error", "missing_section", `Missing required section: ${section}`));
|
|
15372
15373
|
}
|
|
15373
15374
|
}
|
|
15375
|
+
const skillDir = absPath !== null ? path19.dirname(absPath) : path19.isAbsolute(p) ? path19.dirname(p) : null;
|
|
15376
|
+
for (const { section, when, reason } of CONDITIONAL_SKILL_SECTIONS) {
|
|
15377
|
+
if (skillDir !== null && when(skillDir) && !sectionMatches(section, sections)) {
|
|
15378
|
+
issues.push(
|
|
15379
|
+
new Issue(
|
|
15380
|
+
"error",
|
|
15381
|
+
"missing_conditional_section",
|
|
15382
|
+
`Missing required section: ${section} (${reason})`
|
|
15383
|
+
)
|
|
15384
|
+
);
|
|
15385
|
+
}
|
|
15386
|
+
}
|
|
15374
15387
|
for (const section of RECOMMENDED_SKILL_SECTIONS) {
|
|
15375
15388
|
if (!sectionMatches(section, sections)) {
|
|
15376
15389
|
issues.push(
|
|
@@ -17173,7 +17186,7 @@ function lint_file(p, repoRoot = null) {
|
|
|
17173
17186
|
}
|
|
17174
17187
|
let result;
|
|
17175
17188
|
if (artifactType === "skill") {
|
|
17176
|
-
result = lint_skill(displayPath, text);
|
|
17189
|
+
result = lint_skill(displayPath, text, p);
|
|
17177
17190
|
} else if (artifactType === "rule") {
|
|
17178
17191
|
result = lint_rule(displayPath, text);
|
|
17179
17192
|
} else if (artifactType === "command") {
|
|
@@ -17998,7 +18011,7 @@ function _isCliEntry7() {
|
|
|
17998
18011
|
return false;
|
|
17999
18012
|
}
|
|
18000
18013
|
}
|
|
18001
|
-
var _HERE4, REQUIRED_PERSONA_SECTIONS_CORE, REQUIRED_PERSONA_SECTIONS_SPECIALIST, VALID_PERSONA_TIERS, PERSONA_LINE_BUDGETS, REQUIRED_USERTYPE_SECTIONS, USERTYPE_LINE_BUDGET, VALID_PERSONA_WINGS, PERSONA_LINE_BUDGETS_BY_WING, REQUIRED_SKILL_SECTIONS, SECTION_ALIASES, RECOMMENDED_SKILL_SECTIONS, RULE_BAD_SIGNS, FRUGALITY_WRITER_SKILLS, FRUGALITY_CHARTER_RELPATH, FRUGALITY_CHARTER_INDEX_RULES, VAGUE_VALIDATION_PATTERNS, TRIGGER_WARNING_PATTERNS, ORDERED_STEP_PATTERN, SECTION_PATTERN, FRONTMATTER_PATTERN, DESCRIPTION_PATTERN, STATUS_PATTERN, REPLACED_BY_PATTERN, TIER_PATTERN, TYPE_PATTERN, SOURCE_PATTERN, SENIOR_RELATED_SKILLS_PATTERN, SENIOR_RELATED_WHEN_PATTERN, SENIOR_RELATED_WHEN_NOT_PATTERN, SENIOR_PROACTIVE_PATTERN, SENIOR_OUTPUT_PATTERN, H1_PATTERN, DOUBLE_BLANK_PATTERN, VALID_RULE_TYPES, VALID_RULE_SOURCES, ROUTER_ALLOWED_TRIGGER_KEYS, KERNEL_RULE_IDS2, VALID_EXECUTION_TYPES, VALID_EXECUTION_HANDLERS, VALID_EXECUTION_SAFETY_MODES, VALID_EXECUTION_FIELDS, WING3_SPINE_SLOTS, CONTEXT_SPINE_INLINE_PATTERN, WING3_SAAS_URL_PATTERN, WING3_VENDOR_BLACKLIST, WING3_STACK_LOCKED_PATTERN, WING3_CHANNEL_TACTIC_PATTERN, WING4_SPINE_SLOTS, WING4_SAAS_URL_PATTERN, WING4_VENDOR_BLACKLIST, WING4_STAGE_AGNOSTIC_PATTERN, Issue, LintResult, _ROLE_CONTRACT_CANDIDATES, _ROLE_CONTRACT_SLUGS_CACHE, _ROLE_CONTRACT_REF_PATTERN, PROCEDURE_HEADING_PATTERN, COMMAND_FRONTMATTER_DELEGATION_KEYS, MD_LINK_PATTERN_G, NAME_PATTERN, DISABLE_MODEL_PATTERN, _INSPECT_VERB_PATTERN, last_changed_scope_reason, last_changed_base_diff_ok, _INTERACTION_NAME_PATTERNS, _INTERACTION_CONTENT_KEYWORDS, _EXEC_FILE_SIGNALS, _EXEC_CONTENT_SIGNALS, _TASK_TYPE_SIGNALS, _VERIFICATION_TOOLS, _FRUGALITY_STANDARDS_PATTERN, _FRUGALITY_CHARTER_LINK_PATTERN, _MD_LINK_PATTERN, _MALICE_CRED_EXFIL, _MALICE_REMOTE_EXEC, _MALICE_FORCE_PUSH, _MALICE_CHMOD_SECRETS, _MALICE_SHELL_INJECT, _MALICE_PATTERNS, _NAG_UPDATE_CHECK, _NAG_STAY_CURRENT, _NAG_SELF_PROMO, _NAG_BEST_SKILL, _NAG_MUST_USE, _NAG_ALWAYS_USE, _NAG_PATTERNS, _OUTPUT_SCHEMA_KEY_PATTERN, _SCHEMA_ARTEFACT_TYPES, ArgError, isCliEntry2;
|
|
18014
|
+
var _HERE4, REQUIRED_PERSONA_SECTIONS_CORE, REQUIRED_PERSONA_SECTIONS_SPECIALIST, VALID_PERSONA_TIERS, PERSONA_LINE_BUDGETS, REQUIRED_USERTYPE_SECTIONS, USERTYPE_LINE_BUDGET, VALID_PERSONA_WINGS, PERSONA_LINE_BUDGETS_BY_WING, REQUIRED_SKILL_SECTIONS, SECTION_ALIASES, RECOMMENDED_SKILL_SECTIONS, CONDITIONAL_SKILL_SECTIONS, RULE_BAD_SIGNS, FRUGALITY_WRITER_SKILLS, FRUGALITY_CHARTER_RELPATH, FRUGALITY_CHARTER_INDEX_RULES, VAGUE_VALIDATION_PATTERNS, TRIGGER_WARNING_PATTERNS, ORDERED_STEP_PATTERN, SECTION_PATTERN, FRONTMATTER_PATTERN, DESCRIPTION_PATTERN, STATUS_PATTERN, REPLACED_BY_PATTERN, TIER_PATTERN, TYPE_PATTERN, SOURCE_PATTERN, SENIOR_RELATED_SKILLS_PATTERN, SENIOR_RELATED_WHEN_PATTERN, SENIOR_RELATED_WHEN_NOT_PATTERN, SENIOR_PROACTIVE_PATTERN, SENIOR_OUTPUT_PATTERN, H1_PATTERN, DOUBLE_BLANK_PATTERN, VALID_RULE_TYPES, VALID_RULE_SOURCES, ROUTER_ALLOWED_TRIGGER_KEYS, KERNEL_RULE_IDS2, VALID_EXECUTION_TYPES, VALID_EXECUTION_HANDLERS, VALID_EXECUTION_SAFETY_MODES, VALID_EXECUTION_FIELDS, WING3_SPINE_SLOTS, CONTEXT_SPINE_INLINE_PATTERN, WING3_SAAS_URL_PATTERN, WING3_VENDOR_BLACKLIST, WING3_STACK_LOCKED_PATTERN, WING3_CHANNEL_TACTIC_PATTERN, WING4_SPINE_SLOTS, WING4_SAAS_URL_PATTERN, WING4_VENDOR_BLACKLIST, WING4_STAGE_AGNOSTIC_PATTERN, Issue, LintResult, _ROLE_CONTRACT_CANDIDATES, _ROLE_CONTRACT_SLUGS_CACHE, _ROLE_CONTRACT_REF_PATTERN, PROCEDURE_HEADING_PATTERN, COMMAND_FRONTMATTER_DELEGATION_KEYS, MD_LINK_PATTERN_G, NAME_PATTERN, DISABLE_MODEL_PATTERN, _INSPECT_VERB_PATTERN, last_changed_scope_reason, last_changed_base_diff_ok, _INTERACTION_NAME_PATTERNS, _INTERACTION_CONTENT_KEYWORDS, _EXEC_FILE_SIGNALS, _EXEC_CONTENT_SIGNALS, _TASK_TYPE_SIGNALS, _VERIFICATION_TOOLS, _FRUGALITY_STANDARDS_PATTERN, _FRUGALITY_CHARTER_LINK_PATTERN, _MD_LINK_PATTERN, _MALICE_CRED_EXFIL, _MALICE_REMOTE_EXEC, _MALICE_FORCE_PUSH, _MALICE_CHMOD_SECRETS, _MALICE_SHELL_INJECT, _MALICE_PATTERNS, _NAG_UPDATE_CHECK, _NAG_STAY_CURRENT, _NAG_SELF_PROMO, _NAG_BEST_SKILL, _NAG_MUST_USE, _NAG_ALWAYS_USE, _NAG_PATTERNS, _OUTPUT_SCHEMA_KEY_PATTERN, _SCHEMA_ARTEFACT_TYPES, ArgError, isCliEntry2;
|
|
18002
18015
|
var init_skill_linter = __esm({
|
|
18003
18016
|
"src/scripts/skill_linter.ts"() {
|
|
18004
18017
|
"use strict";
|
|
@@ -18046,6 +18059,22 @@ var init_skill_linter = __esm({
|
|
|
18046
18059
|
"Output format": /* @__PURE__ */ new Set(["Output format", "Output"])
|
|
18047
18060
|
};
|
|
18048
18061
|
RECOMMENDED_SKILL_SECTIONS = [];
|
|
18062
|
+
CONDITIONAL_SKILL_SECTIONS = [
|
|
18063
|
+
{
|
|
18064
|
+
section: "Security constraints",
|
|
18065
|
+
// `isDirectory()`, not `existsSync` — a FILE named `scripts` would satisfy
|
|
18066
|
+
// an existence test while shipping no script at all, and the predicate is
|
|
18067
|
+
// only false-positive-free because it means what it says.
|
|
18068
|
+
when: (skillDir) => {
|
|
18069
|
+
try {
|
|
18070
|
+
return fs19.statSync(path19.join(skillDir, "scripts")).isDirectory();
|
|
18071
|
+
} catch {
|
|
18072
|
+
return false;
|
|
18073
|
+
}
|
|
18074
|
+
},
|
|
18075
|
+
reason: "the skill ships a scripts/ directory"
|
|
18076
|
+
}
|
|
18077
|
+
];
|
|
18049
18078
|
RULE_BAD_SIGNS = ["## Procedure", "## Output format", "## Gotchas"];
|
|
18050
18079
|
FRUGALITY_WRITER_SKILLS = /* @__PURE__ */ new Set([
|
|
18051
18080
|
"skill-writing",
|
|
@@ -19486,6 +19515,7 @@ __export(archive_completed_roadmaps_exports, {
|
|
|
19486
19515
|
_branch_touched_paths: () => _branch_touched_paths,
|
|
19487
19516
|
_git_mv: () => _git_mv,
|
|
19488
19517
|
_inbound_ref_rewrite: () => _inbound_ref_rewrite,
|
|
19518
|
+
_is_frozen_record: () => _is_frozen_record,
|
|
19489
19519
|
_repo_root: () => _repo_root,
|
|
19490
19520
|
archive_completed: () => archive_completed,
|
|
19491
19521
|
main: () => main10
|
|
@@ -19535,6 +19565,10 @@ function _branch_touched_paths(root, base) {
|
|
|
19535
19565
|
}
|
|
19536
19566
|
return out;
|
|
19537
19567
|
}
|
|
19568
|
+
function _is_frozen_record(rel) {
|
|
19569
|
+
const norm = rel.split(path22.sep).join("/");
|
|
19570
|
+
return _FROZEN_RECORD_PREFIXES.some((p) => norm.startsWith(p)) || norm.endsWith(".patch");
|
|
19571
|
+
}
|
|
19538
19572
|
function _inbound_ref_rewrite(root, old_rel, new_rel, dry_run, tracked = true) {
|
|
19539
19573
|
const changed = [];
|
|
19540
19574
|
let candidates;
|
|
@@ -19552,6 +19586,9 @@ function _inbound_ref_rewrite(root, old_rel, new_rel, dry_run, tracked = true) {
|
|
|
19552
19586
|
if (!rel || rel === old_rel) {
|
|
19553
19587
|
continue;
|
|
19554
19588
|
}
|
|
19589
|
+
if (_is_frozen_record(rel)) {
|
|
19590
|
+
continue;
|
|
19591
|
+
}
|
|
19555
19592
|
const fp = path22.join(root, rel);
|
|
19556
19593
|
let text;
|
|
19557
19594
|
try {
|
|
@@ -19781,7 +19818,7 @@ function _isCliEntry10() {
|
|
|
19781
19818
|
return false;
|
|
19782
19819
|
}
|
|
19783
19820
|
}
|
|
19784
|
-
var _HERE6, ArgparseExit2, _PROG6;
|
|
19821
|
+
var _HERE6, ArgparseExit2, _FROZEN_RECORD_PREFIXES, _PROG6;
|
|
19785
19822
|
var init_archive_completed_roadmaps = __esm({
|
|
19786
19823
|
"src/agent-src/scripts/archive_completed_roadmaps.ts"() {
|
|
19787
19824
|
"use strict";
|
|
@@ -19795,6 +19832,7 @@ var init_archive_completed_roadmaps = __esm({
|
|
|
19795
19832
|
this.code = code;
|
|
19796
19833
|
}
|
|
19797
19834
|
};
|
|
19835
|
+
_FROZEN_RECORD_PREFIXES = ["agents/evidence/"];
|
|
19798
19836
|
_PROG6 = "archive_completed_roadmaps.py";
|
|
19799
19837
|
if (_isCliEntry10() || process9.argv[1] === _HERE6) {
|
|
19800
19838
|
try {
|
|
@@ -25344,6 +25382,18 @@ var init_session_role = __esm({
|
|
|
25344
25382
|
}
|
|
25345
25383
|
});
|
|
25346
25384
|
|
|
25385
|
+
// src/scripts/_lib/env_kill_switch.ts
|
|
25386
|
+
function isEnvKillSwitchActive(name) {
|
|
25387
|
+
return !_OFF_VALUES.has(process.env[name] ?? "");
|
|
25388
|
+
}
|
|
25389
|
+
var _OFF_VALUES;
|
|
25390
|
+
var init_env_kill_switch = __esm({
|
|
25391
|
+
"src/scripts/_lib/env_kill_switch.ts"() {
|
|
25392
|
+
"use strict";
|
|
25393
|
+
_OFF_VALUES = /* @__PURE__ */ new Set(["", "0", "false", "False"]);
|
|
25394
|
+
}
|
|
25395
|
+
});
|
|
25396
|
+
|
|
25347
25397
|
// src/scripts/ai_council/quorum.ts
|
|
25348
25398
|
function resolveQuorumThreshold(total, setting = "majority") {
|
|
25349
25399
|
if (!Number.isInteger(total) || total < 0) {
|
|
@@ -25430,8 +25480,7 @@ function _utf8Bytes(cp) {
|
|
|
25430
25480
|
);
|
|
25431
25481
|
}
|
|
25432
25482
|
function _kill_switch_active() {
|
|
25433
|
-
|
|
25434
|
-
return !(value === "" || value === "0" || value === "false" || value === "False");
|
|
25483
|
+
return isEnvKillSwitchActive(_KILL_SWITCH_ENV);
|
|
25435
25484
|
}
|
|
25436
25485
|
function appendEvent(event, opts = {}) {
|
|
25437
25486
|
if (_kill_switch_active()) {
|
|
@@ -25593,6 +25642,7 @@ var SCHEMA_VERSION6, _VALID_ACTIONS, _KILL_SWITCH_ENV, _HERE15, _REPO_ROOT, _DEF
|
|
|
25593
25642
|
var init_events_log = __esm({
|
|
25594
25643
|
"src/scripts/ai_council/events_log.ts"() {
|
|
25595
25644
|
"use strict";
|
|
25645
|
+
init_env_kill_switch();
|
|
25596
25646
|
init_quorum();
|
|
25597
25647
|
SCHEMA_VERSION6 = 2;
|
|
25598
25648
|
_VALID_ACTIONS = /* @__PURE__ */ new Set([
|
|
@@ -29945,6 +29995,7 @@ var init_review_gate = __esm({
|
|
|
29945
29995
|
"src/scripts/ai_team/review_gate.ts"() {
|
|
29946
29996
|
"use strict";
|
|
29947
29997
|
init_claude_plugin();
|
|
29998
|
+
init_env_kill_switch();
|
|
29948
29999
|
init_state_io();
|
|
29949
30000
|
init_config2();
|
|
29950
30001
|
GATE_LEDGER_RELPATH = path50.join("agents", "runtime", "team", "events.log");
|
|
@@ -37947,6 +37998,49 @@ function synthesis_template(mode) {
|
|
|
37947
37998
|
`Unknown synthesis mode ${_pyRepr9(mode)}. Expected one of: [${expected.map((m) => _pyReprStr2(m)).join(", ")}]`
|
|
37948
37999
|
);
|
|
37949
38000
|
}
|
|
38001
|
+
function parse_verdict_line(text) {
|
|
38002
|
+
_VERDICT_RE.lastIndex = 0;
|
|
38003
|
+
let last = null;
|
|
38004
|
+
let m;
|
|
38005
|
+
while ((m = _VERDICT_RE.exec(text)) !== null) {
|
|
38006
|
+
last = m;
|
|
38007
|
+
}
|
|
38008
|
+
if (last === null) {
|
|
38009
|
+
return null;
|
|
38010
|
+
}
|
|
38011
|
+
const raw = (last[1] ?? "").trim();
|
|
38012
|
+
if (raw.length === 0) {
|
|
38013
|
+
return null;
|
|
38014
|
+
}
|
|
38015
|
+
if (/^<.*>$/.test(raw)) {
|
|
38016
|
+
return null;
|
|
38017
|
+
}
|
|
38018
|
+
return { label: raw.toLowerCase(), display: raw };
|
|
38019
|
+
}
|
|
38020
|
+
function describe_verdict_mismatch(text, tally) {
|
|
38021
|
+
const verdict = parse_verdict_line(text);
|
|
38022
|
+
if (verdict === null) {
|
|
38023
|
+
return null;
|
|
38024
|
+
}
|
|
38025
|
+
const optionNamedSplit = tally.options.some(
|
|
38026
|
+
(o) => o.label.toLowerCase() === SPLIT_VERDICT_LABEL
|
|
38027
|
+
);
|
|
38028
|
+
const claimsSplit = verdict.label === SPLIT_VERDICT_LABEL && !optionNamedSplit;
|
|
38029
|
+
if (tally.consensus === null) {
|
|
38030
|
+
if (claimsSplit) {
|
|
38031
|
+
return null;
|
|
38032
|
+
}
|
|
38033
|
+
const how = tally.split ? `split across ${tally.options.length} option(s)` : "no option-level stances parsed";
|
|
38034
|
+
return `synthesis claims "${verdict.display}" but the tally recorded no consensus (${how}); use \`${SPLIT_VERDICT_LABEL}\` or re-run the round`;
|
|
38035
|
+
}
|
|
38036
|
+
if (claimsSplit) {
|
|
38037
|
+
return `synthesis claims a split but the tally cleared "${tally.consensus.label}"`;
|
|
38038
|
+
}
|
|
38039
|
+
if (verdict.label !== tally.consensus.label.toLowerCase()) {
|
|
38040
|
+
return `synthesis claims "${verdict.display}" but the tally cleared "${tally.consensus.label}"`;
|
|
38041
|
+
}
|
|
38042
|
+
return null;
|
|
38043
|
+
}
|
|
37950
38044
|
function _strip_host_identity(text) {
|
|
37951
38045
|
if (!text) {
|
|
37952
38046
|
return text;
|
|
@@ -38043,7 +38137,7 @@ ${text}`);
|
|
|
38043
38137
|
function peer_review_synthesis_addendum() {
|
|
38044
38138
|
return PEER_REVIEW_SYNTHESIS_ADDENDUM;
|
|
38045
38139
|
}
|
|
38046
|
-
var NEUTRALITY_PREAMBLE, HOST_AGENT_IDENTITY_PATTERNS, PROMPT_MODE, ROADMAP_MODE, DIFF_MODE, FILES_MODE, PR_MODE, DESIGN_MODE, OPTIMIZE_MODE, ANALYSIS_MODE, DEBATE_MODE, STANCE_LINE_CONTRACT, ANTI_CONFORMITY_DIRECTIVE, _MODE_TABLE, FINDING_EXTRACTION_PROMPT, FINDING_SCORING_PROMPT, DEFAULT_SYNTHESIS, PR_SYNTHESIS, ANALYSIS_SYNTHESIS, CREATIVE_SYNTHESIS, _SYNTHESIS_TABLE, _INPUT_MODE_TO_SYNTHESIS_KEY, PEER_REVIEW_PROMPT, PEER_REVIEW_SYNTHESIS_ADDENDUM;
|
|
38140
|
+
var NEUTRALITY_PREAMBLE, HOST_AGENT_IDENTITY_PATTERNS, PROMPT_MODE, ROADMAP_MODE, DIFF_MODE, FILES_MODE, PR_MODE, DESIGN_MODE, OPTIMIZE_MODE, ANALYSIS_MODE, DEBATE_MODE, STANCE_LINE_CONTRACT, ANTI_CONFORMITY_DIRECTIVE, _MODE_TABLE, FINDING_EXTRACTION_PROMPT, FINDING_SCORING_PROMPT, DEFAULT_SYNTHESIS, PR_SYNTHESIS, ANALYSIS_SYNTHESIS, CREATIVE_SYNTHESIS, _SYNTHESIS_TABLE, _INPUT_MODE_TO_SYNTHESIS_KEY, SPLIT_VERDICT_LABEL, _VERDICT_RE, VERDICT_LINE_CONTRACT, PEER_REVIEW_PROMPT, PEER_REVIEW_SYNTHESIS_ADDENDUM;
|
|
38047
38141
|
var init_prompts2 = __esm({
|
|
38048
38142
|
"src/scripts/ai_council/prompts.ts"() {
|
|
38049
38143
|
"use strict";
|
|
@@ -38307,6 +38401,11 @@ turn.`;
|
|
|
38307
38401
|
diff: "default",
|
|
38308
38402
|
files: "default"
|
|
38309
38403
|
};
|
|
38404
|
+
SPLIT_VERDICT_LABEL = "split";
|
|
38405
|
+
_VERDICT_RE = /^\s*VERDICT:\s*(.+?)\s*$/gm;
|
|
38406
|
+
VERDICT_LINE_CONTRACT = `Close the synthesis with EXACTLY this line, and nothing after it:
|
|
38407
|
+
VERDICT: <option-label>
|
|
38408
|
+
Use the SAME <option-label> the Vote Tally cleared. Use \`${SPLIT_VERDICT_LABEL}\` when no option cleared the threshold \u2014 an honest split is a verdict, and claiming a winner the tally did not record is a render error.`;
|
|
38310
38409
|
PEER_REVIEW_PROMPT = `Below are responses from other independent reviewers to the same
|
|
38311
38410
|
artefact you just reviewed. Each is labelled with a neutral identifier
|
|
38312
38411
|
(\`Response-A\`, \`Response-B\`, \u2026). You do NOT know which model produced
|
|
@@ -41809,6 +41908,11 @@ ${addendum}` : _pyLStrip(addendum);
|
|
|
41809
41908
|
template = template ? `${template}
|
|
41810
41909
|
|
|
41811
41910
|
${CHAIRMAN_FIELDS_ADDENDUM}` : CHAIRMAN_FIELDS_ADDENDUM;
|
|
41911
|
+
}
|
|
41912
|
+
if (opts.stance_tally === true && template !== "") {
|
|
41913
|
+
template = `${template}
|
|
41914
|
+
|
|
41915
|
+
${VERDICT_LINE_CONTRACT}`;
|
|
41812
41916
|
}
|
|
41813
41917
|
let body;
|
|
41814
41918
|
if (template) {
|
|
@@ -41831,6 +41935,10 @@ ${body}`;
|
|
|
41831
41935
|
responses.filter((r) => !r.error).map((r) => ({ member: `${r.provider}:${r.model}`, text: r.text }))
|
|
41832
41936
|
);
|
|
41833
41937
|
blocks.push(render_vote_tally(tally));
|
|
41938
|
+
const mismatch = describe_verdict_mismatch(body, tally);
|
|
41939
|
+
if (mismatch !== null) {
|
|
41940
|
+
blocks.push(`> **Verdict/tally mismatch:** ${mismatch}`);
|
|
41941
|
+
}
|
|
41834
41942
|
}
|
|
41835
41943
|
blocks.push(`## Convergence / Divergence
|
|
41836
41944
|
|
|
@@ -12,18 +12,53 @@ probability cause.
|
|
|
12
12
|
|
|
13
13
|
## Lookup
|
|
14
14
|
|
|
15
|
-
| Signature (what you see) | Likely cause | First check |
|
|
16
|
-
|
|
17
|
-
| **Tool/agent loop** — same tool called >2× with near-identical args, no progress | Missing stop condition; reading without acting; ambiguous state | Stop. State the hypothesis in one line. Act or ask — do not re-read. (`context-hygiene` 15/25 rule) |
|
|
18
|
-
| **HTTP 429 / "rate limit" / "quota"** | Too-frequent calls; shared quota; missing backoff | Inspect response headers (`Retry-After`, `X-RateLimit-*`); add exponential backoff; check if a loop is firing the calls |
|
|
19
|
-
| **`ECONNREFUSED` / connection refused** | Service not up; wrong host/port; container not started | Is the service running? (`docker compose ps`, health endpoint); is the port right? (`lsof -i :PORT`); is it bound to `127.0.0.1` vs `0.0.0.0`? |
|
|
20
|
-
| **`ENOENT` / "no such file or directory"** | Wrong cwd; path relative vs absolute; file not generated yet | Print the *resolved absolute* path; check cwd; confirm a prior step actually wrote the file |
|
|
21
|
-
| **Timeout / hang** | Blocking I/O; deadlock; awaiting a condition that never holds | What is it waiting on? Add a log right before the blocking call; bound the wait; check for a missing `await`/`return` |
|
|
22
|
-
| **`OOM` / killed (137) / heap out of memory** | Unbounded collection; loading all rows; leak in a loop | What grows per iteration? Stream/paginate instead of loading all; check `--max-old-space-size` only after finding the growth |
|
|
23
|
-
| **`EACCES` / permission denied** | Wrong file mode; running as wrong user; read-only mount | `ls -l` the target; check the process user; is the path a read-only volume? |
|
|
24
|
-
| **Flaky test (passes alone, fails in suite)** | Shared state; order dependence; real clock/random; unawaited async | Run the test in isolation vs in suite; look for shared fixtures, global state, `Date.now()`/random, missing `await` |
|
|
25
|
-
| **Works locally, fails in CI** | Env/config drift; missing service; absent secret; path/case sensitivity | Diff env (`printenv`); is the service/secret present in CI? Linux is case-sensitive — check filename casing |
|
|
26
|
-
| **Intermittent / "sometimes"** | Race; timing; network; non-deterministic order | Reproduce with condition-based waiting, not fixed sleeps (`systematic-debugging` § Condition-based waiting); log timestamps |
|
|
15
|
+
| ID | Signature (what you see) | Likely cause | First check |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| `tool-loop` | **Tool/agent loop** — same tool called >2× with near-identical args, no progress | Missing stop condition; reading without acting; ambiguous state | Stop. State the hypothesis in one line. Act or ask — do not re-read. (`context-hygiene` 15/25 rule) |
|
|
18
|
+
| `rate-limited` | **HTTP 429 / "rate limit" / "quota"** | Too-frequent calls; shared quota; missing backoff | Inspect response headers (`Retry-After`, `X-RateLimit-*`); add exponential backoff; check if a loop is firing the calls |
|
|
19
|
+
| `connection-refused` | **`ECONNREFUSED` / connection refused** | Service not up; wrong host/port; container not started | Is the service running? (`docker compose ps`, health endpoint); is the port right? (`lsof -i :PORT`); is it bound to `127.0.0.1` vs `0.0.0.0`? |
|
|
20
|
+
| `path-not-found` | **`ENOENT` / "no such file or directory"** | Wrong cwd; path relative vs absolute; file not generated yet | Print the *resolved absolute* path; check cwd; confirm a prior step actually wrote the file |
|
|
21
|
+
| `hang` | **Timeout / hang** | Blocking I/O; deadlock; awaiting a condition that never holds | What is it waiting on? Add a log right before the blocking call; bound the wait; check for a missing `await`/`return` |
|
|
22
|
+
| `out-of-memory` | **`OOM` / killed (137) / heap out of memory** | Unbounded collection; loading all rows; leak in a loop | What grows per iteration? Stream/paginate instead of loading all; check `--max-old-space-size` only after finding the growth |
|
|
23
|
+
| `permission-denied` | **`EACCES` / permission denied** | Wrong file mode; running as wrong user; read-only mount | `ls -l` the target; check the process user; is the path a read-only volume? |
|
|
24
|
+
| `order-dependent-test` | **Flaky test (passes alone, fails in suite)** | Shared state; order dependence; real clock/random; unawaited async | Run the test in isolation vs in suite; look for shared fixtures, global state, `Date.now()`/random, missing `await` |
|
|
25
|
+
| `local-ci-drift` | **Works locally, fails in CI** | Env/config drift; missing service; absent secret; path/case sensitivity | Diff env (`printenv`); is the service/secret present in CI? Linux is case-sensitive — check filename casing |
|
|
26
|
+
| `race` | **Intermittent / "sometimes"** | Race; timing; network; non-deterministic order | Reproduce with condition-based waiting, not fixed sleeps (`systematic-debugging` § Condition-based waiting); log timestamps |
|
|
27
|
+
| `stale-capability-claim` | **Budget exhausted plus repeated attempts at a tool that is not available** | A capability the session claimed for itself instead of resolving — the council, a hook slot, a subagent primitive, an MCP server assumed present | Ask the live verb, not the tree: `agent-config hooks:status` for slot bindings, `agent-config routing:doctor` for per-field capability **provenance** (`registry` / `live-probe` / `default`), `agent-config council:status` for council availability. A `false` from `default` records that nobody answered, not that the host cannot |
|
|
28
|
+
|
|
29
|
+
### Naming convention for IDs
|
|
30
|
+
|
|
31
|
+
An id is a short kebab handle so a signature can be **cited by name** — the
|
|
32
|
+
first column was prose, so nothing could reference a row.
|
|
33
|
+
|
|
34
|
+
One rule governs new ids: **where a row's signature is a stable string the
|
|
35
|
+
tooling already prints, the id is that string verbatim** rather than a kebab
|
|
36
|
+
synonym. `skill_linter.ts` emits a closed set of `Issue` codes —
|
|
37
|
+
`missing_section`, `missing_conditional_section`, `invalid_execution_handler` —
|
|
38
|
+
and a row about one of those is looked up by the code the reader just saw in the
|
|
39
|
+
output. No current row is of that class; the convention is stated for the next
|
|
40
|
+
one, so the table does not accumulate two names for the same failure.
|
|
41
|
+
|
|
42
|
+
## Discrimination drills
|
|
43
|
+
|
|
44
|
+
A row's "first check" is only worth following if the documented signature is what
|
|
45
|
+
actually appears. Each drill breaks the named layer and asserts the documented
|
|
46
|
+
symptom — run it once when you doubt a row, or when adding one. They are text a
|
|
47
|
+
human runs; nothing here is a CI gate.
|
|
48
|
+
|
|
49
|
+
| ID | Drill — break this, expect that |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `tool-loop` | Ask for a fact that is genuinely absent from the tree (a config key nothing defines). Expect repeated searches with shrinking variation and no edit — that shape, not an error message, is the signature. |
|
|
52
|
+
| `rate-limited` | Fire the provider's own rate limit with a tight loop against a cheap endpoint. Expect `429` **plus** `Retry-After`; a `429` with no such header is a different failure (a gateway, not the API). |
|
|
53
|
+
| `connection-refused` | Stop the service and call it (`docker compose stop <svc>`). Expect `ECONNREFUSED` immediately. If it hangs instead, the host resolves but nothing listens — that is `hang`, not this row. |
|
|
54
|
+
| `path-not-found` | Run a path-taking script from a different cwd. Expect `ENOENT` naming a path that is *almost* right — the near-miss is the tell that the path is relative, not missing. |
|
|
55
|
+
| `hang` | `await` a promise nothing resolves. Expect no output and no exit, distinguishable from `race` by being 100% reproducible. |
|
|
56
|
+
| `out-of-memory` | Accumulate rows in an array inside a loop over a large fixture. Expect the heap message and exit 137 — and note that the reported line is where allocation failed, not where the growth is. |
|
|
57
|
+
| `permission-denied` | `chmod 000` a fixture and read it. Expect `EACCES`, not `ENOENT` — confusing the two sends you looking for a missing file that is right there. |
|
|
58
|
+
| `order-dependent-test` | Add a module-level mutable to two tests in one file. Expect green alone, red in suite. Reversing the file order should move the failure — if it does not, the cause is shared *external* state, not order. |
|
|
59
|
+
| `local-ci-drift` | Rename a fixture's casing (`Foo.ts` → `foo.ts`) and import the old name. Expect green on macOS, red on Linux CI. The generalisation: anything the local filesystem is lenient about. |
|
|
60
|
+
| `race` | Replace a condition-based wait with a fixed `sleep` shorter than the operation. Expect intermittent failure whose rate moves with machine load — load-sensitivity is what separates this from `order-dependent-test`. |
|
|
61
|
+
| `stale-capability-claim` | Assert a capability without asking for it, then compare against the live verb. `agent-config routing:doctor` prints provenance per field; a field whose provenance is `default` was never established by anything — which is exactly the state a confident self-claim is indistinguishable from. |
|
|
27
62
|
|
|
28
63
|
## How to use
|
|
29
64
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event4u/agent-config",
|
|
3
3
|
"mcpName": "io.github.event4u-app/agent-config",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.34.0",
|
|
5
5
|
"description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including \"zero runtime daemon\".",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"build:mcp-catalog": "./scripts-run src/scripts/build_mcp_catalog --write --strict",
|
|
78
78
|
"build": "npm run build:cli && npm run build:install-bundle && npm run build:hooks && npm run build:mcp-bundle && npm run build:cli-delegate && npm run build:ui",
|
|
79
79
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.scripts.json",
|
|
80
|
-
"lint:ts": "eslint 'src/**/*.ts'",
|
|
80
|
+
"lint:ts": "eslint --cache 'src/**/*.ts'",
|
|
81
81
|
"test:ts": "vitest run",
|
|
82
82
|
"lint:publint": "publint",
|
|
83
83
|
"prepack": "npm run build && npm run build:discovery && npm run build:mcp-manifest && node src/scripts/prepack-check.mjs",
|
|
@@ -128,12 +128,42 @@ function _branch_touched_paths(root: string, base: string): Set<string> | null {
|
|
|
128
128
|
return out;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Paths where a repo-relative path is DATA, not a live reference — so
|
|
133
|
+
* rewriting it falsifies a record instead of keeping a link resolvable.
|
|
134
|
+
*
|
|
135
|
+
* Two classes, and the second is why this predicate exists at all:
|
|
136
|
+
*
|
|
137
|
+
* - `agents/evidence/**` — a committed record of what was true, or of what a
|
|
138
|
+
* reviewer was shown, at one point in time. Retroactively editing it makes it
|
|
139
|
+
* claim something that did not happen, which is the failure
|
|
140
|
+
* `evaluator-independence` exists to prevent ("the prompt ships with the
|
|
141
|
+
* verdict" is worthless if a later sweep can edit the prompt).
|
|
142
|
+
* - `*.patch` anywhere — a patch's `diff --git a/… b/…` and `+++ b/…` lines are
|
|
143
|
+
* the payload. Rewriting them detaches the patch from the commit it records
|
|
144
|
+
* AND makes `git apply` target a path the diff never created.
|
|
145
|
+
*
|
|
146
|
+
* Measured 2026-08-11: archiving one roadmap rewrote four frozen artefacts
|
|
147
|
+
* under `agents/evidence/reviews/`, including the `diff.patch` headers of a
|
|
148
|
+
* recorded review input. Nothing caught it — the sweep reported it as
|
|
149
|
+
* "4 ref(s) migrated", which reads like success.
|
|
150
|
+
*/
|
|
151
|
+
const _FROZEN_RECORD_PREFIXES: readonly string[] = ['agents/evidence/'];
|
|
152
|
+
|
|
153
|
+
/** Is `rel` a frozen record whose path strings must not be rewritten? */
|
|
154
|
+
export function _is_frozen_record(rel: string): boolean {
|
|
155
|
+
const norm = rel.split(path.sep).join('/');
|
|
156
|
+
return _FROZEN_RECORD_PREFIXES.some((p) => norm.startsWith(p)) || norm.endsWith('.patch');
|
|
157
|
+
}
|
|
158
|
+
|
|
131
159
|
/**
|
|
132
160
|
* Rewrite full-path references `old_rel` → `new_rel` in tracked files.
|
|
133
161
|
*
|
|
134
162
|
* Only the exact repo-relative path is rewritten (bare-filename mentions are
|
|
135
163
|
* left alone). The archived file's own path never matches because the search
|
|
136
|
-
* string is the un-archived path.
|
|
164
|
+
* string is the un-archived path. Frozen records are skipped — see
|
|
165
|
+
* `_is_frozen_record`; a link left dangling in a historical record is correct,
|
|
166
|
+
* because the record describes a tree that no longer exists.
|
|
137
167
|
*/
|
|
138
168
|
function _inbound_ref_rewrite(
|
|
139
169
|
root: string,
|
|
@@ -165,6 +195,10 @@ function _inbound_ref_rewrite(
|
|
|
165
195
|
// skip the roadmap file itself
|
|
166
196
|
continue;
|
|
167
197
|
}
|
|
198
|
+
if (_is_frozen_record(rel)) {
|
|
199
|
+
// A record of a past state — rewriting it falsifies the record.
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
168
202
|
const fp = path.join(root, rel);
|
|
169
203
|
let text: string;
|
|
170
204
|
try {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"solo_conclusion_rate": {
|
|
22
22
|
"definition": "share of post_run passes with `solo: true` — written by the isSoloConcluded predicate in quorum.ts, never re-derived from the numbers here, so one change to the rule cannot leave this definition stale. ceil(n/2) makes 1-of-2 a legitimate conclusion; this measures how often that legitimacy is used, not whether it should be. THREE cases carry solo: true and they are not the same finding — a degraded multi-member council (configured_total > 1), a council configured with one member (configured_total = 1, dispatch = full), and a deliberate --single dispatch (dispatch = single). Split on dispatch and configured_total before reading the rate.",
|
|
23
23
|
"instrument": "quorum_result lines, phase=post_run, command=run, field solo, split by dispatch + configured_total",
|
|
24
|
-
"threshold": "
|
|
24
|
+
"threshold": "DECIDED 2026-08-11 against real data — see ADR-224 (docs/decisions/ADR-224-gate-scoped-solo-attendance-floor.md). The quorum-solo-floor blocker pre-registered three outcomes — a third CLI member, a gate-scoped min_present: 2, or a published null under 5 %. Read at 2026-08-11T09:30Z with the definition above: 1 of 8 post_run/command=run passes carried a degraded solo conclusion = 12.5 %, so the under-5 % null did not fire and the gate-scoped floor was chosen (council 2/2). It was decided on the degraded case only; a --single pass is the operator asking for one voice, not the council failing to assemble. Still no numeric threshold is committed here, deliberately: at n=8 the 95 % interval around 1/8 spans roughly 0.3 %-53 %, so the rate established urgency and not certainty, and ADR-224's review trigger reopens the null at n=40. What this metric measures does NOT change; once the floor lands, how often it FIRES is a second and different question needing its own instrument."
|
|
25
25
|
},
|
|
26
26
|
"absent_reason_distribution": {
|
|
27
27
|
"definition": "count per reason over absent[] entries — no_binary / no_auth / timeout / quota (AbsentReason) plus the two runtime fallbacks the CLI writes directly, `unavailable` (transport resolved to nothing) and `binary_missing` (a CLI client refused to construct). Distinguishes a council degraded by configuration from one degraded by provider behaviour, which have opposite fixes.",
|
|
@@ -57,8 +57,13 @@ export function templateEnvelope(): Record<string, unknown> {
|
|
|
57
57
|
acceptance_criteria: ['<what done means, one line each>'],
|
|
58
58
|
remaining: ['<open work, one line each>'],
|
|
59
59
|
not_carried_forward: ['<what the successor must re-derive from source>'],
|
|
60
|
-
decisions: ['<decision — one-line rationale>'],
|
|
60
|
+
decisions: ['<decision — one-line rationale> [reversible]'],
|
|
61
61
|
constraints: ['<binding constraint>'],
|
|
62
|
+
// Ships EMPTY, unlike the prose lists above: the entries are path refs,
|
|
63
|
+
// and a `<placeholder>` left in by a hurried writer would be a fake path
|
|
64
|
+
// rather than obvious filler. An empty list is also the honest default —
|
|
65
|
+
// nothing off limits is the common case.
|
|
66
|
+
do_not_touch: [],
|
|
62
67
|
open_worker_envelopes: [],
|
|
63
68
|
artifact_paths: ['<path to a deliverable / note / evidence file>'],
|
|
64
69
|
assumptions: [],
|