@event4u/agent-config 14.13.0 → 14.14.1
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 +138 -376
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +36 -0
- 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/code-comment-discipline.md +6 -0
- 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/scripts/archive_completed_roadmaps.ts +6 -6
- package/dist/agent-src/scripts/guarded_baseline.ts +22 -7
- package/dist/agent-src/scripts/stubs_due.ts +118 -2
- package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -8
- 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 +39 -2
- package/dist/cli-delegate/cmd_doctor.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +7 -7
- 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 +1696 -812
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +297 -115
- package/docs/guidelines/agent-infra/guarded-baseline.md +1 -1
- package/docs/guidelines/code-clarity.md +18 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
- package/src/agent-src/scripts/guarded_baseline.ts +22 -7
- package/src/agent-src/scripts/stubs_due.ts +118 -2
- package/src/agent-src/scripts/update_roadmap_progress.ts +8 -8
- package/src/config/assurance-capability-registry.json +1 -1
- package/src/config/estate-count-budget.json +20 -1
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +211 -1
- package/src/config/gate-reachability-exemptions.json +1 -0
- package/src/config/gate-violation-baselines.json +9 -3
- package/src/config/preamble-payload-budget.json +19 -11
- package/src/config/rule-stub-ceilings.json +14 -7
- 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 -4
- package/src/scripts/_lib/activation_ladder.ts +47 -3
- package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
- package/src/scripts/_lib/adr_frontmatter.ts +26 -0
- package/src/scripts/_lib/branch_convergence.ts +186 -0
- package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
- package/src/scripts/_lib/carrier_status.ts +118 -0
- package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
- package/src/scripts/_lib/context_observation.ts +132 -0
- package/src/scripts/_lib/continuation_ladder.ts +188 -0
- package/src/scripts/_lib/corpus_manifest.ts +726 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +18 -2
- package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
- package/src/scripts/_lib/evaluation_cascade.ts +452 -0
- package/src/scripts/_lib/evolution_roi.ts +562 -0
- package/src/scripts/_lib/experiment_binding.ts +95 -0
- package/src/scripts/_lib/harness_evolution_guards.ts +10 -0
- package/src/scripts/_lib/ignored_blocker.ts +8 -1
- package/src/scripts/_lib/lexical_shortlist.ts +215 -0
- package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
- package/src/scripts/_lib/outcome_envelope.ts +6 -1
- package/src/scripts/_lib/outcome_vocabularies.ts +135 -5
- package/src/scripts/_lib/pathology_archive.ts +393 -0
- package/src/scripts/_lib/promotion_capability.ts +315 -0
- package/src/scripts/_lib/promotion_evidence.ts +414 -0
- package/src/scripts/_lib/promotion_review.ts +294 -0
- package/src/scripts/_lib/prompt_shape.ts +92 -0
- package/src/scripts/_lib/release_highlights.ts +79 -1
- package/src/scripts/_lib/release_material.ts +125 -2
- package/src/scripts/_lib/repeated_failure.ts +19 -2
- package/src/scripts/_lib/routing_corpus.ts +307 -0
- package/src/scripts/_lib/routing_index_input.ts +104 -0
- package/src/scripts/_lib/rule_injection.ts +22 -1
- package/src/scripts/_lib/runtime_journal.ts +11 -4
- package/src/scripts/_lib/semantic_noop.ts +167 -0
- package/src/scripts/activation_receipt.ts +258 -0
- package/src/scripts/ai_council/cross_exam.ts +128 -0
- package/src/scripts/ai_council/explain_route.ts +157 -0
- package/src/scripts/ai_council/information_gain.ts +293 -0
- package/src/scripts/ai_council/leakage_corpus.ts +385 -0
- package/src/scripts/ai_council/leakage_patterns.ts +453 -0
- package/src/scripts/ai_council/minority_retention.ts +171 -0
- package/src/scripts/ai_council/recouncil_savings.ts +299 -0
- package/src/scripts/ai_council/replay_route.ts +223 -0
- package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
- package/src/scripts/ai_council/routing_training_row.ts +197 -0
- package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
- package/src/scripts/check_adr_frontmatter.ts +4 -9
- 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_estate_count.ts +205 -24
- package/src/scripts/check_release_highlights.ts +113 -14
- package/src/scripts/check_roadmap_trackable.ts +17 -2
- package/src/scripts/check_web_launch_readiness.ts +310 -20
- package/src/scripts/corpus_manifest.ts +235 -0
- package/src/scripts/evolution_lab.ts +234 -9
- package/src/scripts/generate_capability_matrix.ts +16 -9
- package/src/scripts/grade_target_readiness.ts +221 -2
- package/src/scripts/hook_effect_doctor.ts +47 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +28 -6
- package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/run_continuation_hook.ts +78 -88
- package/src/scripts/hooks/session_eol_hook.ts +12 -1
- package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
- package/src/scripts/install-hooks.sh +18 -0
- package/src/scripts/language_mirror_hook.ts +14 -2
- package/src/scripts/lint_carrier_integrity.ts +820 -0
- package/src/scripts/lint_code_comments.ts +748 -0
- package/src/scripts/lint_plan_risk_register.ts +39 -19
- package/src/scripts/lint_promotion_paths.ts +779 -0
- package/src/scripts/llm_propose.ts +256 -0
- package/src/scripts/measure_delivery_sets.ts +361 -0
- package/src/scripts/measure_routing_signal.ts +307 -0
- package/src/scripts/model_rule_injection.ts +19 -3
- package/src/scripts/probe_council_retention.ts +495 -0
- package/src/scripts/release.ts +21 -37
- package/src/scripts/release_drill.ts +199 -0
- package/src/scripts/release_env.ts +35 -0
- package/src/scripts/release_publication.ts +142 -0
- 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
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* `activation_receipt` — observe how far a shipped artefact climbed the
|
|
4
|
+
* activation ladder, and append the receipt to the audit ledger.
|
|
5
|
+
*
|
|
6
|
+
* `road-to-governed-evidence-production` step 1.1's PRODUCTION CALLER. The
|
|
7
|
+
* producer library (`_lib/activation_receipt_producer.ts`) holds the discipline;
|
|
8
|
+
* this file holds the three real observations and the write. It exists because
|
|
9
|
+
* a library nothing calls has no coverage — the same defect the roadmap kept
|
|
10
|
+
* open on `assertCheapestFirst`. **Corrected 2026-09-01:** that one is now half
|
|
11
|
+
* closed. `assertCheapestFirst` has two production callers in
|
|
12
|
+
* `_lib/llm_candidate_proposer.ts` (`:417`, `:446`), so it no longer polices a
|
|
13
|
+
* population of zero; what it still does not police is a SPENT population,
|
|
14
|
+
* which is what AC-3 stays open on.
|
|
15
|
+
*
|
|
16
|
+
* ## What it observes, and from where
|
|
17
|
+
*
|
|
18
|
+
* | Rung | Source | What is read |
|
|
19
|
+
* |---|---|---|
|
|
20
|
+
* | `eligible` | `source-tree` | the authored artefact under `src/` |
|
|
21
|
+
* | `selected` | `discovery-manifest` | `dist/discovery/discovery-manifest.json`, `install.default` |
|
|
22
|
+
* | `projected` | `host-projection` | the host tree under `.claude/` |
|
|
23
|
+
*
|
|
24
|
+
* `delivered`, `visible` and `adhered` have no admitted source and are therefore
|
|
25
|
+
* ABSENT from every receipt this writes — which the ladder reads as `unknown`
|
|
26
|
+
* and keeps out of every rate's denominator. That is the honest state of
|
|
27
|
+
* coverage today, not a placeholder to be filled with a guess.
|
|
28
|
+
*
|
|
29
|
+
* ## Costs and boundaries
|
|
30
|
+
*
|
|
31
|
+
* Zero model calls, no network, no subprocess: three `statSync` calls and one
|
|
32
|
+
* JSON read. Admissible under the `metered-backend-park` blocker, which forbids
|
|
33
|
+
* a live model harness rather than a filesystem read. Claims:
|
|
34
|
+
* `docs/contracts/activation-receipt-trust-boundary.md`.
|
|
35
|
+
*/
|
|
36
|
+
import * as fs from 'node:fs';
|
|
37
|
+
import * as path from 'node:path';
|
|
38
|
+
import * as crypto from 'node:crypto';
|
|
39
|
+
import { fileURLToPath } from 'node:url';
|
|
40
|
+
|
|
41
|
+
import {
|
|
42
|
+
appendActivationLine,
|
|
43
|
+
buildActivationLine,
|
|
44
|
+
buildActivationReceipt,
|
|
45
|
+
observeProjection,
|
|
46
|
+
observeSelection,
|
|
47
|
+
observeSourceTree,
|
|
48
|
+
type RungObservation,
|
|
49
|
+
} from './_lib/activation_receipt_producer.js';
|
|
50
|
+
import { LADDER_RUNGS, firstStall, type ActivationReceipt } from './_lib/activation_ladder.js';
|
|
51
|
+
|
|
52
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
53
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
54
|
+
|
|
55
|
+
export const EXIT_OK = 0;
|
|
56
|
+
export const EXIT_USAGE = 2;
|
|
57
|
+
|
|
58
|
+
const USAGE = `usage: activation_receipt --rule <id> | --skill <id> [options]
|
|
59
|
+
|
|
60
|
+
--rule ID a rule artefact, e.g. commit-policy
|
|
61
|
+
--skill ID a skill artefact, e.g. code-review
|
|
62
|
+
--host DIR host projection root (default: .claude)
|
|
63
|
+
--root DIR repository root (default: this checkout)
|
|
64
|
+
--dry-run observe and print; do NOT append to the audit ledger
|
|
65
|
+
|
|
66
|
+
Observes the eligible / selected / projected rungs and appends one
|
|
67
|
+
audit-log-v1 line carrying an \`activation\` object. Zero model calls.
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
/** Where the artefact lives, per kind, in each of the two trees. */
|
|
71
|
+
interface ArtefactPaths {
|
|
72
|
+
readonly id: string;
|
|
73
|
+
readonly sourceRel: string;
|
|
74
|
+
readonly hostRel: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function artefactPaths(kind: 'rule' | 'skill', id: string): ArtefactPaths {
|
|
78
|
+
return kind === 'rule'
|
|
79
|
+
? { id, sourceRel: path.join('rules', `${id}.md`), hostRel: path.join('rules', `${id}.md`) }
|
|
80
|
+
: {
|
|
81
|
+
id,
|
|
82
|
+
sourceRel: path.join('skills', id, 'SKILL.md'),
|
|
83
|
+
hostRel: path.join('skills', id, 'SKILL.md'),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The ids the discovery manifest admits into the DEFAULT install.
|
|
89
|
+
*
|
|
90
|
+
* Returns an EMPTY set when the manifest is missing or unreadable, and
|
|
91
|
+
* `observeSelection` reads an empty set as "no manifest" rather than as "the
|
|
92
|
+
* manifest selected nothing" — the absent/negative split again, at the one place
|
|
93
|
+
* a build artefact can legitimately be missing.
|
|
94
|
+
*/
|
|
95
|
+
export function selectedIdsFromManifest(manifestPath: string): Set<string> {
|
|
96
|
+
let doc: unknown;
|
|
97
|
+
try {
|
|
98
|
+
doc = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
99
|
+
} catch {
|
|
100
|
+
return new Set();
|
|
101
|
+
}
|
|
102
|
+
const artefacts = (doc as { artefacts?: unknown }).artefacts;
|
|
103
|
+
if (!Array.isArray(artefacts)) return new Set();
|
|
104
|
+
const out = new Set<string>();
|
|
105
|
+
for (const a of artefacts) {
|
|
106
|
+
const rec = a as { path?: unknown; install?: { default?: unknown } };
|
|
107
|
+
if (typeof rec.path !== 'string') continue;
|
|
108
|
+
if (rec.install?.default !== true) continue;
|
|
109
|
+
out.add(artefactIdFromPath(rec.path));
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The artefact id a manifest path names.
|
|
116
|
+
*
|
|
117
|
+
* `src/rules/commit-policy.md` -> `commit-policy`;
|
|
118
|
+
* `src/skills/code-review/SKILL.md` -> `code-review`. Any other path yields its
|
|
119
|
+
* basename without extension, which is not an id anything will match — a
|
|
120
|
+
* deliberate non-match rather than a guess.
|
|
121
|
+
*/
|
|
122
|
+
export function artefactIdFromPath(p: string): string {
|
|
123
|
+
const parts = p.split('/');
|
|
124
|
+
const last = parts[parts.length - 1] ?? '';
|
|
125
|
+
if (last === 'SKILL.md') return parts[parts.length - 2] ?? '';
|
|
126
|
+
return last.replace(/\.md$/, '');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface ObserveResult {
|
|
130
|
+
readonly receipt: ActivationReceipt | null;
|
|
131
|
+
readonly errors: readonly string[];
|
|
132
|
+
readonly observations: readonly RungObservation[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The three observations, in ladder order. Pure apart from the reads. */
|
|
136
|
+
export function observeArtefact(
|
|
137
|
+
root: string,
|
|
138
|
+
hostRoot: string,
|
|
139
|
+
kind: 'rule' | 'skill',
|
|
140
|
+
id: string,
|
|
141
|
+
): ObserveResult {
|
|
142
|
+
const p = artefactPaths(kind, id);
|
|
143
|
+
const observations: RungObservation[] = [];
|
|
144
|
+
const eligible = observeSourceTree(path.join(root, 'src'), p.sourceRel);
|
|
145
|
+
if (eligible !== undefined) observations.push(eligible);
|
|
146
|
+
const selected = observeSelection(
|
|
147
|
+
selectedIdsFromManifest(path.join(root, 'dist', 'discovery', 'discovery-manifest.json')),
|
|
148
|
+
id,
|
|
149
|
+
);
|
|
150
|
+
if (selected !== undefined) observations.push(selected);
|
|
151
|
+
const projected = observeProjection(hostRoot, p.hostRel);
|
|
152
|
+
if (projected !== undefined) observations.push(projected);
|
|
153
|
+
|
|
154
|
+
const { receipt, errors } = buildActivationReceipt(id, observations);
|
|
155
|
+
return { receipt, errors, observations };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface Flags {
|
|
159
|
+
kind?: 'rule' | 'skill';
|
|
160
|
+
id?: string;
|
|
161
|
+
host?: string;
|
|
162
|
+
root?: string;
|
|
163
|
+
dryRun: boolean;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function parse(argv: readonly string[]): Flags | string {
|
|
167
|
+
const f: Flags = { dryRun: false };
|
|
168
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
169
|
+
const a = argv[i];
|
|
170
|
+
if (a === '--dry-run') {
|
|
171
|
+
f.dryRun = true;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const v = argv[i + 1];
|
|
175
|
+
if (v === undefined || v.startsWith('--')) return `${a} requires a value`;
|
|
176
|
+
i += 1;
|
|
177
|
+
if (a === '--rule') {
|
|
178
|
+
f.kind = 'rule';
|
|
179
|
+
f.id = v;
|
|
180
|
+
} else if (a === '--skill') {
|
|
181
|
+
f.kind = 'skill';
|
|
182
|
+
f.id = v;
|
|
183
|
+
} else if (a === '--host') {
|
|
184
|
+
f.host = v;
|
|
185
|
+
} else if (a === '--root') {
|
|
186
|
+
f.root = v;
|
|
187
|
+
} else {
|
|
188
|
+
return `unknown option '${a}'`;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return f;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function main(argv?: string[]): number {
|
|
195
|
+
const args = argv ?? process.argv.slice(2);
|
|
196
|
+
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
|
|
197
|
+
process.stdout.write(USAGE);
|
|
198
|
+
return args.length === 0 ? EXIT_USAGE : EXIT_OK;
|
|
199
|
+
}
|
|
200
|
+
const parsed = parse(args);
|
|
201
|
+
if (typeof parsed === 'string') {
|
|
202
|
+
process.stderr.write(`activation_receipt: error: ${parsed}\n${USAGE}`);
|
|
203
|
+
return EXIT_USAGE;
|
|
204
|
+
}
|
|
205
|
+
if (parsed.kind === undefined || parsed.id === undefined || parsed.id === '') {
|
|
206
|
+
process.stderr.write(`activation_receipt: error: one of --rule or --skill is required\n${USAGE}`);
|
|
207
|
+
return EXIT_USAGE;
|
|
208
|
+
}
|
|
209
|
+
const root = parsed.root ?? REPO_ROOT;
|
|
210
|
+
const hostRoot = parsed.host ?? path.join(root, '.claude');
|
|
211
|
+
|
|
212
|
+
const { receipt, errors } = observeArtefact(root, hostRoot, parsed.kind, parsed.id);
|
|
213
|
+
if (receipt === null) {
|
|
214
|
+
for (const e of errors) process.stderr.write(`activation_receipt: refused — ${e}\n`);
|
|
215
|
+
return 1;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const stall = firstStall(receipt);
|
|
219
|
+
const observedCount = Object.keys(receipt.rungs).length;
|
|
220
|
+
process.stdout.write(
|
|
221
|
+
`activation_receipt · ${receipt.artefact} · observed ${observedCount}/${LADDER_RUNGS.length} rung(s) · ` +
|
|
222
|
+
(stall === null
|
|
223
|
+
? 'climbed every observed rung · family=none\n'
|
|
224
|
+
: `stalled at ${stall.stage} · family=${stall.family}\n`),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const ts = new Date().toISOString();
|
|
228
|
+
const { line, errors: lineErrors } = buildActivationLine({
|
|
229
|
+
artefact: receipt.artefact,
|
|
230
|
+
rungs: receipt.rungs,
|
|
231
|
+
ts,
|
|
232
|
+
id: crypto.randomUUID(),
|
|
233
|
+
});
|
|
234
|
+
if (line === null) {
|
|
235
|
+
for (const e of lineErrors) process.stderr.write(`activation_receipt: refused — ${e}\n`);
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
if (parsed.dryRun) {
|
|
239
|
+
process.stdout.write(`${JSON.stringify(line)}\n`);
|
|
240
|
+
return EXIT_OK;
|
|
241
|
+
}
|
|
242
|
+
const file = appendActivationLine(root, line, ts);
|
|
243
|
+
process.stdout.write(`activation_receipt · appended to ${path.relative(root, file)}\n`);
|
|
244
|
+
return EXIT_OK;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function _isCliEntry(): boolean {
|
|
248
|
+
try {
|
|
249
|
+
if (process.argv[1] === undefined) return false;
|
|
250
|
+
return fs.realpathSync(_HERE) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
251
|
+
} catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (_isCliEntry()) {
|
|
257
|
+
process.exit(main());
|
|
258
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Targeted cross-examination — step 8.1.
|
|
3
|
+
*
|
|
4
|
+
* `road-to-inbox-harvest-2026-08-e-council-topology-evidence` step 8.1: *"Select
|
|
5
|
+
* a disputed finding, a conflicting pair, or a correct-looking minority claim,
|
|
6
|
+
* and ask focused rebuttal questions"*, verified by *"the cross-exam prompt
|
|
7
|
+
* names the exact disputed claim"*.
|
|
8
|
+
*
|
|
9
|
+
* ## "Names the exact claim" is a byte-level obligation, not a summary
|
|
10
|
+
*
|
|
11
|
+
* The failure this prevents is a composer that paraphrases: *"one reviewer
|
|
12
|
+
* questioned the index approach"* is a description of a dispute and gives the
|
|
13
|
+
* cross-examined model nothing to rebut. The prompt therefore carries the
|
|
14
|
+
* claim's **verbatim text**, unmodified and untruncated, and
|
|
15
|
+
* {@link crossExamNamesClaim} is the predicate that decides it — a substring
|
|
16
|
+
* check on the original string, which is the only form of "exact" that survives
|
|
17
|
+
* a later edit to the prompt's wording.
|
|
18
|
+
*
|
|
19
|
+
* ## The claim is untrusted, and is fenced
|
|
20
|
+
*
|
|
21
|
+
* A disputed claim is model output. It reaches the prompt through
|
|
22
|
+
* `wrapUntrustedBlocks` under a nonce, exactly as `build_peer_review_user_prompt`
|
|
23
|
+
* already does (`prompts.ts:932-942`) — a claim that could close its own fence
|
|
24
|
+
* could inject instructions into the cross-exam. Fencing does not modify the
|
|
25
|
+
* payload, which is what lets it coexist with the verbatim obligation above.
|
|
26
|
+
*
|
|
27
|
+
* ## Selection is deterministic and ordered, and the order is argued
|
|
28
|
+
*
|
|
29
|
+
* `conflicting-pair` outranks `disputed-finding` outranks `minority-claim`. A
|
|
30
|
+
* conflicting pair is the only kind where **at least one side is definitely
|
|
31
|
+
* wrong**, so the rebuttal has the highest information density; a lone disputed
|
|
32
|
+
* finding may resolve to "both are partly right"; a correct-looking minority
|
|
33
|
+
* claim is last because the majority may simply be right and the call buys the
|
|
34
|
+
* least. Ties break on the claim id, so selection is a pure function of input.
|
|
35
|
+
*
|
|
36
|
+
* Pure and offline: composes a prompt, never dispatches one.
|
|
37
|
+
*/
|
|
38
|
+
import { wrapUntrustedBlocks } from '../_lib/untrusted_content.js';
|
|
39
|
+
|
|
40
|
+
/** What kind of thing is being cross-examined. Priority order, strongest first. */
|
|
41
|
+
export const CROSS_EXAM_KINDS = ['conflicting-pair', 'disputed-finding', 'minority-claim'] as const;
|
|
42
|
+
export type CrossExamKind = (typeof CROSS_EXAM_KINDS)[number];
|
|
43
|
+
|
|
44
|
+
/** One candidate for cross-examination. */
|
|
45
|
+
export interface DisputedClaim {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly kind: CrossExamKind;
|
|
48
|
+
/** Neutral label of whoever asserted it (`Response-A`), never a provider name. */
|
|
49
|
+
readonly assertedBy: string;
|
|
50
|
+
/** THE CLAIM, VERBATIM. Never summarised, never truncated. */
|
|
51
|
+
readonly claim: string;
|
|
52
|
+
/** Neutral label of whoever disputes it, when there is one. */
|
|
53
|
+
readonly disputedBy?: string;
|
|
54
|
+
/** The opposing text, verbatim, when there is one. */
|
|
55
|
+
readonly counterClaim?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The focused questions. Fixed and shared across kinds on purpose: a per-kind
|
|
60
|
+
* question set is a second thing to keep in sync with the neutrality contract,
|
|
61
|
+
* and every question below is answerable about any of the three kinds.
|
|
62
|
+
*/
|
|
63
|
+
export const CROSS_EXAM_QUESTIONS: readonly string[] = [
|
|
64
|
+
'Is the claim above true as stated? Answer yes, no, or "true only under a condition" and name the condition.',
|
|
65
|
+
'What specific evidence would settle it? Name a file, a measurement, or a test — not a further opinion.',
|
|
66
|
+
'If it is false, what is the smallest correction that makes it true?',
|
|
67
|
+
'What would you have to believe for the opposing position to be correct?',
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const PREAMBLE = `You are cross-examining ONE specific claim from a council deliberation.
|
|
71
|
+
Do not review the artefact again, do not restate the debate, and do not
|
|
72
|
+
comment on any claim other than the one quoted below. Your entire reply
|
|
73
|
+
answers the numbered questions about that claim.`;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Pick the claim to cross-examine, or `null`.
|
|
77
|
+
*
|
|
78
|
+
* Deterministic: kind priority first, then claim id.
|
|
79
|
+
*/
|
|
80
|
+
export function selectCrossExamTarget(candidates: readonly DisputedClaim[]): DisputedClaim | null {
|
|
81
|
+
if (candidates.length === 0) return null;
|
|
82
|
+
const ranked = [...candidates].sort((a, b) => {
|
|
83
|
+
const ka = CROSS_EXAM_KINDS.indexOf(a.kind);
|
|
84
|
+
const kb = CROSS_EXAM_KINDS.indexOf(b.kind);
|
|
85
|
+
return ka - kb || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
86
|
+
});
|
|
87
|
+
return ranked[0] as DisputedClaim;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Compose the cross-exam prompt. The claim text is carried verbatim. */
|
|
91
|
+
export function buildCrossExamPrompt(target: DisputedClaim, opts: { readonly nonce?: string } = {}): string {
|
|
92
|
+
const blocks = [{ heading: `### The claim under examination (asserted by ${target.assertedBy})`, content: target.claim }];
|
|
93
|
+
if (target.counterClaim !== undefined) {
|
|
94
|
+
blocks.push({
|
|
95
|
+
heading: `### The opposing position (asserted by ${target.disputedBy ?? 'another reviewer'})`,
|
|
96
|
+
content: target.counterClaim,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const fenced = wrapUntrustedBlocks(blocks, opts.nonce === undefined ? {} : { nonce: opts.nonce });
|
|
100
|
+
const questions = CROSS_EXAM_QUESTIONS.map((q, i) => `${String(i + 1)}. ${q}`).join('\n');
|
|
101
|
+
return [
|
|
102
|
+
PREAMBLE,
|
|
103
|
+
'',
|
|
104
|
+
`Kind: ${target.kind} · claim id: ${target.id}`,
|
|
105
|
+
'',
|
|
106
|
+
fenced,
|
|
107
|
+
'',
|
|
108
|
+
'---',
|
|
109
|
+
'',
|
|
110
|
+
'Answer these, in order:',
|
|
111
|
+
'',
|
|
112
|
+
questions,
|
|
113
|
+
'',
|
|
114
|
+
].join('\n');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Does this prompt name the exact claim?
|
|
119
|
+
*
|
|
120
|
+
* A plain substring check on the ORIGINAL string. Deliberately not a similarity
|
|
121
|
+
* score: "exact" that tolerates a threshold is not exact, and the paraphrase is
|
|
122
|
+
* the failure mode. Both sides of a pair must be present when a pair was given.
|
|
123
|
+
*/
|
|
124
|
+
export function crossExamNamesClaim(prompt: string, target: DisputedClaim): boolean {
|
|
125
|
+
if (!prompt.includes(target.claim)) return false;
|
|
126
|
+
if (target.counterClaim !== undefined && !prompt.includes(target.counterClaim)) return false;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Free explain mode — step 12.2.
|
|
3
|
+
*
|
|
4
|
+
* `road-to-inbox-harvest-2026-08-e-council-topology-evidence` step 12.2: *"Add
|
|
5
|
+
* a free explain mode: why task-side orchestration resolved to council, which
|
|
6
|
+
* topology would run, estimated spend and calls, evidence source — with no paid
|
|
7
|
+
* model call to explain routing"*, verified by *"explain mode issues zero
|
|
8
|
+
* provider calls"*.
|
|
9
|
+
*
|
|
10
|
+
* ## Free is the load-bearing word, and it is structural
|
|
11
|
+
*
|
|
12
|
+
* This module imports **no client, no transport and no dispatch**. It reads
|
|
13
|
+
* `explainLadder` (`_lib/judgment_ladder.ts:499`, itself regex-only) and
|
|
14
|
+
* `estimate_cost` (`pricing.ts:121`, arithmetic over a price table). There is
|
|
15
|
+
* nothing here to make a call WITH, which is a stronger guarantee than a
|
|
16
|
+
* documented promise not to — and the test asserts the import graph rather than
|
|
17
|
+
* trusting this paragraph.
|
|
18
|
+
*
|
|
19
|
+
* ## Three of the four fields are answerable today; one is not
|
|
20
|
+
*
|
|
21
|
+
* 1. **why it resolved to council** — `explainLadder`'s full per-rung trail,
|
|
22
|
+
* including rungs that were evaluated-and-rejected with the detector's own
|
|
23
|
+
* reason and rungs the short-circuit never reached. Free.
|
|
24
|
+
* 2. **which topology would run** — NOT ANSWERABLE. 7.2 is open, no selector
|
|
25
|
+
* exists, and naming a topology here would be a guess dressed as an
|
|
26
|
+
* explanation. Reported as {@link TOPOLOGY_UNAVAILABLE}, never omitted:
|
|
27
|
+
* a field silently missing from an explanation reads as "not applicable".
|
|
28
|
+
* 3. **estimated spend and calls** — arithmetic over the configured members,
|
|
29
|
+
* the round count and the price table, with the subscription-seat
|
|
30
|
+
* distinction `pricing.ts` already draws: a non-billable seat contributes
|
|
31
|
+
* calls but **zero** dollars, and the estimate says so per member rather
|
|
32
|
+
* than folding it into a total.
|
|
33
|
+
* 4. **evidence source** — which artefact, if any, licenses the routing.
|
|
34
|
+
* `none` is a legal and common value today and is rendered as such.
|
|
35
|
+
*
|
|
36
|
+
* Pure and offline.
|
|
37
|
+
*/
|
|
38
|
+
import { explainLadder } from '../_lib/judgment_ladder.js';
|
|
39
|
+
import type { LadderExplanation, LadderInputs } from '../_lib/judgment_ladder.js';
|
|
40
|
+
import { estimate_cost, estimate_input_tokens } from './pricing.js';
|
|
41
|
+
import type { PriceTable } from './pricing.js';
|
|
42
|
+
|
|
43
|
+
/** The exact string the topology field carries while no selector exists. */
|
|
44
|
+
export const TOPOLOGY_UNAVAILABLE = 'unavailable — no topology selector exists (step 7.2 open)';
|
|
45
|
+
|
|
46
|
+
/** One member, as the estimate needs it. */
|
|
47
|
+
export interface EstimateMember {
|
|
48
|
+
readonly name: string;
|
|
49
|
+
readonly provider: string;
|
|
50
|
+
readonly model: string;
|
|
51
|
+
/**
|
|
52
|
+
* `false` for a vendor-official CLI seat running under the user's
|
|
53
|
+
* subscription auth — it contributes CALLS but no dollars, per
|
|
54
|
+
* `pricing.ts` § billable-aware aggregation.
|
|
55
|
+
*/
|
|
56
|
+
readonly billable: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SpendEstimate {
|
|
60
|
+
readonly calls: number;
|
|
61
|
+
readonly usd: number;
|
|
62
|
+
readonly perMember: readonly {
|
|
63
|
+
readonly name: string;
|
|
64
|
+
readonly calls: number;
|
|
65
|
+
readonly usd: number;
|
|
66
|
+
readonly billable: boolean;
|
|
67
|
+
}[];
|
|
68
|
+
/** Members whose calls are real but whose spend is zero. Named, not hidden. */
|
|
69
|
+
readonly nonBillableMembers: readonly string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface RouteExplanation {
|
|
73
|
+
/** Field 1 — free, from the regex-only resolver. */
|
|
74
|
+
readonly ladder: LadderExplanation;
|
|
75
|
+
/** Field 2 — always {@link TOPOLOGY_UNAVAILABLE} today. Present, never omitted. */
|
|
76
|
+
readonly topology: string;
|
|
77
|
+
/** Field 3 — arithmetic, no call. */
|
|
78
|
+
readonly spend: SpendEstimate;
|
|
79
|
+
/** Field 4 — the artefact licensing the route, or `'none'`. */
|
|
80
|
+
readonly evidenceSource: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Estimate calls and spend without issuing one.
|
|
85
|
+
*
|
|
86
|
+
* `calls` counts every member on every round; `usd` counts only the billable
|
|
87
|
+
* ones. Reporting a subscription seat's answer at API rates is the defect
|
|
88
|
+
* `pricing.ts` records finding on 2026-08-27, and this keeps the two figures
|
|
89
|
+
* separate rather than reproducing it.
|
|
90
|
+
*/
|
|
91
|
+
export function estimateSpend(
|
|
92
|
+
members: readonly EstimateMember[],
|
|
93
|
+
rounds: number,
|
|
94
|
+
questionText: string,
|
|
95
|
+
maxOutputTokens: number,
|
|
96
|
+
table: PriceTable,
|
|
97
|
+
): SpendEstimate {
|
|
98
|
+
const inputTokens = estimate_input_tokens(questionText);
|
|
99
|
+
const perMember = members.map((m) => {
|
|
100
|
+
const calls = Math.max(0, rounds);
|
|
101
|
+
if (!m.billable) return { name: m.name, calls, usd: 0, billable: false };
|
|
102
|
+
const c = estimate_cost(m.provider, m.model, inputTokens, maxOutputTokens, table);
|
|
103
|
+
return { name: m.name, calls, usd: (c.input_usd + c.output_usd) * calls, billable: true };
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
calls: perMember.reduce((a, m) => a + m.calls, 0),
|
|
107
|
+
usd: perMember.reduce((a, m) => a + m.usd, 0),
|
|
108
|
+
perMember,
|
|
109
|
+
nonBillableMembers: perMember.filter((m) => !m.billable).map((m) => m.name),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Build the whole explanation. No call is issued and none can be. */
|
|
114
|
+
export function explainRoute(
|
|
115
|
+
ladderInputs: LadderInputs,
|
|
116
|
+
members: readonly EstimateMember[],
|
|
117
|
+
rounds: number,
|
|
118
|
+
questionText: string,
|
|
119
|
+
maxOutputTokens: number,
|
|
120
|
+
table: PriceTable,
|
|
121
|
+
evidenceSource: string = 'none',
|
|
122
|
+
): RouteExplanation {
|
|
123
|
+
return {
|
|
124
|
+
ladder: explainLadder(ladderInputs),
|
|
125
|
+
topology: TOPOLOGY_UNAVAILABLE,
|
|
126
|
+
spend: estimateSpend(members, rounds, questionText, maxOutputTokens, table),
|
|
127
|
+
evidenceSource,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Human-readable rendering. Every one of the four fields appears, including the unavailable one. */
|
|
132
|
+
export function renderRouteExplanation(e: RouteExplanation): string {
|
|
133
|
+
const lines = [
|
|
134
|
+
'council: explain (free — no provider call was issued)',
|
|
135
|
+
'',
|
|
136
|
+
` resolution rung ${String(e.ladder.result.rung)} / ${e.ladder.result.verdict}`,
|
|
137
|
+
` because ${e.ladder.result.reason}`,
|
|
138
|
+
' ladder trail',
|
|
139
|
+
];
|
|
140
|
+
for (const r of e.ladder.trail) {
|
|
141
|
+
lines.push(` rung ${String(r.rung)} → ${r.resolves_to.padEnd(8)} ${r.status.padEnd(12)} ${r.reason}`);
|
|
142
|
+
}
|
|
143
|
+
if (e.ladder.no_spawn_reason !== undefined) lines.push(` no spawn ${e.ladder.no_spawn_reason}`);
|
|
144
|
+
lines.push(
|
|
145
|
+
'',
|
|
146
|
+
` topology ${e.topology}`,
|
|
147
|
+
` estimated ${String(e.spend.calls)} call(s), $${e.spend.usd.toFixed(4)}`,
|
|
148
|
+
);
|
|
149
|
+
for (const m of e.spend.perMember) {
|
|
150
|
+
lines.push(
|
|
151
|
+
` ${m.name.padEnd(14)} ${String(m.calls)} call(s), $${m.usd.toFixed(4)}` +
|
|
152
|
+
(m.billable ? '' : ' (subscription seat — calls are real, spend is not)'),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
lines.push(` evidence ${e.evidenceSource}`, '');
|
|
156
|
+
return lines.join('\n');
|
|
157
|
+
}
|