@event4u/agent-config 7.0.1 → 7.1.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.
Files changed (103) hide show
  1. package/.claude-plugin/marketplace.json +8 -1
  2. package/CHANGELOG.md +99 -0
  3. package/README.md +4 -2
  4. package/dist/agent-src/commands/council/default.md +28 -20
  5. package/dist/agent-src/commands/evals/fix.json +5 -1
  6. package/dist/agent-src/commands/fix/comments.md +101 -0
  7. package/dist/agent-src/commands/fix.md +5 -2
  8. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +65 -0
  9. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +68 -0
  10. package/dist/agent-src/contexts/execution/host-capability-manifest.md +68 -0
  11. package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +62 -0
  12. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +59 -0
  13. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +8 -3
  14. package/dist/agent-src/contexts/execution/subagent-response-contract.md +56 -0
  15. package/dist/agent-src/contexts/execution/subagent-routing.md +57 -0
  16. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +54 -0
  17. package/dist/agent-src/contexts/execution/subagent-steering.md +71 -0
  18. package/dist/agent-src/contexts/execution/verify-budget.md +50 -0
  19. package/dist/agent-src/rules/delegation-policy.md +84 -0
  20. package/dist/agent-src/rules/design-fidelity.md +98 -0
  21. package/dist/agent-src/rules/domain-safety-pii.md +22 -0
  22. package/dist/agent-src/rules/legal-safety-floor.md +144 -0
  23. package/dist/agent-src/rules/lethal-trifecta-guard.md +25 -22
  24. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +102 -15
  25. package/dist/agent-src/scripts/update_roadmap_progress.ts +52 -7
  26. package/dist/agent-src/skills/ai-council/SKILL.md +28 -1
  27. package/dist/agent-src/skills/contract-review/SKILL.md +124 -0
  28. package/dist/agent-src/skills/contract-review/evals/triggers.json +17 -0
  29. package/dist/agent-src/skills/dpa-review/SKILL.md +129 -0
  30. package/dist/agent-src/skills/dpa-review/evals/triggers.json +17 -0
  31. package/dist/agent-src/skills/laravel/SKILL.md +1 -0
  32. package/dist/agent-src/skills/legal-intake-triage/SKILL.md +127 -0
  33. package/dist/agent-src/skills/legal-intake-triage/evals/triggers.json +17 -0
  34. package/dist/agent-src/skills/legal-practice-profile/SKILL.md +100 -0
  35. package/dist/agent-src/skills/legal-practice-profile/evals/triggers.json +17 -0
  36. package/dist/agent-src/skills/nda-triage/SKILL.md +113 -0
  37. package/dist/agent-src/skills/nda-triage/evals/triggers.json +17 -0
  38. package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +36 -9
  39. package/dist/agent-src/skills/recursive-verification/SKILL.md +186 -0
  40. package/dist/agent-src/skills/roadmap-management/SKILL.md +17 -0
  41. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +14 -0
  42. package/dist/agent-src/templates/agent-settings.md +64 -19
  43. package/dist/discovery/deprecation-report.md +1 -1
  44. package/dist/discovery/discovery-manifest.json +278 -23
  45. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  46. package/dist/discovery/discovery-manifest.summary.md +12 -4
  47. package/dist/discovery/orphan-report.md +1 -1
  48. package/dist/discovery/packs.json +50 -8
  49. package/dist/discovery/trust-report.md +7 -5
  50. package/dist/discovery/workspaces.json +34 -6
  51. package/dist/mcp/registry-manifest.json +3 -3
  52. package/dist/router.json +1 -1
  53. package/dist/server/schemas/settings.js +14 -0
  54. package/dist/server/schemas/settings.js.map +1 -1
  55. package/docs/architecture.md +4 -4
  56. package/docs/benchmark.md +46 -0
  57. package/docs/catalog.md +18 -6
  58. package/docs/command-flows.md +7 -1
  59. package/docs/contracts/ai-council-config.md +17 -10
  60. package/docs/contracts/audit-log-v1.md +7 -0
  61. package/docs/contracts/auto-orchestration-v1.md +70 -0
  62. package/docs/contracts/command-clusters.md +1 -1
  63. package/docs/contracts/discovery-manifest.schema.json +2 -2
  64. package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +19 -0
  65. package/docs/decisions/ADR-020-global-only-consumer-scope.md +13 -6
  66. package/docs/decisions/ADR-049-configuration-trust-boundary.md +11 -1
  67. package/docs/decisions/ADR-093-ai-council-config-user-global.md +10 -4
  68. package/docs/decisions/ADR-104-ai-council-config-global-only.md +119 -0
  69. package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +102 -0
  70. package/docs/decisions/ADR-106-recursive-verification-benchmark-gate.md +126 -0
  71. package/docs/decisions/ADR-107-legal-domain-pack-adoption.md +51 -0
  72. package/docs/decisions/ADR-108-open-source-forever-no-commercial-tier.md +46 -0
  73. package/docs/decisions/INDEX.md +5 -0
  74. package/docs/getting-started.md +2 -2
  75. package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +53 -0
  76. package/docs/guidelines/agent-infra/domain-pack-architecture.md +55 -0
  77. package/package.json +1 -1
  78. package/src/config/agent-settings.template.yml +72 -3
  79. package/src/config/discovery/packs.yml +13 -0
  80. package/src/config/discovery/workspaces.yml +7 -0
  81. package/src/scripts/_lib/auto_dispatch.ts +95 -0
  82. package/src/scripts/_lib/host_capability.ts +59 -0
  83. package/src/scripts/_lib/orchestration_gate.ts +98 -0
  84. package/src/scripts/_lib/subagent_bundle.ts +154 -0
  85. package/src/scripts/_lib/subagent_response.ts +82 -0
  86. package/src/scripts/_lib/subagent_routing.ts +81 -0
  87. package/src/scripts/_lib/subagent_spawn.ts +97 -0
  88. package/src/scripts/_lib/subagent_steering.ts +64 -0
  89. package/src/scripts/_lib/verify_budget.ts +54 -0
  90. package/src/scripts/ai_council/advisors.ts +4 -2
  91. package/src/scripts/ai_council/clients.ts +7 -1
  92. package/src/scripts/ai_council/config.ts +22 -17
  93. package/src/scripts/bench_ab_v2_run.ts +127 -0
  94. package/src/scripts/bench_ab_v2_stats.ts +39 -0
  95. package/src/scripts/check_council_config_location.ts +6 -5
  96. package/src/scripts/lint_discovery_vocabulary.ts +6 -0
  97. package/src/scripts/lint_framework_leakage_allowlist.json +21 -0
  98. package/src/scripts/lint_legal_pack.ts +139 -0
  99. package/src/scripts/probe_projection_fidelity.ts +7 -1
  100. package/src/scripts/rdp_gate_classify.ts +131 -0
  101. package/src/scripts/rdp_quality_eval.ts +437 -0
  102. package/src/scripts/schemas/agent-settings.schema.json +11 -0
  103. package/src/scripts/skill_linter.ts +3 -0
