@event4u/agent-config 9.1.0 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +22 -251
- package/README.md +1 -1
- package/dist/agent-src/commands/implement-ticket.md +2 -0
- package/dist/agent-src/commands/review/changes.md +22 -0
- package/dist/agent-src/contexts/execution/subagent-modes-detail.md +41 -6
- package/dist/agent-src/contexts/execution/verify-budget.md +15 -0
- package/dist/agent-src/rules/cross-source-consistency.md +92 -0
- package/dist/agent-src/rules/delegation-policy.md +1 -1
- package/dist/agent-src/skills/estimate-ticket/SKILL.md +2 -0
- package/dist/agent-src/skills/feature-planning/SKILL.md +1 -1
- package/dist/agent-src/skills/po-discovery/SKILL.md +2 -0
- package/dist/agent-src/skills/refine-ticket/SKILL.md +7 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +14 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +26 -5
- package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +18 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +3 -2
- package/dist/agent-src/skills/subagent-orchestration/prompts/adversarial-verification-council.md +79 -0
- package/dist/agent-src/skills/subagent-orchestration/schemas/adversarial-findings.json +56 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +42 -19
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/install/install.mjs +9 -0
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/router.json +1 -1
- package/dist/server/schemas/settings.js +5 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +8 -1
- package/docs/architecture.md +2 -2
- package/docs/archive/CHANGELOG-pre-9.2.0.md +276 -0
- package/docs/benchmark.md +24 -0
- package/docs/decisions/ADR-122-adversarial-verification-council.md +136 -0
- package/docs/decisions/INDEX.md +1 -0
- package/docs/design/adversarial-council-eval.md +106 -0
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/cross-source-consistency-mechanics.md +109 -0
- package/docs/proof.md +2 -2
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +42 -0
- package/src/scripts/_lib/adversarial_council_gate.test.ts +112 -0
- package/src/scripts/_lib/adversarial_council_gate.ts +98 -0
- package/src/scripts/_lib/adversarial_reconcile.test.ts +197 -0
- package/src/scripts/_lib/adversarial_reconcile.ts +187 -0
- package/src/scripts/_lib/verify_budget.ts +4 -1
- package/src/scripts/schemas/agent-settings.schema.json +11 -0
- package/src/server/schemas/settings.ts +9 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adversarial verification council — reconciliation core (Phase 1).
|
|
3
|
+
*
|
|
4
|
+
* Pure, no-I/O, DETERMINISTIC (no Date/random — ids come from sorted key order).
|
|
5
|
+
* Turns per-skeptic finding lists into one `AdversarialFindings` envelope with
|
|
6
|
+
* per-finding provenance (which models raised it) and a cross-model confidence
|
|
7
|
+
* signal. This is the countable heart the ADR mandates lives in TS with tests,
|
|
8
|
+
* never as LLM-computed prose.
|
|
9
|
+
*
|
|
10
|
+
* Contract: it VERIFIES (ranks + annotates findings). It NEVER gates the change
|
|
11
|
+
* — the panel is advisory only (Hard Floor). "Suppression" of a false positive
|
|
12
|
+
* demotes a finding into `false_positives_suppressed`; it is never silently
|
|
13
|
+
* dropped.
|
|
14
|
+
*
|
|
15
|
+
* Schema: src/skills/subagent-orchestration/schemas/adversarial-findings.json
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export type Severity = 'critical' | 'high' | 'medium' | 'low';
|
|
19
|
+
export type Confidence = 'high' | 'medium' | 'low';
|
|
20
|
+
|
|
21
|
+
/** Worst → best; index is the rank used for severity aggregation + sorting. */
|
|
22
|
+
const SEVERITY_ORDER: readonly Severity[] = ['critical', 'high', 'medium', 'low'];
|
|
23
|
+
|
|
24
|
+
/** A single defect as one skeptic reports it. */
|
|
25
|
+
export interface RawFinding {
|
|
26
|
+
severity: Severity;
|
|
27
|
+
category: string;
|
|
28
|
+
location: string;
|
|
29
|
+
description: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** One skeptic's return: what it raised, plus keys it examined and judged clean. */
|
|
33
|
+
export interface SkepticReturn {
|
|
34
|
+
model: string;
|
|
35
|
+
findings: RawFinding[];
|
|
36
|
+
/** `findingKey` values this skeptic actively refuted (examined → not a defect). */
|
|
37
|
+
refutes?: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface Finding {
|
|
41
|
+
id: string;
|
|
42
|
+
severity: Severity;
|
|
43
|
+
category: string;
|
|
44
|
+
location: string;
|
|
45
|
+
description: string;
|
|
46
|
+
raised_by: string[];
|
|
47
|
+
refuted_by: string[];
|
|
48
|
+
confidence: Confidence;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface AdversarialFindings {
|
|
52
|
+
panel: { models: string[]; skeptic_count: number };
|
|
53
|
+
findings: Finding[];
|
|
54
|
+
false_positives_suppressed: Finding[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Dedup key — same location + same category is the same defect. */
|
|
58
|
+
export function findingKey(f: Pick<RawFinding, 'location' | 'category'>): string {
|
|
59
|
+
return `${f.location.trim()}::${f.category.trim().toLowerCase()}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Throw on a malformed raw finding — the reconciler refuses garbage in. */
|
|
63
|
+
export function assertValidRawFinding(f: RawFinding): void {
|
|
64
|
+
if (!SEVERITY_ORDER.includes(f.severity)) {
|
|
65
|
+
throw new Error(`invalid severity: ${JSON.stringify(f.severity)}`);
|
|
66
|
+
}
|
|
67
|
+
for (const [k, v] of Object.entries({ category: f.category, location: f.location, description: f.description })) {
|
|
68
|
+
if (typeof v !== 'string' || v.trim().length === 0) {
|
|
69
|
+
throw new Error(`finding.${k} must be a non-empty string`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Most-severe (lowest index) of two severities. */
|
|
75
|
+
function moreSevere(a: Severity, b: Severity): Severity {
|
|
76
|
+
return SEVERITY_ORDER.indexOf(a) <= SEVERITY_ORDER.indexOf(b) ? a : b;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Confidence from corroboration:
|
|
81
|
+
* - `high` — raised by >=2 skeptics AND by at least half the panel (quorum).
|
|
82
|
+
* - `medium` — raised by >=2 (below quorum), OR the lone raiser on a solo panel.
|
|
83
|
+
* - `low` — a single raiser on a multi-skeptic panel (uncorroborated doubt).
|
|
84
|
+
*/
|
|
85
|
+
export function severityQuorum(raisedCount: number, refutedCount: number, panelSize: number): Confidence {
|
|
86
|
+
if (raisedCount >= 2 && raisedCount * 2 >= panelSize) return 'high';
|
|
87
|
+
if (raisedCount >= 2) return 'medium';
|
|
88
|
+
if (panelSize === 1 && refutedCount === 0) return 'medium';
|
|
89
|
+
return 'low';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A finding is a suppressible false positive when a single skeptic raised it and
|
|
94
|
+
* a strict majority of the OTHER skeptics actively refuted it. Demote, never drop.
|
|
95
|
+
*/
|
|
96
|
+
export function isSuppressedFalsePositive(raisedCount: number, refutedCount: number, panelSize: number): boolean {
|
|
97
|
+
if (raisedCount !== 1) return false;
|
|
98
|
+
const others = panelSize - 1;
|
|
99
|
+
return others > 0 && refutedCount * 2 > others;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface Aggregate {
|
|
103
|
+
key: string;
|
|
104
|
+
severity: Severity;
|
|
105
|
+
category: string;
|
|
106
|
+
location: string;
|
|
107
|
+
description: string;
|
|
108
|
+
raised_by: string[];
|
|
109
|
+
refuted_by: string[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Reconcile per-skeptic returns into one advisory findings envelope.
|
|
114
|
+
* Deterministic: findings are id'd and ordered by (severity rank, key).
|
|
115
|
+
*/
|
|
116
|
+
export function reconcileFindings(returns: SkepticReturn[]): AdversarialFindings {
|
|
117
|
+
const models = returns.map((r) => r.model);
|
|
118
|
+
const skepticCount = returns.length;
|
|
119
|
+
const byKey = new Map<string, Aggregate>();
|
|
120
|
+
|
|
121
|
+
for (const ret of returns) {
|
|
122
|
+
for (const raw of ret.findings) {
|
|
123
|
+
assertValidRawFinding(raw);
|
|
124
|
+
const key = findingKey(raw);
|
|
125
|
+
const existing = byKey.get(key);
|
|
126
|
+
if (existing) {
|
|
127
|
+
existing.severity = moreSevere(existing.severity, raw.severity);
|
|
128
|
+
if (!existing.raised_by.includes(ret.model)) existing.raised_by.push(ret.model);
|
|
129
|
+
// Keep the longest description — usually the most informative.
|
|
130
|
+
if (raw.description.trim().length > existing.description.length) {
|
|
131
|
+
existing.description = raw.description.trim();
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
byKey.set(key, {
|
|
135
|
+
key,
|
|
136
|
+
severity: raw.severity,
|
|
137
|
+
category: raw.category.trim(),
|
|
138
|
+
location: raw.location.trim(),
|
|
139
|
+
description: raw.description.trim(),
|
|
140
|
+
raised_by: [ret.model],
|
|
141
|
+
refuted_by: [],
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Second pass: attach refutations only to findings that were actually raised.
|
|
148
|
+
for (const ret of returns) {
|
|
149
|
+
for (const refutedKey of ret.refutes ?? []) {
|
|
150
|
+
const agg = byKey.get(refutedKey);
|
|
151
|
+
if (agg && !agg.raised_by.includes(ret.model) && !agg.refuted_by.includes(ret.model)) {
|
|
152
|
+
agg.refuted_by.push(ret.model);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const ordered = [...byKey.values()].sort((a, b) => {
|
|
158
|
+
const bySeverity = SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity);
|
|
159
|
+
return bySeverity !== 0 ? bySeverity : a.key.localeCompare(b.key);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const findings: Finding[] = [];
|
|
163
|
+
const suppressed: Finding[] = [];
|
|
164
|
+
ordered.forEach((agg, i) => {
|
|
165
|
+
const finding: Finding = {
|
|
166
|
+
id: `avc-${String(i + 1).padStart(3, '0')}`,
|
|
167
|
+
severity: agg.severity,
|
|
168
|
+
category: agg.category,
|
|
169
|
+
location: agg.location,
|
|
170
|
+
description: agg.description,
|
|
171
|
+
raised_by: agg.raised_by,
|
|
172
|
+
refuted_by: agg.refuted_by,
|
|
173
|
+
confidence: severityQuorum(agg.raised_by.length, agg.refuted_by.length, skepticCount),
|
|
174
|
+
};
|
|
175
|
+
if (isSuppressedFalsePositive(agg.raised_by.length, agg.refuted_by.length, skepticCount)) {
|
|
176
|
+
suppressed.push(finding);
|
|
177
|
+
} else {
|
|
178
|
+
findings.push(finding);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
panel: { models, skeptic_count: skepticCount },
|
|
184
|
+
findings,
|
|
185
|
+
false_positives_suppressed: suppressed,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
* silent pass.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// 'council' is the opt-in adversarial-verification-council escalation (Mode 9,
|
|
17
|
+
// ADR-122): default-off, advisory, recorded but never returned by
|
|
18
|
+
// selectVerifyMode — the orchestrator chooses it for high-risk changes.
|
|
19
|
+
export type VerifyMode = 'deterministic' | 'judge' | 'none' | 'council';
|
|
17
20
|
|
|
18
21
|
/** Max changed-line count (inclusive) that still counts as trivial. */
|
|
19
22
|
export const TRIVIAL_CHANGE_FLOOR = 20;
|
|
@@ -84,6 +84,17 @@
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
},
|
|
87
|
+
"consistency": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"additionalProperties": true,
|
|
90
|
+
"properties": {
|
|
91
|
+
"cross_source": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"enum": ["on", "auto", "off"],
|
|
94
|
+
"description": "Consumed by the cross-source-consistency rule. When the agent works from multiple sources (ticket text, an attached image/mockup, the spec, the codebase) it checks them against each other and asks before proceeding on a discrepancy. on (default) = surface every real cross-source contradiction / silent-scope-expansion as one question; auto = surface only high-confidence contradictions, state low-confidence as an assumption; off = no cross-source checking."
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
87
98
|
"code_style": {
|
|
88
99
|
"type": "object",
|
|
89
100
|
"additionalProperties": true,
|
|
@@ -26,6 +26,7 @@ const qualityCadence = z.enum(['end_of_roadmap', 'per_phase', 'per_step']);
|
|
|
26
26
|
const regenCadence = z.enum(['per_step', 'every_5_steps', 'phase_boundary']);
|
|
27
27
|
const worktreeMode = z.enum(['off', 'on', 'ask']);
|
|
28
28
|
const fidelityMode = z.enum(['strict', 'structural', 'hard-floor']);
|
|
29
|
+
const crossSourceMode = z.enum(['on', 'auto', 'off']);
|
|
29
30
|
const richSkillsMode = z.enum(['on', 'ask', 'off']);
|
|
30
31
|
const replyMethod = z.enum(['replies_endpoint', 'create_review_comment', 'auto']);
|
|
31
32
|
const confidenceBand = z.enum(['off', 'low', 'medium', 'high']);
|
|
@@ -203,6 +204,11 @@ export const settingsSchema = z.object({
|
|
|
203
204
|
'How strictly the agent must follow a user-provided prototype / mockup / design system (consumed by the design-fidelity rule). strict = build 1:1, every visible deviation needs confirmation; structural = structure locked, silent gaps fillable with a stated assumption; hard-floor = any deviation is never autonomous.',
|
|
204
205
|
),
|
|
205
206
|
}).default({ fidelity_mode: 'strict' }),
|
|
207
|
+
consistency: z.object({
|
|
208
|
+
cross_source: crossSourceMode.default('on').describe(
|
|
209
|
+
'Consumed by the cross-source-consistency rule. When the agent works from multiple sources (ticket text, an attached image/mockup, the spec, the codebase) it checks them against each other and asks before proceeding on a discrepancy — instead of silently guessing. on (default) = surface every real cross-source contradiction / silent-scope-expansion as one question; auto = surface only high-confidence contradictions, state low-confidence as an assumption; off = no cross-source checking.',
|
|
210
|
+
),
|
|
211
|
+
}).default({ cross_source: 'on' }),
|
|
206
212
|
tokens: z.object({
|
|
207
213
|
rich_skills: richSkillsMode.default('on').describe(
|
|
208
214
|
'Whether skills marked token_budget_class: rich may load in full (exempt from telegraph-speak + thin-projector trimming), consumed by the token-budget-discipline rule. on = allowed (default); off = fall back to standard condensed behavior; ask = surface an estimated token delta (tokens, not dollars) and ask once per session before loading.',
|
|
@@ -282,6 +288,9 @@ export const settingsSchema = z.object({
|
|
|
282
288
|
max_parallel: z.number().int().min(1).default(3).describe(
|
|
283
289
|
'Hard cap on subagents running in parallel during /do-in-parallel, /do-competitively, and /judge runs. Raise for faster fan-out, lower if you hit rate limits or want lower token spend.',
|
|
284
290
|
),
|
|
291
|
+
adversarial_council: z.enum(['off', 'ask', 'on']).default('off').describe(
|
|
292
|
+
'Opt-in adversarial-verification-council mode (subagent-orchestration Mode 9, ADR-122). off (default) = never runs; ask = offer it on an explicit high-risk change; on = auto-run on high-risk changes. Advisory only — a panel of distinct-model skeptics red-teams a real change for defect FINDING coverage and NEVER auto-gates it (Hard Floor). Stays default-off until the adversarial-council-finding-coverage claim is backed.',
|
|
293
|
+
),
|
|
285
294
|
}),
|
|
286
295
|
worktrees: z.object({
|
|
287
296
|
mode: worktreeMode.default('ask').describe(
|