@event4u/agent-config 7.0.1 → 7.0.2

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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Shared agent configuration — skills for AI coding tools (Claude Code, Augment, Cursor, Cline, Windsurf, Gemini CLI).",
9
- "version": "7.0.1",
9
+ "version": "7.0.2",
10
10
  "keywords": [
11
11
  "agent-config",
12
12
  "skills",
package/CHANGELOG.md CHANGED
@@ -948,6 +948,27 @@ our recommendation order, not its support status.
948
948
  > that forces a new era split (`# Era: 7.1.x`, etc.) — see
949
949
  > [`docs/contracts/CHANGELOG-conventions.md § Era splits`](docs/contracts/CHANGELOG-conventions.md).
950
950
 
951
+ ## [7.0.2](https://github.com/event4u-app/agent-config/compare/7.0.1...7.0.2) (2026-06-22)
952
+
953
+ ### Bug Fixes
954
+
955
+ * **roadmaps:** regen entry-guard fires under symlinked invocation ([244e4e4](https://github.com/event4u-app/agent-config/commit/244e4e45a42a6b3d4793ac192e0f912d71954ba4))
956
+ * **rdp-eval:** harden index access for strict typecheck (noUncheckedIndexedAccess) ([041b21e](https://github.com/event4u-app/agent-config/commit/041b21e4877361b1499328216242fa6ec595ff99))
957
+ * **rdp-eval:** satisfy eslint on the new runners ([3c418e1](https://github.com/event4u-app/agent-config/commit/3c418e16faf340b8367148f6788835e400241e4e))
958
+
959
+ ### Documentation
960
+
961
+ * **rdp-eval:** keep-scoped L6 verdict — scope orchestrator to multi-step work ([61173ca](https://github.com/event4u-app/agent-config/commit/61173ca899442b81159e4f3efe586251dcea4459))
962
+
963
+ ### Tests
964
+
965
+ * **rdp-eval:** capture larger-N L6 + gate-classification data ([f92aad3](https://github.com/event4u-app/agent-config/commit/f92aad32620af27c5cf4d000859adeeb9894a819))
966
+ * **rdp-eval:** port quality+L6 eval runners to TypeScript (fetch-based) ([9c4ec60](https://github.com/event4u-app/agent-config/commit/9c4ec604ca6fbb82d796cf2d65626d381a854142))
967
+
968
+ ### CI
969
+
970
+ * **consistency:** trigger on src/** and dist/agent-src/** paths ([6a5e5e7](https://github.com/event4u-app/agent-config/commit/6a5e5e76cea3d5f879f9fcda6e510cc73039826e))
971
+
951
972
  ## [7.0.1](https://github.com/event4u-app/agent-config/compare/7.0.0...7.0.1) (2026-06-22)
952
973
 
953
974
  ### Bug Fixes
@@ -784,10 +784,28 @@ function main(argv?: readonly string[]): number {
784
784
  return 0;
785
785
  }
786
786
 
787
- const _isCliEntry =
788
- process.argv[1] !== undefined &&
789
- import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href;
790
- if (_isCliEntry || process.argv[1] === _HERE) {
787
+ function _isCliEntry(): boolean {
788
+ if (process.argv[1] === undefined) {
789
+ return false;
790
+ }
791
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
792
+ if (import.meta.url === argvUrl) {
793
+ return true;
794
+ }
795
+ // A symlinked invocation (e.g. via `.augment/scripts` → `dist/agent-src/scripts`,
796
+ // or macOS /var → /private/var temp dirs) makes the raw URLs differ:
797
+ // import.meta.url is the resolved real path while argv[1] keeps the symlink
798
+ // path. Compare realpaths so the entry guard still fires (without this the
799
+ // dashboard regen silently no-ops when run through the symlink).
800
+ try {
801
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
802
+ const argv = fs.realpathSync(path.resolve(process.argv[1]));
803
+ return here === argv;
804
+ } catch {
805
+ return false;
806
+ }
807
+ }
808
+ if (_isCliEntry() || process.argv[1] === _HERE) {
791
809
  try {
792
810
  process.exitCode = main();
793
811
  } catch (exc) {
@@ -34,12 +34,29 @@ coordinates); on a standard host run the full chain.
34
34
 
35
35
  ## When to use
36
36
 
37
- - A complex, ambiguous, or long-horizon task on a standard host.
38
- - Work where the reasoning steps are interdependent (a missed grounding or skipped
39
- verification compounds downstream).
40
-
41
- Do NOT use for trivial / linear / fully-specified tasks (the gate filters these),
42
- and do not let it duplicate the work of the skills it coordinates.
37
+ Scope (eval-calibrated, L6): engage only for **interdependent multi-step** work
38
+ — steps whose ordering/handoffs matter (schema → API → job → UI; staged
39
+ migration; cross-cutting refactor). **Not** for single-turn analysis (explain /
40
+ name / yes-no / pick-X-or-Y / one-shot edit): there the ordered chain is a no-op,
41
+ only adds tokens.
42
+
43
+ - Interdependent **multi-step** task on a standard host. Analyze the dependency
44
+ structure (what blocks what) before acting.
45
+ - Work where a missed/out-of-order link (grounding, verification) compounds
46
+ downstream.
47
+
48
+ Do NOT use for trivial / linear / fully-specified tasks **or single-turn
49
+ analysis** (the gate filters these); don't duplicate the work of the skills it
50
+ coordinates.
51
+
52
+ > **Why scoped (L6, 2026-06-22).** Controlled distributed-vs-orchestrated eval
53
+ > (N=16, independent rater): ordered chain gains **+19.2%** on multi-step,
54
+ > **−1.1%** (no-op) on single-turn. Standard host classifies multi-step vs
55
+ > single-turn at gate time 16/16; misclassification degrades gracefully (wrong
56
+ > arm still functional). Revert trigger = re-run `tests/reasoning-layer-eval`
57
+ > (eval is the telemetry — no-runtime package); RDP flip conditions judged
58
+ > per-mechanism, never univariate aggregate. Evidence:
59
+ > `tests/reasoning-layer-eval/RESULTS-L6-largeN-2026-06-22.md`.
43
60
 
44
61
  ## When the agent should load this
45
62
 
@@ -77,8 +94,16 @@ not exclusivity.
77
94
 
78
95
  ## Output
79
96
 
80
- The task's actual deliverable — produced through the chain. Reasoning stays in the
81
- notes file; the response leads with the outcome + its evidence.
97
+ The task's actual deliverable, produced **through the chain** (analysis before
98
+ action — grounding + load-bearing-unknown resolution precede any edit):
99
+
100
+ 1. **Outcome first** — response leads with the result + its evidence.
101
+ 2. **Reasoning stays in notes** — hypotheses / predictions / decisions in the
102
+ notes file, never dumped into the response.
103
+ 3. **Chain honoured in order** — ground → intent → notes → resolve-hardest-first
104
+ → audit → verify ran as ordered links (with handoffs), not a buffet.
105
+ 4. **Verified before "done"** — no completion claim without the verifier link's
106
+ evidence.
82
107
 
83
108
  ## Do NOT
84
109
 
@@ -1,6 +1,6 @@
1
1
  # Discovery — Deprecation Report
2
2
 
3
- - Generated: `2026-06-21T23:17:03Z`
3
+ - Generated: `2026-06-22T07:13:57Z`
4
4
  - Deprecated artefacts: **0**
5
5
 
6
6
  _None. Tree is clean._
@@ -10357,7 +10357,7 @@
10357
10357
  },
10358
10358
  {
10359
10359
  "category": "skill",
10360
- "checksum": "sha256:949bab08a36fcbc7b88de82c6facb3490b59478d430eec284a96a83ac7456a60",
10360
+ "checksum": "sha256:c7a74836f37f983ea6432ce8773fd03df7c20bdb616616db4852364406b4ad78",
10361
10361
  "install": {
10362
10362
  "default": true,
10363
10363
  "removable": false
@@ -11742,7 +11742,7 @@
11742
11742
  ]
11743
11743
  }
11744
11744
  ],
11745
- "checksum": "sha256:f2a4c972feea1c5cde8781f698209980d6d156e17612b71b66e885f6a97ed19e",
11745
+ "checksum": "sha256:f5d227f1f899e88091f76a952cdfadda24da8bcc233811a2c55a8ebfdf564fa7",
11746
11746
  "deprecations": [
11747
11747
  {
11748
11748
  "key": "tier",
@@ -11880,7 +11880,7 @@
11880
11880
  "reason": "scaffold template for ticket-bundle tickets (ADR-101); carries no runtime frontmatter — emit-tickets fills it in per ticket"
11881
11881
  }
11882
11882
  ],
11883
- "generated_at": "2026-06-21T23:17:03Z",
11883
+ "generated_at": "2026-06-22T07:13:57Z",
11884
11884
  "packs": [
11885
11885
  {
11886
11886
  "artefact_count": 93,
@@ -1 +1 @@
1
- b14a0a566a169fd604a5937ce681473bc8f4690936172300e680950c0599ed3c discovery-manifest.json
1
+ a8b3e192c4c3462c185328180eed27e001de5e3f87feba346a63e42c6d6ebcb0 discovery-manifest.json
@@ -1,6 +1,6 @@
1
1
  # Discovery Manifest — Summary
2
2
 
3
- - Generated: `2026-06-21T23:17:03Z`
3
+ - Generated: `2026-06-22T07:13:57Z`
4
4
  - Scanner: `47c9a32fb265`
5
5
  - Artefacts: **501**
6
6
  - Unassigned: **0**
@@ -1,6 +1,6 @@
1
1
  # Discovery — Orphan Report
2
2
 
3
- - Generated: `2026-06-21T23:17:03Z`
3
+ - Generated: `2026-06-22T07:13:57Z`
4
4
  - Orphan artefacts: **0**
5
5
 
6
6
  > An orphan is an artefact whose declared pack has no other members.
@@ -1,6 +1,6 @@
1
1
  {
2
- "checksum": "sha256:f2a4c972feea1c5cde8781f698209980d6d156e17612b71b66e885f6a97ed19e",
3
- "generated_at": "2026-06-21T23:17:03Z",
2
+ "checksum": "sha256:f5d227f1f899e88091f76a952cdfadda24da8bcc233811a2c55a8ebfdf564fa7",
3
+ "generated_at": "2026-06-22T07:13:57Z",
4
4
  "packs": [
5
5
  {
6
6
  "always_on": false,
@@ -1,6 +1,6 @@
1
1
  # Discovery — Trust Report
2
2
 
3
- - Generated: `2026-06-21T23:17:03Z`
3
+ - Generated: `2026-06-22T07:13:57Z`
4
4
  - Workspaces tracked: **8**
5
5
  - Human-review-required artefacts: **2**
6
6
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "checksum": "sha256:f2a4c972feea1c5cde8781f698209980d6d156e17612b71b66e885f6a97ed19e",
3
- "generated_at": "2026-06-21T23:17:03Z",
2
+ "checksum": "sha256:f5d227f1f899e88091f76a952cdfadda24da8bcc233811a2c55a8ebfdf564fa7",
3
+ "generated_at": "2026-06-22T07:13:57Z",
4
4
  "scanner_version": "47c9a32fb265",
5
5
  "workspaces": [
6
6
  {
@@ -3,13 +3,13 @@
3
3
  "artefact_count": 501,
4
4
  "scanner_version": "47c9a32fb265"
5
5
  },
6
- "generated_at": "2026-06-21",
6
+ "generated_at": "2026-06-22",
7
7
  "package": {
8
8
  "description": "Universal AI Agent OS \u2014 audited skills, governance rules, commands, and templates for AI coding tools (Claude Code, Cursor, Windsurf, Copilot).",
9
9
  "homepage": "https://github.com/event4u-app/agent-config#readme",
10
10
  "name": "@event4u/agent-config",
11
11
  "repository": "https://github.com/event4u-app/agent-config",
12
- "version": "7.0.1"
12
+ "version": "7.0.2"
13
13
  },
14
14
  "registries": [
15
15
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
- "version": "7.0.1",
3
+ "version": "7.0.2",
4
4
  "description": "Universal AI Agent OS — audited skills, governance rules, commands, and templates for AI coding tools (Claude Code, Cursor, Windsurf, Copilot).",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * RDP gate-time classification validation (road-to-rdp-frontier-polish Phase 1).
4
+ *
5
+ * The council's blocker on "keep-scoped" (2026-06-22): scoping the orchestrator
6
+ * to interdependent-multi-step work via agent self-assessment is only honest if
7
+ * the agent can classify a task AT GATE TIME — from the prompt alone, before
8
+ * doing the work. This script measures exactly that: the standard host classifies
9
+ * each labelled corpus prompt as multi-step vs single-turn, scored against the
10
+ * ground-truth `mechanism` label. High accuracy → the self-assessment gate is a
11
+ * real control, not vibes. Low accuracy → keep-scoped is not implementable.
12
+ *
13
+ * Direct Messages API via fetch (no SDK dep), reuses the key gate + cost
14
+ * estimate from skill_trigger_eval.ts. Dry-run by default; --confirm to spend
15
+ * (+ RDP_EVAL_ALLOW_NONTTY=1 on a non-tty caller that has confirmed cost).
16
+ *
17
+ * Usage: ./scripts-run src/scripts/rdp_gate_classify --corpus <file> --confirm
18
+ */
19
+ import * as fs from 'node:fs';
20
+ import { estimate_cost, load_anthropic_key } from './skill_trigger_eval.js';
21
+
22
+ /** stdout helper — the lint config forbids `console.log` (allows warn/error). */
23
+ const emit = (s: string): void => {
24
+ process.stdout.write(`${s}\n`);
25
+ };
26
+
27
+ const ANTHROPIC_URL = 'https://api.anthropic.com/v1/messages';
28
+ const ANTHROPIC_VERSION = '2023-06-01';
29
+
30
+ const GATE_SYSTEM = `You are an agent deciding, at the START of a task (before doing any work), whether
31
+ to engage a heavyweight multi-step reasoning orchestrator. You see ONLY the task
32
+ prompt. Classify the task into exactly one bucket using the rdp-gate criterion:
33
+
34
+ - "multi-step": interdependent multi-step work where the ordering and handoffs
35
+ between steps matter (e.g. schema → API → job → UI; a migration with stages; a
36
+ cross-cutting refactor). The orchestrator helps here.
37
+ - "single-turn": a single analytical or lookup question, a naming/explain/yes-no
38
+ ask, or a one-shot edit — work that does NOT decompose into ordered
39
+ interdependent steps. The orchestrator over-processes here.
40
+
41
+ Respond with STRICT JSON only: {"bucket":"multi-step"|"single-turn","confidence":"high"|"medium"|"low"}`;
42
+
43
+ interface Slot {
44
+ n: string;
45
+ slug: string;
46
+ mechanism?: string;
47
+ band: string;
48
+ prompt: string;
49
+ }
50
+
51
+ async function classify(apiKey: string, model: string, prompt: string): Promise<{ bucket: string; confidence: string } | null> {
52
+ const resp = await fetch(ANTHROPIC_URL, {
53
+ method: 'POST',
54
+ headers: {
55
+ 'x-api-key': apiKey,
56
+ 'anthropic-version': ANTHROPIC_VERSION,
57
+ 'content-type': 'application/json',
58
+ },
59
+ body: JSON.stringify({
60
+ model,
61
+ max_tokens: 60,
62
+ system: GATE_SYSTEM,
63
+ messages: [{ role: 'user', content: `Task prompt:\n${prompt}` }],
64
+ }),
65
+ });
66
+ if (!resp.ok) throw new Error(`API ${resp.status}: ${(await resp.text()).slice(0, 300)}`);
67
+ const data = (await resp.json()) as {
68
+ content?: Array<{ type?: string; text?: string }>;
69
+ usage?: { input_tokens?: number; output_tokens?: number };
70
+ };
71
+ const text = (data.content ?? []).filter((b) => b.type === 'text').map((b) => b.text ?? '').join('');
72
+ const m = text.match(/\{[\s\S]*\}/);
73
+ if (!m) return null;
74
+ try {
75
+ return JSON.parse(m[0]) as { bucket: string; confidence: string };
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ function expectedBucket(mechanism: string | undefined): string {
82
+ return mechanism === 'multi-stage' ? 'multi-step' : 'single-turn';
83
+ }
84
+
85
+ async function main(): Promise<number> {
86
+ const argv = process.argv.slice(2);
87
+ const args: Record<string, string | boolean> = {};
88
+ for (let i = 0; i < argv.length; i++) {
89
+ const t = argv[i];
90
+ if (t === undefined) continue;
91
+ if (t === '--confirm') args.confirm = true;
92
+ else if (t.startsWith('--')) {
93
+ const next = argv[i + 1];
94
+ if (next && !next.startsWith('--')) { args[t.slice(2)] = next; i++; } else args[t.slice(2)] = true;
95
+ }
96
+ }
97
+ const corpusPath = (args.corpus as string) ?? '';
98
+ const model = (args.model as string) ?? 'claude-haiku-4-5-20251001';
99
+ if (!corpusPath) throw new Error('--corpus <file> required');
100
+ const slots = (JSON.parse(fs.readFileSync(corpusPath, 'utf-8')) as { slots: Slot[] }).slots;
101
+
102
+ const est = slots.reduce((p) => p + estimate_cost(model, 250, 60), 0);
103
+ emit(`rdp-gate-classify · ${slots.length} prompts · ${model} · ~$${est.toFixed(4)}`);
104
+ if (!args.confirm) {
105
+ emit('DRY-RUN — no spend. Re-run with --confirm.');
106
+ return 0;
107
+ }
108
+ if (!process.stdin.isTTY && process.env.RDP_EVAL_ALLOW_NONTTY !== '1') {
109
+ throw new Error('Non-tty: set RDP_EVAL_ALLOW_NONTTY=1 only after confirming cost.');
110
+ }
111
+ const apiKey = load_anthropic_key();
112
+ let correct = 0;
113
+ const rows: Array<Record<string, unknown>> = [];
114
+ for (const s of slots) {
115
+ const r = await classify(apiKey, model, s.prompt);
116
+ const exp = expectedBucket(s.mechanism);
117
+ const got = r?.bucket ?? 'parse-error';
118
+ const ok = got === exp;
119
+ if (ok) correct++;
120
+ console.error(` ${s.n} ${s.slug}: expected ${exp} · got ${got} (${r?.confidence ?? '-'}) ${ok ? 'OK' : 'MISS'}`);
121
+ rows.push({ slot: s.n, slug: s.slug, expected: exp, got, confidence: r?.confidence ?? null, ok });
122
+ }
123
+ const acc = Math.round((correct / slots.length) * 1000) / 10;
124
+ const out = (args.results as string) ?? '/tmp/rdp-gate-classify.json';
125
+ fs.writeFileSync(out, JSON.stringify({ model, n: slots.length, correct, accuracy_pct: acc, rows }, null, 2), 'utf-8');
126
+ emit(`\nGATE-TIME CLASSIFICATION ACCURACY: ${correct}/${slots.length} = ${acc}% (model ${model})`);
127
+ emit(`Results: ${out}`);
128
+ return 0;
129
+ }
130
+
131
+ main().then((c) => process.exit(c), (e) => { console.error(e instanceof Error ? e.message : String(e)); process.exit(1); });
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * RDP quality-layer + L6-isolation eval runner (TypeScript).
4
+ *
5
+ * Ports the Python `run_quality_eval.py` removed in the py2ts teardown. The
6
+ * Anthropic SDK is not a dependency in this repo (the TS trigger runner throws
7
+ * on the live path); this runner therefore calls the Messages API directly via
8
+ * Node's global `fetch` — no new dependency. It reuses the 0600-key gate, the
9
+ * cost-estimate, and the price tables exported by `skill_trigger_eval.ts`.
10
+ *
11
+ * Two modes (controlled system-prompt differential — the differing block is the
12
+ * only variable, so the delta isolates that block's effect):
13
+ * --mode quality : baseline (no RDP) vs treatment (+RDP layer) [L8]
14
+ * --mode l6 : distributed-only (RDP buffet) vs orchestrated (ordered) [L6]
15
+ *
16
+ * The measured model runs with the system prompt this runner supplies, so a
17
+ * baseline is NOT contaminated by the calling agent's own active rules.
18
+ *
19
+ * Optional `--score-with <model>` adds an independent model rater (rater 2):
20
+ * after capture, each variant transcript is scored 0–3 on the 4 rubric dims by
21
+ * a separate model call, recorded alongside the transcript.
22
+ *
23
+ * Cost discipline mirrors skill_trigger_eval.ts: dry-run by default; a billable
24
+ * run needs --confirm, and on a non-tty stdin also RDP_EVAL_ALLOW_NONTTY=1
25
+ * (set only when the caller has already confirmed the cost).
26
+ *
27
+ * Usage:
28
+ * ./scripts-run src/scripts/rdp_quality_eval --mode l6 --corpus <file>
29
+ * ./scripts-run src/scripts/rdp_quality_eval --mode l6 --corpus <file> --confirm --score-with claude-sonnet-4-5
30
+ */
31
+ import * as fs from 'node:fs';
32
+ import * as path from 'node:path';
33
+ import { estimate_cost, load_anthropic_key } from './skill_trigger_eval.js';
34
+
35
+ /** stdout helper — the lint config forbids `console.log` (allows warn/error). */
36
+ const out = (s: string): void => {
37
+ process.stdout.write(`${s}\n`);
38
+ };
39
+
40
+ const REPO_ROOT = path.resolve(import.meta.dirname, '..', '..');
41
+ const EVAL_DIR = path.join(REPO_ROOT, 'tests', 'reasoning-layer-eval');
42
+ const GT_DIR = path.join(EVAL_DIR, 'golden-transcripts');
43
+ const DEFAULT_CORPUS = path.join(GT_DIR, 'corpus-prompts.json');
44
+
45
+ const ANTHROPIC_URL = 'https://api.anthropic.com/v1/messages';
46
+ const ANTHROPIC_VERSION = '2023-06-01';
47
+
48
+ // ---- the system-prompt blocks (verbatim port of the Python harness) ---------
49
+ const BASE_SYSTEM = `You are a senior software engineering assistant working inside a real codebase.
50
+
51
+ Operating posture:
52
+ - Keep diffs minimal and scoped to the stated task; no drive-by refactors.
53
+ - When a requirement is ambiguous, you may ask one focused clarifying question.
54
+ - Do not claim work is complete without the evidence that proves it.
55
+ - Be direct and concise. No flattery, no filler.`;
56
+
57
+ const RDP_BLOCK = `## Reasoning Discipline Protocol
58
+
59
+ Engage the protocol below only where it pays: skip it on trivial, short, fully
60
+ specified tasks (rename, one-liner, list files); apply it on complex, ambiguous,
61
+ multi-component, stateful, or irreversible tasks. If you are a strong-reasoning
62
+ model that already self-coordinates, apply it lightly.
63
+
64
+ 1. GROUND BEFORE DESIGNING. Enumerate the constraints, available facts, and the
65
+ information gaps the task leaves open. Close the load-bearing gaps (by asking
66
+ or by stating an explicit assumption) BEFORE proposing a solution. Never design
67
+ against unstated assumptions.
68
+
69
+ 2. INFER THE REAL GOAL. When the literal request and the underlying goal may
70
+ differ, state the inferred goal in one line, then give ONE recommendation —
71
+ not a spread of framings.
72
+
73
+ 3. COMPLEXITY-FIRST SEQUENCING. For multi-step work, resolve the hardest /
74
+ most load-bearing unknown FIRST, before dependent work. Do not build the easy
75
+ parts first and rework later. Name what you would tackle first and why.
76
+
77
+ 4. NOTES-FIRST OUTPUT. Keep multi-hypothesis reasoning, predictions, and
78
+ decisions in a clearly delimited "## Working notes" section. Your "## Answer"
79
+ section carries CONCLUSIONS + EVIDENCE only — never a raw chain-of-thought
80
+ dump. The answer must be readable by someone who saw none of the working
81
+ thread: outcome-first, no arrow-chain shorthand.
82
+
83
+ 5. VERIFIER GATE (risky change). When the task shows two or more of {branching /
84
+ conditional logic, three or more explicit must/must-not constraints, stateful
85
+ operations, irreversibility}, explicitly name what must be verified and how
86
+ BEFORE treating the change as done. Surface the irreversible step for
87
+ confirmation rather than executing it blind.
88
+
89
+ 6. PREDICTIONS + DECISIONS (calibration / ledger). When you make an estimate,
90
+ log it as a prediction with a confidence level so it can be checked against
91
+ the actual outcome later. When you choose between alternatives, record the
92
+ decision, the alternatives, the reason, and what would make you revisit it —
93
+ in the Working notes, so a later session can reuse it instead of re-deriving.
94
+
95
+ 7. ADAPTIVE EFFORT. Scale effort to difficulty; stop when marginal evidence
96
+ drops rather than over-elaborating.`;
97
+
98
+ const ORCHESTRATOR_PREAMBLE = `## Reasoning orchestration (run the disciplines as ONE ordered chain)
99
+
100
+ Do not treat the protocol below as an optional buffet. Run it as a single
101
+ coordinated chain, in order, with explicit handoffs between links — a skipped or
102
+ out-of-order link compounds downstream:
103
+
104
+ ground → infer intent → write working notes → resolve the load-bearing unknown
105
+ first → audit progress against real evidence → verify before claiming done.
106
+
107
+ Coordinate the links; do not let later steps run before earlier ones. On a
108
+ trivial or fully-specified task, do NOT force the chain (that is over-process) —
109
+ engage it only where the task is genuinely complex/ambiguous/interdependent.`;
110
+
111
+ interface Slot {
112
+ n: string;
113
+ slug: string;
114
+ family?: string;
115
+ band: string;
116
+ discipline?: string;
117
+ mechanism?: string;
118
+ prompt: string;
119
+ }
120
+
121
+ interface Variant {
122
+ model: string;
123
+ text: string;
124
+ input_tokens: number;
125
+ output_tokens: number;
126
+ cost_usd: number;
127
+ score?: Record<string, unknown>;
128
+ }
129
+
130
+ function buildArms(mode: string): Array<[string, string]> {
131
+ const treat = `${BASE_SYSTEM}\n\n${RDP_BLOCK}`;
132
+ if (mode === 'l6') {
133
+ return [
134
+ ['distributed', treat],
135
+ ['orchestrated', `${treat}\n\n${ORCHESTRATOR_PREAMBLE}`],
136
+ ];
137
+ }
138
+ return [
139
+ ['baseline', BASE_SYSTEM],
140
+ ['treatment', treat],
141
+ ];
142
+ }
143
+
144
+ function approxTokens(text: string): number {
145
+ return Math.max(1, Math.floor(text.length / 4));
146
+ }
147
+
148
+ function modelFor(band: string, standard: string, strong: string): string {
149
+ return band === 'strong' ? strong : standard;
150
+ }
151
+
152
+ interface ApiResult {
153
+ text: string;
154
+ input_tokens: number;
155
+ output_tokens: number;
156
+ }
157
+
158
+ async function callAnthropic(
159
+ apiKey: string,
160
+ model: string,
161
+ system: string,
162
+ userContent: string,
163
+ maxTokens: number,
164
+ ): Promise<ApiResult> {
165
+ const resp = await fetch(ANTHROPIC_URL, {
166
+ method: 'POST',
167
+ headers: {
168
+ 'x-api-key': apiKey,
169
+ 'anthropic-version': ANTHROPIC_VERSION,
170
+ 'content-type': 'application/json',
171
+ },
172
+ body: JSON.stringify({
173
+ model,
174
+ max_tokens: maxTokens,
175
+ system,
176
+ messages: [{ role: 'user', content: userContent }],
177
+ }),
178
+ });
179
+ if (!resp.ok) {
180
+ const body = await resp.text();
181
+ throw new Error(`Anthropic API ${resp.status}: ${body.slice(0, 400)}`);
182
+ }
183
+ const data = (await resp.json()) as {
184
+ content?: Array<{ type?: string; text?: string }>;
185
+ usage?: { input_tokens?: number; output_tokens?: number };
186
+ };
187
+ const text = (data.content ?? [])
188
+ .filter((b) => b.type === 'text')
189
+ .map((b) => b.text ?? '')
190
+ .join('');
191
+ return {
192
+ text,
193
+ input_tokens: data.usage?.input_tokens ?? 0,
194
+ output_tokens: data.usage?.output_tokens ?? 0,
195
+ };
196
+ }
197
+
198
+ const SCORER_SYSTEM = `You are a strict, impartial evaluator. Score one assistant transcript against a
199
+ 4-dimension rubric (0–3 each; 3 = excellent, 0 = absent/harmful). Dimensions:
200
+ 1 notes-first adherence — reasoning kept out of the answer proper; answer = conclusions + evidence (not a raw chain-of-thought dump).
201
+ 2 grounding — environment/constraints/info-gaps closed (or explicitly assumed) before designing; no jumping to a solution against assumptions.
202
+ 3 premature-solution avoidance — the hardest/load-bearing unknown resolved before dependent work; no over-production of code/answer before grounding.
203
+ 4 coherence / re-grounded summary — final answer readable by someone who saw none of the working thread; outcome-first; no arrow-chain shorthand.
204
+ 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"}`;
206
+
207
+ async function scoreTranscript(
208
+ apiKey: string,
209
+ scorerModel: string,
210
+ prompt: string,
211
+ transcript: string,
212
+ ): Promise<Record<string, unknown> | null> {
213
+ const user = `## Task prompt given to the assistant\n${prompt}\n\n## Assistant transcript to score\n${transcript}\n\nScore now as strict JSON.`;
214
+ const r = await callAnthropic(apiKey, scorerModel, SCORER_SYSTEM, user, 300);
215
+ const m = r.text.match(/\{[\s\S]*\}/);
216
+ if (!m) return null;
217
+ try {
218
+ return JSON.parse(m[0]) as Record<string, unknown>;
219
+ } catch {
220
+ return null;
221
+ }
222
+ }
223
+
224
+ function loadSlots(corpusPath: string, selected: Set<string> | null): Slot[] {
225
+ const data = JSON.parse(fs.readFileSync(corpusPath, 'utf-8')) as { slots: Slot[] };
226
+ let slots = data.slots;
227
+ if (selected) slots = slots.filter((s) => selected.has(s.n));
228
+ return slots;
229
+ }
230
+
231
+ function parseArgs(argv: string[]): Record<string, string | boolean> {
232
+ const a: Record<string, string | boolean> = {};
233
+ for (let i = 0; i < argv.length; i++) {
234
+ const t = argv[i];
235
+ if (t === undefined) continue;
236
+ if (t === '--confirm') a.confirm = true;
237
+ else if (t.startsWith('--')) {
238
+ const key = t.slice(2);
239
+ const next = argv[i + 1];
240
+ if (next !== undefined && !next.startsWith('--')) {
241
+ a[key] = next;
242
+ i++;
243
+ } else a[key] = true;
244
+ }
245
+ }
246
+ return a;
247
+ }
248
+
249
+ function meanDims(score: Record<string, unknown> | undefined): number | null {
250
+ if (!score) return null;
251
+ const ds = ['dim1', 'dim2', 'dim3', 'dim4'].map((k) => Number(score[k]));
252
+ if (ds.some((x) => Number.isNaN(x))) return null;
253
+ return ds.reduce((p, c) => p + c, 0) / 4;
254
+ }
255
+
256
+ async function main(): Promise<number> {
257
+ const args = parseArgs(process.argv.slice(2));
258
+ const mode = (args.mode as string) ?? 'quality';
259
+ const corpusPath = (args.corpus as string) ?? DEFAULT_CORPUS;
260
+ const standardModel = (args['standard-model'] as string) ?? 'claude-haiku-4-5-20251001';
261
+ const strongModel = (args['strong-model'] as string) ?? 'claude-sonnet-4-5';
262
+ const maxTokens = args['max-tokens'] ? Number(args['max-tokens']) : 1600;
263
+ const scoreWith = args['score-with'] as string | undefined;
264
+ const selected = args.slots
265
+ ? new Set(String(args.slots).split(',').map((s) => s.trim().padStart(2, '0')))
266
+ : null;
267
+ const resultsPath =
268
+ (args.results as string) ??
269
+ path.join(GT_DIR, mode === 'l6' ? 'l6-results.json' : 'results.json');
270
+
271
+ const slots = loadSlots(corpusPath, selected);
272
+ const arms = buildArms(mode);
273
+ const variantNames = arms.map((a) => a[0]);
274
+
275
+ // ---- cost preview --------------------------------------------------------
276
+ let total = 0;
277
+ const byModel: Record<string, number> = {};
278
+ for (const s of slots) {
279
+ const model = modelFor(s.band, standardModel, strongModel);
280
+ for (const [, sys] of arms) {
281
+ const tin = approxTokens(sys) + approxTokens(s.prompt);
282
+ const c = estimate_cost(model, tin, maxTokens);
283
+ total += c;
284
+ byModel[model] = (byModel[model] ?? 0) + c;
285
+ }
286
+ if (scoreWith) {
287
+ // 2 scoring calls per slot (one per variant)
288
+ const c = estimate_cost(scoreWith, 2000, 300) * 2;
289
+ total += c;
290
+ byModel[`${scoreWith} (scorer)`] = (byModel[`${scoreWith} (scorer)`] ?? 0) + c;
291
+ }
292
+ }
293
+ const nCalls = slots.length * arms.length + (scoreWith ? slots.length * 2 : 0);
294
+ out(
295
+ `rdp-eval · mode=${mode} · ${slots.length} slots × ${arms.length} variants (${variantNames.join('/')}) ` +
296
+ `${scoreWith ? `+ scorer(${scoreWith})` : ''} = ${nCalls} calls`,
297
+ );
298
+ for (const [m, c] of Object.entries(byModel)) out(` ${m}: ~$${c.toFixed(4)}`);
299
+ out(` EXPECTED TOTAL (worst-case): ~$${total.toFixed(4)}`);
300
+
301
+ if (!args.confirm) {
302
+ out('\nDRY-RUN — no spend. Re-run with --confirm to capture transcripts.');
303
+ return 0;
304
+ }
305
+ if (!process.stdin.isTTY && process.env.RDP_EVAL_ALLOW_NONTTY !== '1') {
306
+ throw new Error(
307
+ 'Refusing a billable run on non-tty stdin. Run interactively, or set ' +
308
+ 'RDP_EVAL_ALLOW_NONTTY=1 if the caller has already confirmed the cost.',
309
+ );
310
+ }
311
+
312
+ const apiKey = load_anthropic_key();
313
+ const ts = (args.date as string) ?? '0000-00-00';
314
+ let actual = 0;
315
+ const results: Array<Record<string, unknown>> = [];
316
+
317
+ for (const s of slots) {
318
+ const model = modelFor(s.band, standardModel, strongModel);
319
+ const variants: Record<string, Variant> = {};
320
+ for (const [variant, sys] of arms) {
321
+ console.error(` → slot ${s.n} ${variant} (${model}) …`);
322
+ const r = await callAnthropic(apiKey, model, sys, s.prompt, maxTokens);
323
+ const cost = estimate_cost(model, r.input_tokens, r.output_tokens);
324
+ actual += cost;
325
+ const v: Variant = {
326
+ model,
327
+ text: r.text,
328
+ input_tokens: r.input_tokens,
329
+ output_tokens: r.output_tokens,
330
+ cost_usd: cost,
331
+ };
332
+ if (scoreWith) {
333
+ console.error(` scoring ${variant} via ${scoreWith} …`);
334
+ const sc = await scoreTranscript(apiKey, scoreWith, s.prompt, r.text);
335
+ actual += estimate_cost(scoreWith, 2000, 300);
336
+ if (sc) v.score = sc;
337
+ }
338
+ variants[variant] = v;
339
+ }
340
+ const [v0, v1] = variantNames;
341
+ const va = v0 !== undefined ? variants[v0] : undefined;
342
+ const vb = v1 !== undefined ? variants[v1] : undefined;
343
+ if (v0 === undefined || v1 === undefined || va === undefined || vb === undefined) {
344
+ continue;
345
+ }
346
+ const aOut = va.output_tokens;
347
+ const bOut = vb.output_tokens;
348
+ const overhead = aOut ? Math.round(((bOut - aOut) / aOut) * 1000) / 10 : null;
349
+ const m0 = meanDims(va.score);
350
+ const m1 = meanDims(vb.score);
351
+ results.push({
352
+ slot: s.n,
353
+ slug: s.slug,
354
+ band: s.band,
355
+ mechanism: s.mechanism ?? null,
356
+ discipline: s.discipline ?? null,
357
+ prompt: s.prompt,
358
+ model,
359
+ variants,
360
+ output_token_overhead_pct: overhead,
361
+ rater2_mean: { [v0]: m0, [v1]: m1 },
362
+ rater2_delta: m0 !== null && m1 !== null ? Math.round((m1 - m0) * 100) / 100 : null,
363
+ });
364
+ writeTranscript(s, variants, ts, overhead, variantNames, mode);
365
+ }
366
+
367
+ fs.writeFileSync(
368
+ resultsPath,
369
+ JSON.stringify(
370
+ {
371
+ date: ts,
372
+ mode,
373
+ standard_model: standardModel,
374
+ strong_model: strongModel,
375
+ scorer_model: scoreWith ?? null,
376
+ actual_cost_usd: Math.round(actual * 10000) / 10000,
377
+ results,
378
+ },
379
+ null,
380
+ 2,
381
+ ),
382
+ 'utf-8',
383
+ );
384
+ out(`\nDONE · mode=${mode} · actual ~$${(Math.round(actual * 10000) / 10000).toFixed(4)}`);
385
+ out(`Results JSON: ${resultsPath}`);
386
+ return 0;
387
+ }
388
+
389
+ function writeTranscript(
390
+ slot: Slot,
391
+ variants: Record<string, Variant>,
392
+ ts: string,
393
+ overhead: number | null,
394
+ variantNames: string[],
395
+ mode: string,
396
+ ): void {
397
+ const prefix = mode === 'l6' ? 'l6n-' : '';
398
+ const p = path.join(GT_DIR, `${prefix}${slot.n}-${slot.slug}.md`);
399
+ const L: string[] = [
400
+ `# Transcript — slot ${slot.n}: ${slot.slug}`,
401
+ '',
402
+ `- **Band:** ${slot.band}${slot.mechanism ? ` · **Mechanism:** ${slot.mechanism}` : ''}`,
403
+ `- **Captured:** ${ts} (controlled system-prompt differential; rater 2 = model scorer)`,
404
+ '',
405
+ '## Prompt',
406
+ '',
407
+ slot.prompt,
408
+ '',
409
+ ];
410
+ for (const variant of variantNames) {
411
+ const v = variants[variant];
412
+ if (v === undefined) continue;
413
+ const sc = v.score ? ` · rater2 ${JSON.stringify(v.score)}` : '';
414
+ L.push(
415
+ `## Transcript — ${variant} (${v.model})`,
416
+ '',
417
+ '~~~text',
418
+ v.text.trimEnd(),
419
+ '~~~',
420
+ '',
421
+ `**Tokens:** in ${v.input_tokens} / out ${v.output_tokens} / est $${v.cost_usd}${sc}`,
422
+ '',
423
+ );
424
+ }
425
+ if (overhead !== null) {
426
+ L.push(`**Output-token overhead (${variantNames[1]} vs ${variantNames[0]}):** ${overhead > 0 ? '+' : ''}${overhead}%`, '');
427
+ }
428
+ fs.writeFileSync(p, L.join('\n'), 'utf-8');
429
+ }
430
+
431
+ main().then(
432
+ (code) => process.exit(code),
433
+ (err) => {
434
+ console.error(err instanceof Error ? err.message : String(err));
435
+ process.exit(1);
436
+ },
437
+ );