@@ -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
+ );
@@ -72,6 +72,17 @@
72
72
  "enum": ["off", "on", "ask"]
73
73
  }
74
74
  }
75
+ },
76
+ "design": {
77
+ "type": "object",
78
+ "additionalProperties": true,
79
+ "properties": {
80
+ "fidelity_mode": {
81
+ "type": "string",
82
+ "enum": ["strict", "structural", "hard-floor"],
83
+ "description": "How strictly the agent must follow a user-provided prototype / mockup / design system. Consumed by the design-fidelity rule. strict = build 1:1, every visible deviation needs confirmation; structural = structure locked, silent gaps fillable with a stated assumption; hard-floor = any deviation is never autonomous."
84
+ }
85
+ }
75
86
  }
76
87
  }
77
88
  }
@@ -1407,6 +1407,7 @@ export function lint_skill(p: string, text: string): LintResult {
1407
1407
  const skillName = basename(p) === 'SKILL.md' ? parentName(p) : stem(p);
1408
1408
  if (skillName && !skillName.includes('-') && skillName.length >= 3) {
1409
1409
  const ALLOWED_BARE_NOUNS = new Set([
1410
+ 'brand',
1410
1411
  'database',
1411
1412
  'devcontainer',
1412
1413
  'docker',
@@ -1414,12 +1415,14 @@ export function lint_skill(p: string, text: string): LintResult {
1414
1415
  'flux',
1415
1416
  'forecasting',
1416
1417
  'grafana',
1418
+ 'iconography',
1417
1419
  'laravel',
1418
1420
  'livewire',
1419
1421
  'markitdown',
1420
1422
  'mcp',
1421
1423
  'openapi',
1422
1424
  'performance',
1425
+ 'premortem',
1423
1426
  'security',
1424
1427
  'terraform',
1425
1428
  'terragrunt',