@event4u/agent-config 14.21.0 → 14.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +164 -187
- package/CREDITS.md +37 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +380 -5
- package/dist/agent-src/commands/analyze/repo.md +4 -2
- package/dist/agent-src/commands/pr/merge.md +47 -38
- package/dist/agent-src/commands/roadmap/next.md +8 -0
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +12 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
- package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
- package/dist/agent-src/rules/token-efficiency.md +26 -3
- package/dist/agent-src/scripts/_cli_entry.ts +51 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +120 -27
- package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
- package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/dist/agent-src/scripts/stubs_due.ts +4 -18
- package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/dist/agent-src/skills/code-intelligence/SKILL.md +82 -1
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +52 -2
- package/dist/agent-src/templates/agent-settings.md +0 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/_cli_entry.js +8 -0
- package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
- package/dist/cli-delegate/archival_sweep.js +8 -0
- package/dist/cli-delegate/archive_completed_roadmaps.js +542 -0
- package/dist/cli-delegate/check_update_banner.js +923 -0
- package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-2KKXJW25.js} +23 -9
- package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
- package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
- package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
- package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-IAI3XL43.js} +15 -3
- package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
- package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-KCNTUSPI.js} +7 -1
- package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
- package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
- package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
- package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
- package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
- package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
- package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
- package/dist/cli-delegate/cmd_conformance.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +45 -13
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_session_recycle.js +4 -14
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/cli-delegate/dashboard_mode.js +16 -0
- package/dist/cli-delegate/gate_budget.js +24 -0
- package/dist/cli-delegate/gate_execute.js +25 -0
- package/dist/cli-delegate/guarded_baseline.js +18 -0
- package/dist/cli-delegate/resume_probe.js +22 -0
- package/dist/cli-delegate/roadmap_gates.js +638 -0
- package/dist/cli-delegate/roadmap_set_step.js +173 -0
- package/dist/cli-delegate/stub_queue.js +47 -0
- package/dist/cli-delegate/stubs_due.js +38 -0
- package/dist/cli-delegate/ticket_bundles.js +59 -0
- package/dist/cli-delegate/update_roadmap_progress.js +60 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +13 -13
- 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 +1813 -946
- package/dist/install/claudeLayerCarriage.js +237 -0
- package/dist/install/claudePathsPlan.js +79 -0
- package/dist/install/hostLayerFingerprint.js +1 -1
- package/dist/install/install.mjs +21 -3
- package/dist/install/partitionEligibility.js +231 -117
- package/dist/install/ruleLayerPartition.js +10 -7
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +10870 -7641
- package/dist/scripts/_lib/lean_projection_mode.js +108 -0
- package/dist/scripts/_lib/router_match.js +243 -0
- package/dist/scripts/_lib/rule_injection.js +216 -0
- package/dist/scripts/condense.js +52 -45
- package/dist/scripts/project_thin_rules.js +139 -19
- package/dist/server/schemas/settings.js +16 -3
- package/docs/MIGRATION.md +105 -1
- package/docs/contracts/settings-classes.md +40 -8
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
- package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
- package/llms.txt +1 -1
- package/package.json +7 -4
- package/src/agent-src/scripts/_cli_entry.ts +51 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +120 -27
- package/src/agent-src/scripts/roadmap_gates.ts +4 -20
- package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/src/agent-src/scripts/stubs_due.ts +4 -18
- package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/src/config/agent-settings.template.yml +47 -1
- package/src/config/continuity-surface.json +516 -0
- package/src/config/estate-count-budget.json +1 -1
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +29 -8
- package/src/config/gate-violation-baselines.json +8 -8
- package/src/config/gitignore-block.txt +19 -0
- package/src/config/hook-token-budget.json +7 -6
- package/src/config/host-injection-effect.json +73 -0
- package/src/config/packed-binary-manifest.json +33 -0
- package/src/config/preamble-payload-budget.json +112 -105
- package/src/config/publish-surface.json +4 -2
- package/src/config/routing-coverage-seed.json +9 -2
- package/src/install/claudeLayerCarriage.ts +273 -0
- package/src/install/claudePathsPlan.ts +76 -0
- package/src/install/hostLayerFingerprint.ts +1 -1
- package/src/install/partitionEligibility.ts +261 -127
- package/src/install/ruleLayerPartition.ts +19 -23
- package/src/scripts/_cli/cmd_session_recycle.ts +2 -25
- package/src/scripts/_cli/handoff_sessions.ts +14 -44
- package/src/scripts/_dispatch.bash +77 -27
- package/src/scripts/_lib/auto_dispatch.ts +2 -2
- package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
- package/src/scripts/_lib/continuity_slot.ts +295 -0
- package/src/scripts/_lib/continuity_surface.ts +296 -0
- package/src/scripts/_lib/continuity_writer.ts +253 -0
- package/src/scripts/_lib/corpus_manifest.ts +26 -9
- package/src/scripts/_lib/hook_settings.ts +86 -1
- package/src/scripts/_lib/injection_effect.ts +151 -0
- package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
- package/src/scripts/_lib/lean_projection_mode.ts +127 -0
- package/src/scripts/_lib/packed_binary_predicate.ts +262 -0
- package/src/scripts/_lib/pin_resolver.ts +13 -0
- package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
- package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
- package/src/scripts/_lib/retired_status.ts +74 -0
- package/src/scripts/_lib/session_eol.ts +44 -0
- package/src/scripts/_lib/session_index_trust.ts +346 -0
- package/src/scripts/_lib/skill_catalogue.ts +156 -21
- package/src/scripts/_lib/standing_bound_ratchet.ts +154 -0
- package/src/scripts/_lib/subagent_capsule.ts +117 -8
- package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
- package/src/scripts/ai_council/api_transport.ts +5 -0
- package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
- package/src/scripts/ai_council/prompts.ts +9 -0
- package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
- package/src/scripts/audit_mcp_tools.ts +69 -25
- package/src/scripts/build_mcp_catalog.ts +24 -1
- package/src/scripts/capture_skill_catalogue.ts +24 -11
- package/src/scripts/check_candidate_lines.ts +499 -0
- package/src/scripts/check_continuity_surface.ts +475 -0
- package/src/scripts/check_dependency_floors.ts +13 -6
- package/src/scripts/check_estate_count.ts +40 -79
- package/src/scripts/check_host_tree_parity.ts +291 -0
- package/src/scripts/check_pack_size.ts +262 -23
- package/src/scripts/check_preamble_payload_budget.ts +136 -7
- package/src/scripts/check_roadmap_trackable.ts +19 -16
- package/src/scripts/check_rule_layer_partition.ts +54 -34
- package/src/scripts/check_rule_projection_integrity.ts +102 -2
- package/src/scripts/check_single_delivery.ts +97 -31
- package/src/scripts/check_update_banner.ts +13 -0
- package/src/scripts/ci_settle.ts +205 -5
- package/src/scripts/code_graph/build.ts +217 -15
- package/src/scripts/code_graph/cli.ts +334 -15
- package/src/scripts/code_graph/detect.ts +38 -0
- package/src/scripts/code_graph/extract.ts +11 -0
- package/src/scripts/code_graph/grammar_probe.ts +43 -0
- package/src/scripts/code_graph/loader.ts +88 -14
- package/src/scripts/code_graph/query.ts +144 -11
- package/src/scripts/code_graph/resolution_tiers.ts +238 -0
- package/src/scripts/code_graph/sqlite_store.ts +262 -6
- package/src/scripts/code_graph/types.ts +117 -3
- package/src/scripts/code_graph/validate.ts +26 -1
- package/src/scripts/code_graph/verbs.ts +590 -0
- package/src/scripts/condense.ts +54 -42
- package/src/scripts/corpus_manifest.ts +5 -5
- package/src/scripts/council_cli.ts +11 -12
- package/src/scripts/count_intent_disagreement.ts +426 -0
- package/src/scripts/handoff_context_hook.ts +118 -1
- package/src/scripts/hook_effect_doctor.ts +82 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +87 -12
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
- package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
- package/src/scripts/hooks/code_graph_context_hook.ts +201 -0
- package/src/scripts/hooks/concern_registry.ts +4 -2
- package/src/scripts/hooks/host_semantics.ts +1 -1
- package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
- package/src/scripts/hooks/push_settle_hook.ts +3 -2
- package/src/scripts/hooks/rule_inject_hook.ts +108 -21
- package/src/scripts/hooks/session_eol_hook.ts +120 -2
- package/src/scripts/hooks/skill_route_hook.ts +11 -4
- package/src/scripts/hot_context_hook.ts +62 -7
- package/src/scripts/inbox_source_census.ts +322 -0
- package/src/scripts/install-hooks.sh +82 -0
- package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
- package/src/scripts/lint_plan_risk_register.ts +13 -17
- package/src/scripts/lint_roadmap_blockers.ts +164 -1
- package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
- package/src/scripts/mcp_server/graph_tools.ts +294 -0
- package/src/scripts/mcp_server/path_util.ts +31 -0
- package/src/scripts/mcp_server/suggest_skill.ts +72 -0
- package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
- package/src/scripts/mcp_server/tools.ts +5 -69
- package/src/scripts/media/lib/adapter-contract.md +7 -2
- package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
- package/src/scripts/model_rule_injection.ts +61 -4
- package/src/scripts/project_thin_rules.ts +149 -16
- package/src/scripts/rdp_candidates_delta.ts +276 -0
- package/src/scripts/rdp_quality_eval.ts +217 -19
- package/src/scripts/release_mix_taxonomy.json +3 -2
- package/src/scripts/report_host_injection_effect.ts +189 -0
- package/src/scripts/report_layer_overlap.ts +41 -0
- package/src/scripts/report_skill_menu_census.ts +341 -0
- package/src/scripts/report_standing_payload_by_host.ts +650 -0
- package/src/scripts/schemas/agent-settings.schema.json +7 -1
- package/src/scripts/session_memory_index.ts +81 -8
- package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
- package/src/server/schemas/settings.ts +23 -4
- package/src/vendor/grammars/README.md +86 -0
- package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
- package/src/scripts/_lib/carrier_status.ts +0 -118
- package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
- package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* rdp_candidates_delta.ts — step 3.2's two numbers, computed from the two
|
|
4
|
+
* stored runs rather than asserted.
|
|
5
|
+
*
|
|
6
|
+
* Reads a baseline results file and a treatment results file in the envelope
|
|
7
|
+
* `rdp_quality_eval` writes, joins them on slot plus variant name, and prints:
|
|
8
|
+
*
|
|
9
|
+
* - the dim-5 delta, INTENTION-TO-TREAT: every treatment transcript against
|
|
10
|
+
* every baseline transcript on the same 16 slots. This is the primary
|
|
11
|
+
* reading, and it is primary on the 2026-09-08 council's instruction:
|
|
12
|
+
* conditioning on whether the model complied selects treatment outputs on
|
|
13
|
+
* post-treatment behaviour and biases the estimate. The compliance-
|
|
14
|
+
* conditioned figure is printed too, labelled exploratory.
|
|
15
|
+
* - the dim-1 reading on both arms. Dim 1 is the notes-first tripwire: dim 5
|
|
16
|
+
* rising while dim 1 falls means the line is being emitted into the reply
|
|
17
|
+
* instead of the notes, which is the third outcome step 3.3 must name
|
|
18
|
+
* rather than round away.
|
|
19
|
+
* - the output-token delta on the single-step (`ss`) slots, which are this
|
|
20
|
+
* corpus's trivial-task proxy and therefore where the published ~5 %
|
|
21
|
+
* L10 cost guard (`tests/reasoning-layer-eval/README.md` § Fail
|
|
22
|
+
* conditions) applies.
|
|
23
|
+
*
|
|
24
|
+
* A NOTE ON WHICH OVERHEAD THIS IS, because the corpus carries two and they
|
|
25
|
+
* are not the same number. `output_token_overhead_pct` stored in each run is
|
|
26
|
+
* arm-versus-arm WITHIN that run (orchestrated vs distributed). What step 3.2
|
|
27
|
+
* asks for is treatment-versus-baseline, which no stored field holds, so it is
|
|
28
|
+
* computed here from raw `output_tokens` on both sides.
|
|
29
|
+
*
|
|
30
|
+
* Exit: 0 printed · 1 usage/IO error · 2 a join hole (a slot or variant present
|
|
31
|
+
* on one side and not the other — a delta over a partial join is not a delta).
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import * as fs from 'node:fs';
|
|
35
|
+
import * as path from 'node:path';
|
|
36
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
37
|
+
|
|
38
|
+
import { checkReport } from './check_candidate_lines.js';
|
|
39
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
40
|
+
|
|
41
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
42
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
43
|
+
|
|
44
|
+
interface Variant {
|
|
45
|
+
text?: string;
|
|
46
|
+
output_tokens?: number;
|
|
47
|
+
score?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SlotRecord {
|
|
51
|
+
slot: string;
|
|
52
|
+
slug?: string;
|
|
53
|
+
mechanism?: string;
|
|
54
|
+
variants?: Record<string, Variant>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Exported so a test can build a synthetic run without widening to `unknown`. */
|
|
58
|
+
export interface Run {
|
|
59
|
+
date?: string;
|
|
60
|
+
mode?: string;
|
|
61
|
+
scorer_model?: string | null;
|
|
62
|
+
actual_cost_usd?: number;
|
|
63
|
+
results?: SlotRecord[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface Cell {
|
|
67
|
+
slot: string;
|
|
68
|
+
slug: string;
|
|
69
|
+
family: 'ms' | 'ss';
|
|
70
|
+
variant: string;
|
|
71
|
+
dim1: number | null;
|
|
72
|
+
dim5: number | null;
|
|
73
|
+
outputTokens: number | null;
|
|
74
|
+
/** Treatment side only: did the transcript actually carry a clean line? */
|
|
75
|
+
complied: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function familyOf(r: SlotRecord): 'ms' | 'ss' {
|
|
79
|
+
if (r.mechanism === 'stateless') return 'ss';
|
|
80
|
+
if (r.mechanism === 'multi-stage') return 'ms';
|
|
81
|
+
return (r.slug ?? '').startsWith('ss-') ? 'ss' : 'ms';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function num(v: unknown): number | null {
|
|
85
|
+
const n = Number(v);
|
|
86
|
+
return Number.isFinite(n) ? n : null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function flatten(run: Run, measureCompliance: boolean): Cell[] {
|
|
90
|
+
const out: Cell[] = [];
|
|
91
|
+
for (const r of run.results ?? []) {
|
|
92
|
+
for (const [variant, v] of Object.entries(r.variants ?? {})) {
|
|
93
|
+
let complied = false;
|
|
94
|
+
if (measureCompliance && typeof v.text === 'string' && v.text !== '') {
|
|
95
|
+
const verdict = checkReport(v.text);
|
|
96
|
+
complied = verdict.lines.length > 0 && verdict.findings.length === 0;
|
|
97
|
+
}
|
|
98
|
+
out.push({
|
|
99
|
+
slot: r.slot,
|
|
100
|
+
slug: r.slug ?? '',
|
|
101
|
+
family: familyOf(r),
|
|
102
|
+
variant,
|
|
103
|
+
dim1: num(v.score?.dim1),
|
|
104
|
+
dim5: num(v.score?.dim5),
|
|
105
|
+
outputTokens: num(v.output_tokens),
|
|
106
|
+
complied,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const mean = (xs: number[]): number | null =>
|
|
114
|
+
xs.length === 0 ? null : Math.round((xs.reduce((p, c) => p + c, 0) / xs.length) * 1000) / 1000;
|
|
115
|
+
|
|
116
|
+
const fmt = (x: number | null, digits = 3): string => (x === null ? 'n/a' : x.toFixed(digits));
|
|
117
|
+
|
|
118
|
+
export interface Report {
|
|
119
|
+
lines: string[];
|
|
120
|
+
joinHoles: string[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function compare(base: Run, treat: Run): Report {
|
|
124
|
+
const b = flatten(base, false);
|
|
125
|
+
const t = flatten(treat, true);
|
|
126
|
+
const key = (c: Cell): string => `${c.slot}/${c.variant}`;
|
|
127
|
+
const bMap = new Map(b.map((c) => [key(c), c]));
|
|
128
|
+
const tMap = new Map(t.map((c) => [key(c), c]));
|
|
129
|
+
|
|
130
|
+
const joinHoles: string[] = [];
|
|
131
|
+
for (const k of bMap.keys()) if (!tMap.has(k)) joinHoles.push(`treatment missing ${k}`);
|
|
132
|
+
for (const k of tMap.keys()) if (!bMap.has(k)) joinHoles.push(`baseline missing ${k}`);
|
|
133
|
+
|
|
134
|
+
const paired = [...bMap.keys()].filter((k) => tMap.has(k)).sort();
|
|
135
|
+
const L: string[] = [];
|
|
136
|
+
|
|
137
|
+
L.push(`baseline: ${base.mode ?? '?'} · ${String((base.results ?? []).length)} slot(s) · rater ${base.scorer_model ?? '?'}`);
|
|
138
|
+
L.push(`treatment: ${treat.mode ?? '?'} · ${String((treat.results ?? []).length)} slot(s) · rater ${treat.scorer_model ?? '?'}`);
|
|
139
|
+
L.push(`paired transcripts: ${String(paired.length)}`);
|
|
140
|
+
L.push('');
|
|
141
|
+
|
|
142
|
+
// ---- dim5, intention-to-treat (primary) ---------------------------------
|
|
143
|
+
const bDim5 = paired.map((k) => (bMap.get(k) as Cell).dim5).filter((x): x is number => x !== null);
|
|
144
|
+
const tDim5 = paired.map((k) => (tMap.get(k) as Cell).dim5).filter((x): x is number => x !== null);
|
|
145
|
+
const bM = mean(bDim5);
|
|
146
|
+
const tM = mean(tDim5);
|
|
147
|
+
L.push('dim5 — form-alternative surfacing (INTENTION-TO-TREAT, primary)');
|
|
148
|
+
L.push(` baseline mean ${fmt(bM)} / 3 (${bM === null ? 'n/a' : ((bM / 3) * 100).toFixed(1)} %) n=${String(bDim5.length)}`);
|
|
149
|
+
L.push(` treatment mean ${fmt(tM)} / 3 (${tM === null ? 'n/a' : ((tM / 3) * 100).toFixed(1)} %) n=${String(tDim5.length)}`);
|
|
150
|
+
if (bM !== null && tM !== null) {
|
|
151
|
+
const dPts = Math.round((tM - bM) * 1000) / 1000;
|
|
152
|
+
const dPp = Math.round(((tM - bM) / 3) * 1000) / 10;
|
|
153
|
+
L.push(` delta ${dPts > 0 ? '+' : ''}${fmt(dPts)} / 3 (${dPp > 0 ? '+' : ''}${dPp.toFixed(1)} pp of the 0-3 scale)`);
|
|
154
|
+
}
|
|
155
|
+
const bZero = bDim5.filter((x) => x === 0).length;
|
|
156
|
+
const tZero = tDim5.filter((x) => x === 0).length;
|
|
157
|
+
L.push(` scoring 0 (one form only): baseline ${String(bZero)}/${String(bDim5.length)} → treatment ${String(tZero)}/${String(tDim5.length)}`);
|
|
158
|
+
const bTop = bDim5.filter((x) => x >= 2).length;
|
|
159
|
+
const tTop = tDim5.filter((x) => x >= 2).length;
|
|
160
|
+
L.push(` scoring >= 2: baseline ${String(bTop)}/${String(bDim5.length)} → treatment ${String(tTop)}/${String(tDim5.length)}`);
|
|
161
|
+
L.push('');
|
|
162
|
+
|
|
163
|
+
// ---- dim1, the tripwire -------------------------------------------------
|
|
164
|
+
const bDim1 = paired.map((k) => (bMap.get(k) as Cell).dim1).filter((x): x is number => x !== null);
|
|
165
|
+
const tDim1 = paired.map((k) => (tMap.get(k) as Cell).dim1).filter((x): x is number => x !== null);
|
|
166
|
+
const b1 = mean(bDim1);
|
|
167
|
+
const t1 = mean(tDim1);
|
|
168
|
+
L.push('dim1 — notes-first adherence (TRIPWIRE: dim5 up while dim1 down = the line is in the reply)');
|
|
169
|
+
L.push(` baseline mean ${fmt(b1)} / 3 treatment mean ${fmt(t1)} / 3` +
|
|
170
|
+
(b1 !== null && t1 !== null ? ` delta ${t1 - b1 > 0 ? '+' : ''}${fmt(Math.round((t1 - b1) * 1000) / 1000)}` : ''));
|
|
171
|
+
L.push('');
|
|
172
|
+
|
|
173
|
+
// ---- compliance (exploratory) ------------------------------------------
|
|
174
|
+
const complied = paired.filter((k) => (tMap.get(k) as Cell).complied);
|
|
175
|
+
L.push('compliance — a well-formed Candidates line, zero shape findings');
|
|
176
|
+
L.push(` ${String(complied.length)} / ${String(paired.length)} treatment transcript(s)` +
|
|
177
|
+
` (${paired.length === 0 ? 'n/a' : ((complied.length / paired.length) * 100).toFixed(1)} %)`);
|
|
178
|
+
const cDim5 = complied.map((k) => (tMap.get(k) as Cell).dim5).filter((x): x is number => x !== null);
|
|
179
|
+
const cM = mean(cDim5);
|
|
180
|
+
L.push(` dim5 among compliant: ${fmt(cM)} / 3 n=${String(cDim5.length)} [EXPLORATORY — conditioning on`);
|
|
181
|
+
L.push(' post-treatment behaviour biases the estimate; the ITT figure above is primary]');
|
|
182
|
+
L.push('');
|
|
183
|
+
|
|
184
|
+
// ---- per family ---------------------------------------------------------
|
|
185
|
+
L.push('dim5 by family');
|
|
186
|
+
for (const fam of ['ms', 'ss'] as const) {
|
|
187
|
+
const ks = paired.filter((k) => (bMap.get(k) as Cell).family === fam);
|
|
188
|
+
const bf = mean(ks.map((k) => (bMap.get(k) as Cell).dim5).filter((x): x is number => x !== null));
|
|
189
|
+
const tf = mean(ks.map((k) => (tMap.get(k) as Cell).dim5).filter((x): x is number => x !== null));
|
|
190
|
+
L.push(` ${fam}: baseline ${fmt(bf)} → treatment ${fmt(tf)}` +
|
|
191
|
+
(bf !== null && tf !== null ? ` delta ${tf - bf > 0 ? '+' : ''}${fmt(Math.round((tf - bf) * 1000) / 1000)}` : '') +
|
|
192
|
+
` n=${String(ks.length)}`);
|
|
193
|
+
}
|
|
194
|
+
L.push('');
|
|
195
|
+
|
|
196
|
+
// ---- ss output-token overhead, treatment vs baseline -------------------
|
|
197
|
+
L.push('output-token overhead on the ss (single-step) slots — treatment vs baseline');
|
|
198
|
+
L.push(' the L10 cost guard is ~5 % on the trivial proxy (README.md § Fail conditions)');
|
|
199
|
+
const ssKeys = paired.filter((k) => (bMap.get(k) as Cell).family === 'ss');
|
|
200
|
+
const perSlot: number[] = [];
|
|
201
|
+
for (const k of ssKeys) {
|
|
202
|
+
const bo = (bMap.get(k) as Cell).outputTokens;
|
|
203
|
+
const to = (tMap.get(k) as Cell).outputTokens;
|
|
204
|
+
if (bo === null || to === null || bo === 0) continue;
|
|
205
|
+
const pct = Math.round(((to - bo) / bo) * 1000) / 10;
|
|
206
|
+
perSlot.push(pct);
|
|
207
|
+
L.push(` ${k.padEnd(18)} ${String(bo).padStart(5)} → ${String(to).padStart(5)} ${pct > 0 ? '+' : ''}${pct.toFixed(1)} %`);
|
|
208
|
+
}
|
|
209
|
+
const ssMean = mean(perSlot);
|
|
210
|
+
L.push(` mean per-slot overhead: ${ssMean === null ? 'n/a' : `${ssMean > 0 ? '+' : ''}${ssMean.toFixed(1)} %`} n=${String(perSlot.length)}`);
|
|
211
|
+
// Aggregate too: a mean of per-slot percentages is dominated by small
|
|
212
|
+
// denominators, so the token-weighted figure is printed beside it rather
|
|
213
|
+
// than instead of it.
|
|
214
|
+
const bTot = ssKeys.reduce((p, k) => p + ((bMap.get(k) as Cell).outputTokens ?? 0), 0);
|
|
215
|
+
const tTot = ssKeys.reduce((p, k) => p + ((tMap.get(k) as Cell).outputTokens ?? 0), 0);
|
|
216
|
+
if (bTot > 0) {
|
|
217
|
+
const agg = Math.round(((tTot - bTot) / bTot) * 1000) / 10;
|
|
218
|
+
L.push(` token-weighted overhead: ${agg > 0 ? '+' : ''}${agg.toFixed(1)} % (${String(bTot)} → ${String(tTot)} output tokens)`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return { lines: L, joinHoles };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
225
|
+
const bIdx = argv.indexOf('--baseline');
|
|
226
|
+
const tIdx = argv.indexOf('--treatment');
|
|
227
|
+
const bPath = bIdx === -1 ? undefined : argv[bIdx + 1];
|
|
228
|
+
const tPath = tIdx === -1 ? undefined : argv[tIdx + 1];
|
|
229
|
+
if (bPath === undefined || tPath === undefined) {
|
|
230
|
+
process.stderr.write('usage: rdp_candidates_delta --baseline <results.json> --treatment <results.json>\n');
|
|
231
|
+
return 1;
|
|
232
|
+
}
|
|
233
|
+
let base: Run;
|
|
234
|
+
let treat: Run;
|
|
235
|
+
try {
|
|
236
|
+
base = JSON.parse(fs.readFileSync(path.resolve(bPath), 'utf-8')) as Run;
|
|
237
|
+
treat = JSON.parse(fs.readFileSync(path.resolve(tPath), 'utf-8')) as Run;
|
|
238
|
+
} catch (e) {
|
|
239
|
+
process.stderr.write(`cannot read inputs: ${String(e)}\n`);
|
|
240
|
+
return 1;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const rep = compare(base, treat);
|
|
244
|
+
process.stdout.write(`${rep.lines.join('\n')}\n`);
|
|
245
|
+
|
|
246
|
+
const paired = rep.lines.find((l) => l.startsWith('paired transcripts:'));
|
|
247
|
+
const n = paired ? Number(paired.split(':')[1]) : 0;
|
|
248
|
+
try {
|
|
249
|
+
assertScanned({
|
|
250
|
+
gate: 'rdp_candidates_delta',
|
|
251
|
+
scanned: n,
|
|
252
|
+
units: 'paired transcript(s)',
|
|
253
|
+
roots: [bPath, tPath],
|
|
254
|
+
});
|
|
255
|
+
} catch (e) {
|
|
256
|
+
if (e instanceof DeadScopeError) {
|
|
257
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
258
|
+
return 1;
|
|
259
|
+
}
|
|
260
|
+
throw e;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (rep.joinHoles.length > 0) {
|
|
264
|
+
process.stderr.write(`\n❌ ${String(rep.joinHoles.length)} join hole(s) — a delta over a partial join is not a delta:\n`);
|
|
265
|
+
for (const h of rep.joinHoles) process.stderr.write(` ${h}\n`);
|
|
266
|
+
return 2;
|
|
267
|
+
}
|
|
268
|
+
process.stdout.write(`\nscanned: ${String(n)}\n`);
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
273
|
+
process.exit(main());
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export { REPO_ROOT };
|
|
@@ -108,6 +108,50 @@ Coordinate the links; do not let later steps run before earlier ones. On a
|
|
|
108
108
|
trivial or fully-specified task, do NOT force the chain (that is over-process) —
|
|
109
109
|
engage it only where the task is genuinely complex/ambiguous/interdependent.`;
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* The candidate-forms treatment, appended to EVERY arm by `--candidates`.
|
|
113
|
+
*
|
|
114
|
+
* Step 3.1 of `road-to-candidate-moves-floor`. The 2026-09-07 split council
|
|
115
|
+
* authorised instructing this line to the model under test in the treatment
|
|
116
|
+
* arm, "where it can be measured without being a shipped obligation on
|
|
117
|
+
* anyone", and blocked the shipped form. So it lives here, in the eval's own
|
|
118
|
+
* system prompt, and nowhere in `src/rules/` or `mandated-lines.md`.
|
|
119
|
+
*
|
|
120
|
+
* Appended to BOTH l6 arms rather than becoming a third arm, because the
|
|
121
|
+
* corpus has no per-slot field for a treatment and the baseline this must be
|
|
122
|
+
* compared against is the 32 stored transcripts — 16 slots × the same two RDP
|
|
123
|
+
* framings. Appending to both keeps the candidates block the single variable.
|
|
124
|
+
*
|
|
125
|
+
* The grammar is the one `check_candidate_lines.ts` parses, so compliance is
|
|
126
|
+
* measurable rather than eyeballed.
|
|
127
|
+
*/
|
|
128
|
+
const CANDIDATES_BLOCK = `## Candidate forms before the choice
|
|
129
|
+
|
|
130
|
+
Before you settle on HOW to solve this, generate the candidate solution FORMS
|
|
131
|
+
and emit one line naming them. Do this at the decision point, before the
|
|
132
|
+
answer — not as a trailing summary.
|
|
133
|
+
|
|
134
|
+
Emit exactly one line in this shape:
|
|
135
|
+
|
|
136
|
+
Candidates: K0 <keep the current form / change nothing> · A <form> [<the axis
|
|
137
|
+
it differs on>] · B <form> [<axis>] → <chosen id>; <the observation that
|
|
138
|
+
decided it>
|
|
139
|
+
|
|
140
|
+
Rules:
|
|
141
|
+
- \`K0\` is "keep the current form / change nothing". It is listed FIRST and it
|
|
142
|
+
is a real option — draw it when it is right.
|
|
143
|
+
- Each non-K0 candidate carries, in brackets, the AXIS it differs on. Three
|
|
144
|
+
candidates that differ only in where a helper lives are one candidate.
|
|
145
|
+
- The axes must be pairwise distinct.
|
|
146
|
+
- End with the choice and the OBSERVATION that decided between them — not a
|
|
147
|
+
preference.
|
|
148
|
+
- Where exactly one form is admissible (a framework fixes the extension point,
|
|
149
|
+
an existing contract fixes the location), emit a single candidate carrying
|
|
150
|
+
\`[forced: <the constraint>]\` and say what it forecloses. Never invent a
|
|
151
|
+
second form so the line looks full.
|
|
152
|
+
- On a genuinely trivial or fully-specified task, this line is not owed. Do not
|
|
153
|
+
emit it to prove you read this.`;
|
|
154
|
+
|
|
111
155
|
interface Slot {
|
|
112
156
|
n: string;
|
|
113
157
|
slug: string;
|
|
@@ -127,18 +171,22 @@ interface Variant {
|
|
|
127
171
|
score?: Record<string, unknown>;
|
|
128
172
|
}
|
|
129
173
|
|
|
130
|
-
function buildArms(mode: string): Array<[string, string]> {
|
|
174
|
+
function buildArms(mode: string, candidates = false): Array<[string, string]> {
|
|
131
175
|
const treat = `${BASE_SYSTEM}\n\n${RDP_BLOCK}`;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
[
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
176
|
+
const arms: Array<[string, string]> =
|
|
177
|
+
mode === 'l6'
|
|
178
|
+
? [
|
|
179
|
+
['distributed', treat],
|
|
180
|
+
['orchestrated', `${treat}\n\n${ORCHESTRATOR_PREAMBLE}`],
|
|
181
|
+
]
|
|
182
|
+
: [
|
|
183
|
+
['baseline', BASE_SYSTEM],
|
|
184
|
+
['treatment', treat],
|
|
185
|
+
];
|
|
186
|
+
if (!candidates) return arms;
|
|
187
|
+
// Appended to EVERY arm: the candidates block is the single variable
|
|
188
|
+
// against the stored run, so it must not co-vary with the arm.
|
|
189
|
+
return arms.map(([name, sys]) => [name, `${sys}\n\n${CANDIDATES_BLOCK}`]);
|
|
142
190
|
}
|
|
143
191
|
|
|
144
192
|
function approxTokens(text: string): number {
|
|
@@ -196,13 +244,20 @@ async function callAnthropic(
|
|
|
196
244
|
}
|
|
197
245
|
|
|
198
246
|
const SCORER_SYSTEM = `You are a strict, impartial evaluator. Score one assistant transcript against a
|
|
199
|
-
|
|
247
|
+
5-dimension rubric (0–3 each; 3 = excellent, 0 = absent/harmful). Dimensions:
|
|
200
248
|
1 notes-first adherence — reasoning kept out of the answer proper; answer = conclusions + evidence (not a raw chain-of-thought dump).
|
|
201
249
|
2 grounding — environment/constraints/info-gaps closed (or explicitly assumed) before designing; no jumping to a solution against assumptions.
|
|
202
250
|
3 premature-solution avoidance — the hardest/load-bearing unknown resolved before dependent work; no over-production of code/answer before grounding.
|
|
203
251
|
4 coherence / re-grounded summary — final answer readable by someone who saw none of the working thread; outcome-first; no arrow-chain shorthand.
|
|
252
|
+
5 form-alternative surfacing — was a materially DIFFERENT SOLUTION FORM generated and discriminated BEFORE the choice, rather than the one form the transcript started with being verified afterwards? This is NOT dimension 3: dim 3 scores which unknown was resolved first INSIDE a chosen form; dim 5 scores whether a second form was ever on the table at all. A transcript can be perfectly risk-first inside the only shape it ever considered, and that scores 0 here.
|
|
253
|
+
0 = one form only. A single approach appears and everything after it is verification. Good execution does NOT raise this.
|
|
254
|
+
1 = a second form is mentioned but not discriminated — no axis it differs on, or no reason given for dropping it.
|
|
255
|
+
2 = a second form is named on a stated axis, but nothing observable decides between them; the choice reads as preference.
|
|
256
|
+
3 = a second form is named on a stated axis AND the observation that decided between them is stated. "Keep the current form / change nothing" counts as a form.
|
|
257
|
+
Forced-form carve-out: where exactly one form is admissible (a framework fixes the extension point, an existing contract fixes the location), a transcript that NAMES the constraint foreclosing the alternatives scores 3, not 0.
|
|
258
|
+
Materially different means differing on an AXIS, not in placement: three options that differ only in where a helper lives are one option.
|
|
204
259
|
Also report reasoning_extraction_refusal: true ONLY if the model refused due to a meta/extraction instruction.
|
|
205
|
-
Respond with STRICT JSON only, no prose: {"dim1":N,"dim2":N,"dim3":N,"dim4":N,"reasoning_extraction_refusal":false,"note":"<=120 chars"}`;
|
|
260
|
+
Respond with STRICT JSON only, no prose: {"dim1":N,"dim2":N,"dim3":N,"dim4":N,"dim5":N,"reasoning_extraction_refusal":false,"note":"<=120 chars"}`;
|
|
206
261
|
|
|
207
262
|
async function scoreTranscript(
|
|
208
263
|
apiKey: string,
|
|
@@ -246,11 +301,118 @@ function parseArgs(argv: string[]): Record<string, string | boolean> {
|
|
|
246
301
|
return a;
|
|
247
302
|
}
|
|
248
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Mean over the dimensions the score actually carries.
|
|
306
|
+
*
|
|
307
|
+
* `dim5` is scored from 2026-09-07 and the 32 stored L6N transcripts predate
|
|
308
|
+
* it, so the divisor is the number of dimensions PRESENT, never a constant.
|
|
309
|
+
* A fixed `/5` would silently deflate every historical mean by a fifth and
|
|
310
|
+
* make the treatment-minus-baseline delta this eval exists to compute read as
|
|
311
|
+
* an improvement that is really an arithmetic artefact.
|
|
312
|
+
*/
|
|
249
313
|
function meanDims(score: Record<string, unknown> | undefined): number | null {
|
|
250
314
|
if (!score) return null;
|
|
251
|
-
const
|
|
315
|
+
const required = ['dim1', 'dim2', 'dim3', 'dim4'];
|
|
316
|
+
const ds = required.map((k) => Number(score[k]));
|
|
252
317
|
if (ds.some((x) => Number.isNaN(x))) return null;
|
|
253
|
-
|
|
318
|
+
if (score.dim5 !== undefined && !Number.isNaN(Number(score.dim5))) ds.push(Number(score.dim5));
|
|
319
|
+
return ds.reduce((p, c) => p + c, 0) / ds.length;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** One stored run: the envelope `--score-only` reads and writes back. */
|
|
323
|
+
interface StoredRun {
|
|
324
|
+
date?: string;
|
|
325
|
+
mode?: string;
|
|
326
|
+
scorer_model?: string | null;
|
|
327
|
+
results: Array<{
|
|
328
|
+
slot: string;
|
|
329
|
+
slug?: string;
|
|
330
|
+
band?: string;
|
|
331
|
+
prompt: string;
|
|
332
|
+
variants: Record<string, { text?: string; score?: Record<string, unknown> }>;
|
|
333
|
+
}>;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Re-score an existing results file with a rater, capturing nothing.
|
|
338
|
+
*
|
|
339
|
+
* The transcripts are read from disk and sent to the scorer; no assistant call
|
|
340
|
+
* is made, so the only spend is the rater. Every score the run already carried
|
|
341
|
+
* is REPLACED rather than merged: a half-updated score object where `dim5` came
|
|
342
|
+
* from one rater and `dim1`-`dim4` from another is not a reading of anything.
|
|
343
|
+
*/
|
|
344
|
+
async function scoreOnly(
|
|
345
|
+
sourcePath: string,
|
|
346
|
+
scorerModel: string,
|
|
347
|
+
outPath: string,
|
|
348
|
+
confirm: boolean,
|
|
349
|
+
): Promise<number> {
|
|
350
|
+
if (!fs.existsSync(sourcePath)) {
|
|
351
|
+
process.stderr.write(`❌ --score-only: no such results file: ${sourcePath}\n`);
|
|
352
|
+
return 2;
|
|
353
|
+
}
|
|
354
|
+
const run = JSON.parse(fs.readFileSync(sourcePath, 'utf-8')) as StoredRun;
|
|
355
|
+
const transcripts: Array<{ slot: string; variant: string; prompt: string; text: string }> = [];
|
|
356
|
+
for (const r of run.results ?? []) {
|
|
357
|
+
for (const [variant, v] of Object.entries(r.variants ?? {})) {
|
|
358
|
+
if (typeof v.text === 'string' && v.text.length > 0) {
|
|
359
|
+
transcripts.push({ slot: r.slot, variant, prompt: r.prompt, text: v.text });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (transcripts.length === 0) {
|
|
364
|
+
// Not "scored zero and passed" — an empty corpus is unreadable input.
|
|
365
|
+
process.stderr.write(
|
|
366
|
+
`❌ --score-only: ${sourcePath} carries no transcript text. ` +
|
|
367
|
+
`A re-score over nothing is not a baseline.\n`,
|
|
368
|
+
);
|
|
369
|
+
return 2;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const est = estimate_cost(scorerModel, 2000, 300) * transcripts.length;
|
|
373
|
+
out(
|
|
374
|
+
`rdp-eval · mode=score-only · ${String(transcripts.length)} stored transcript(s) ` +
|
|
375
|
+
`from ${sourcePath} · rater ${scorerModel} · 0 capture calls`,
|
|
376
|
+
);
|
|
377
|
+
out(` EXPECTED TOTAL (worst-case): ~$${est.toFixed(4)}`);
|
|
378
|
+
if (!confirm) {
|
|
379
|
+
out('\nDRY-RUN — no spend. Re-run with --confirm to score.');
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
382
|
+
// Score-only skips CAPTURE, not spend — the rater is billed per transcript.
|
|
383
|
+
// It therefore inherits the capture path's non-tty guard verbatim rather
|
|
384
|
+
// than becoming the unguarded way to bill the same account.
|
|
385
|
+
if (!process.stdin.isTTY && process.env.RDP_EVAL_ALLOW_NONTTY !== '1') {
|
|
386
|
+
throw new Error(
|
|
387
|
+
'Refusing a billable score-only run on non-tty stdin. Run interactively, or set ' +
|
|
388
|
+
'RDP_EVAL_ALLOW_NONTTY=1 if the caller has already confirmed the cost.',
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const apiKey = load_anthropic_key();
|
|
393
|
+
let actual = 0;
|
|
394
|
+
let failed = 0;
|
|
395
|
+
for (const t of transcripts) {
|
|
396
|
+
console.error(` scoring ${t.slot}/${t.variant} via ${scorerModel} …`);
|
|
397
|
+
const sc = await scoreTranscript(apiKey, scorerModel, t.prompt, t.text);
|
|
398
|
+
actual += estimate_cost(scorerModel, 2000, 300);
|
|
399
|
+
const target = run.results.find((r) => r.slot === t.slot)?.variants[t.variant];
|
|
400
|
+
if (target) {
|
|
401
|
+
// An unparsable reply DELETES the stale score rather than keeping
|
|
402
|
+
// it: a score from the previous rater sitting under this run's
|
|
403
|
+
// `scorer_model` would misattribute the reading.
|
|
404
|
+
if (sc === null) delete target.score;
|
|
405
|
+
else target.score = sc;
|
|
406
|
+
}
|
|
407
|
+
if (sc === null) failed += 1;
|
|
408
|
+
}
|
|
409
|
+
run.scorer_model = scorerModel;
|
|
410
|
+
run.mode = `${run.mode ?? 'unknown'}+score-only`;
|
|
411
|
+
fs.writeFileSync(outPath, `${JSON.stringify(run, null, 2)}\n`);
|
|
412
|
+
out(`\nscanned: ${String(transcripts.length)}`);
|
|
413
|
+
out(`rdp-eval · wrote ${outPath} · ~$${actual.toFixed(4)} · ${String(failed)} unparsable`);
|
|
414
|
+
// An unparsable rater reply is a hole in the reading, not a rounding error.
|
|
415
|
+
return failed > 0 ? 1 : 0;
|
|
254
416
|
}
|
|
255
417
|
|
|
256
418
|
async function main(): Promise<number> {
|
|
@@ -268,9 +430,29 @@ async function main(): Promise<number> {
|
|
|
268
430
|
(args.results as string) ??
|
|
269
431
|
path.join(GT_DIR, mode === 'l6' ? 'l6-results.json' : 'results.json');
|
|
270
432
|
|
|
433
|
+
// ---- score-only: re-score transcripts that already exist -----------------
|
|
434
|
+
// A stored results file IS the baseline arm for a dimension added after it
|
|
435
|
+
// was captured. Re-scoring it costs the scorer calls and NOTHING for
|
|
436
|
+
// capture, which is the only way a baseline for `dim5` can exist at all:
|
|
437
|
+
// the 32 L6N transcripts predate the dimension, and re-capturing them would
|
|
438
|
+
// produce a *different* baseline under today's suite rather than the one
|
|
439
|
+
// the treatment must be compared against.
|
|
440
|
+
if (args['score-only']) {
|
|
441
|
+
const scoreOnlyPath = String(args['score-only']);
|
|
442
|
+
if (!scoreWith) {
|
|
443
|
+
process.stderr.write('❌ --score-only requires --score-with <model> (the rater).\n');
|
|
444
|
+
return 2;
|
|
445
|
+
}
|
|
446
|
+
return await scoreOnly(scoreOnlyPath, scoreWith, resultsPath, Boolean(args.confirm));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const withCandidates = Boolean(args.candidates);
|
|
271
450
|
const slots = loadSlots(corpusPath, selected);
|
|
272
|
-
const arms = buildArms(mode);
|
|
451
|
+
const arms = buildArms(mode, withCandidates);
|
|
273
452
|
const variantNames = arms.map((a) => a[0]);
|
|
453
|
+
if (withCandidates) {
|
|
454
|
+
out(' treatment: +CANDIDATES_BLOCK on every arm (step 3.1 — instructed, not shipped)');
|
|
455
|
+
}
|
|
274
456
|
|
|
275
457
|
// ---- cost preview --------------------------------------------------------
|
|
276
458
|
let total = 0;
|
|
@@ -361,7 +543,7 @@ async function main(): Promise<number> {
|
|
|
361
543
|
rater2_mean: { [v0]: m0, [v1]: m1 },
|
|
362
544
|
rater2_delta: m0 !== null && m1 !== null ? Math.round((m1 - m0) * 100) / 100 : null,
|
|
363
545
|
});
|
|
364
|
-
writeTranscript(s, variants, ts, overhead, variantNames, mode);
|
|
546
|
+
writeTranscript(s, variants, ts, overhead, variantNames, mode, withCandidates);
|
|
365
547
|
}
|
|
366
548
|
|
|
367
549
|
fs.writeFileSync(
|
|
@@ -369,7 +551,9 @@ async function main(): Promise<number> {
|
|
|
369
551
|
JSON.stringify(
|
|
370
552
|
{
|
|
371
553
|
date: ts,
|
|
372
|
-
|
|
554
|
+
// Self-describing: a results file whose arms carried the
|
|
555
|
+
// candidates block must not be readable as a plain l6 run.
|
|
556
|
+
mode: withCandidates ? `${mode}+candidates` : mode,
|
|
373
557
|
standard_model: standardModel,
|
|
374
558
|
strong_model: strongModel,
|
|
375
559
|
scorer_model: scoreWith ?? null,
|
|
@@ -386,6 +570,18 @@ async function main(): Promise<number> {
|
|
|
386
570
|
return 0;
|
|
387
571
|
}
|
|
388
572
|
|
|
573
|
+
/**
|
|
574
|
+
* The transcript filename is keyed on the MODE, so a run under a different
|
|
575
|
+
* treatment must not reuse it.
|
|
576
|
+
*
|
|
577
|
+
* Found the hard way on 2026-09-08: `--candidates` reused the plain `l6n-`
|
|
578
|
+
* prefix and silently overwrote all 16 committed June baseline transcripts
|
|
579
|
+
* mid-run. The measurement survived — `--results` had been pointed at a new
|
|
580
|
+
* path and every transcript body is stored in that JSON — but the baseline's
|
|
581
|
+
* human-readable artefacts were replaced by treatment ones under the baseline's
|
|
582
|
+
* own names, which is the worst kind of quiet: a later reader comparing the
|
|
583
|
+
* markdown would have compared the treatment against itself.
|
|
584
|
+
*/
|
|
389
585
|
function writeTranscript(
|
|
390
586
|
slot: Slot,
|
|
391
587
|
variants: Record<string, Variant>,
|
|
@@ -393,8 +589,10 @@ function writeTranscript(
|
|
|
393
589
|
overhead: number | null,
|
|
394
590
|
variantNames: string[],
|
|
395
591
|
mode: string,
|
|
592
|
+
withCandidates = false,
|
|
396
593
|
): void {
|
|
397
|
-
const
|
|
594
|
+
const base = mode === 'l6' ? 'l6n-' : '';
|
|
595
|
+
const prefix = withCandidates ? `${base}cand-` : base;
|
|
398
596
|
const p = path.join(GT_DIR, `${prefix}${slot.n}-${slot.slug}.md`);
|
|
399
597
|
const L: string[] = [
|
|
400
598
|
`# Transcript — slot ${slot.n}: ${slot.slug}`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"taxonomy_version": "1.
|
|
3
|
-
"updated": "2026-09-
|
|
2
|
+
"taxonomy_version": "1.1.0",
|
|
3
|
+
"updated": "2026-09-08",
|
|
4
4
|
"contract": "docs/decisions/ADR-253-per-pr-user-artifact-gate-declined.md",
|
|
5
5
|
"reader": "src/scripts/measure_release_mix.ts",
|
|
6
6
|
"note": [
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
{ "prefix": "src/install/", "category": "consumer" },
|
|
63
63
|
{ "prefix": "src/server/", "category": "consumer" },
|
|
64
64
|
{ "prefix": "src/ui/", "category": "consumer" },
|
|
65
|
+
{ "prefix": "src/vendor/grammars/", "category": "consumer" },
|
|
65
66
|
{ "prefix": "src/shared/", "category": "unclassified" },
|
|
66
67
|
|
|
67
68
|
{ "prefix": "src/scripts/", "category": "maintenance" },
|