@event4u/agent-config 8.7.0 → 8.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +52 -0
- package/CONTRIBUTING.md +25 -0
- package/README.md +7 -4
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +1 -1
- package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +28 -16
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +15 -0
- package/dist/agent-src/rules/active-remediation.md +92 -0
- package/dist/agent-src/rules/broken-access-control.md +106 -0
- package/dist/agent-src/rules/communication-through-line.md +42 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +8 -0
- package/dist/agent-src/rules/minimal-safe-diff.md +18 -1
- package/dist/agent-src/rules/prefer-enums-over-literals.md +65 -0
- package/dist/agent-src/rules/senior-engineering-discipline.md +82 -0
- package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +105 -0
- package/dist/agent-src/skills/ai-code-blindspots/evals/triggers.json +52 -0
- package/dist/agent-src/skills/authz-review/SKILL.md +21 -4
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +20 -0
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +104 -0
- package/dist/agent-src/skills/frontend-render-security/evals/triggers.json +52 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +7 -0
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +100 -0
- package/dist/agent-src/skills/supply-chain-intake/evals/triggers.json +52 -0
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +43 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +24 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +195 -15
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +13 -5
- package/dist/discovery/trust-report.md +11 -11
- package/dist/discovery/workspaces.json +12 -4
- package/dist/install/install.mjs +5 -2
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
- package/dist/mcp/registry-manifest.json +4 -4
- package/dist/router.json +1 -1
- package/dist/server/schemas/settings.js +2 -1
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +30 -2
- package/docs/architecture.md +2 -2
- package/docs/catalog.md +14 -0
- package/docs/contracts/mcp-tool-inventory.md +31 -30
- package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +6 -0
- package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +2 -2
- package/docs/decisions/ADR-117-subagents-auto-default-on.md +88 -0
- package/docs/decisions/INDEX.md +1 -0
- package/docs/experiences/content_creator.md +2 -0
- package/docs/experiences/finance.md +2 -0
- package/docs/experiences/founder.md +2 -0
- package/docs/experiences/ops.md +2 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/memory-access.md +12 -0
- package/docs/proof.md +45 -3
- package/docs/release-runbook.md +138 -0
- package/docs/second-brain-scope.md +73 -0
- package/docs/succession.md +80 -0
- package/package.json +2 -2
- package/src/config/agent-settings.template.yml +25 -6
- package/src/scripts/_lib/orchestration_gate.ts +12 -8
- package/src/scripts/_lib/orchestration_savings.ts +127 -0
- package/src/scripts/build_proof.ts +38 -0
- package/src/scripts/chat_history.ts +79 -7
- package/src/scripts/domain_soundness_status.ts +236 -0
- package/src/scripts/generate_index.ts +14 -0
- package/src/scripts/hot_context_hook.ts +47 -2
- package/src/scripts/lint_behavioural_eval_freshness.ts +140 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +22 -3
- package/src/scripts/mcp_server/tools.ts +159 -1
- package/src/scripts/memory_lookup.ts +184 -6
- package/src/scripts/memory_replay.ts +285 -0
- package/src/scripts/orchestration_savings_report.ts +100 -0
- package/src/scripts/schemas/domain-truth.schema.json +94 -0
- package/src/scripts/schemas/evals.schema.json +113 -0
- package/src/scripts/second_brain_score.ts +186 -0
- package/src/scripts/session_memory_index.ts +94 -0
- package/src/scripts/skill_eval_coverage.ts +318 -0
- package/src/server/schemas/settings.ts +5 -2
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Memory-retrieval replay rig (road-to-memory-retrieval-economy Phase 0).
|
|
4
|
+
*
|
|
5
|
+
* Replays the corpus-derived query set (tests/fixtures/memory-replay/ —
|
|
6
|
+
* provenance note in queries.yml; council 2026-07-08 Option-A verdict)
|
|
7
|
+
* against the fixture memory root and measures, with the REAL tokenizer
|
|
8
|
+
* (token_count.ts, cl100k_base — never chars/4, roadmap D2):
|
|
9
|
+
*
|
|
10
|
+
* --baseline full-envelope payload per query → pins
|
|
11
|
+
* internal/bench/reports/memory-retrieval-baseline.json
|
|
12
|
+
* --paired index-mode envelope + memory_get(needed) payload vs the
|
|
13
|
+
* full baseline → run report + the three falsification
|
|
14
|
+
* checks from Phase 1b (token saving ≥30%, zero missed
|
|
15
|
+
* needed-fetches; the judge arm is reported as
|
|
16
|
+
* out-of-band — see report field `judge`)
|
|
17
|
+
* --check CI regression: index-mode payload for the replay set must
|
|
18
|
+
* not exceed the recorded baseline's index payload by >5%.
|
|
19
|
+
* INERT (exit 0) while no baseline file exists.
|
|
20
|
+
*
|
|
21
|
+
* Exit codes: 0 ok/inert · 1 check failed or paired falsification red ·
|
|
22
|
+
* 2 usage/fixture error.
|
|
23
|
+
*/
|
|
24
|
+
import * as fs from 'node:fs';
|
|
25
|
+
import * as path from 'node:path';
|
|
26
|
+
import process from 'node:process';
|
|
27
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
28
|
+
|
|
29
|
+
import * as YAML from 'yaml';
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
_setIntakeRoot,
|
|
33
|
+
_setKnowledgeRoot,
|
|
34
|
+
_setMemoryRoot,
|
|
35
|
+
retrieve_v1,
|
|
36
|
+
} from './memory_lookup.js';
|
|
37
|
+
import { gpt_tokens, TIKTOKEN_AVAILABLE } from './_lib/token_count.js';
|
|
38
|
+
|
|
39
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
40
|
+
export const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
41
|
+
export const FIXTURE_DIR = path.join(ROOT, 'tests', 'fixtures', 'memory-replay');
|
|
42
|
+
export const BASELINE_PATH = path.join(
|
|
43
|
+
ROOT,
|
|
44
|
+
'internal',
|
|
45
|
+
'bench',
|
|
46
|
+
'reports',
|
|
47
|
+
'memory-retrieval-baseline.json',
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
export interface ReplayQuery {
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly types: string[];
|
|
53
|
+
readonly keys: string[];
|
|
54
|
+
readonly limit: number;
|
|
55
|
+
readonly needed: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function load_queries(): ReplayQuery[] {
|
|
59
|
+
const raw = YAML.parse(fs.readFileSync(path.join(FIXTURE_DIR, 'queries.yml'), 'utf-8')) as {
|
|
60
|
+
queries: ReplayQuery[];
|
|
61
|
+
};
|
|
62
|
+
return raw.queries;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Point the lookup module at the fixture memory tree. */
|
|
66
|
+
export function bind_fixture_root(): void {
|
|
67
|
+
const memRoot = path.join(FIXTURE_DIR, 'memory-root');
|
|
68
|
+
_setMemoryRoot(memRoot);
|
|
69
|
+
_setKnowledgeRoot(path.join(memRoot, 'knowledge'));
|
|
70
|
+
_setIntakeRoot(path.join(memRoot, 'intake-does-not-exist'));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Real-tokenizer size of an envelope, serialized the way a host sees it. */
|
|
74
|
+
export function envelope_tokens(envelope: unknown): number {
|
|
75
|
+
return gpt_tokens(JSON.stringify(envelope)).tokens;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface PerQuery {
|
|
79
|
+
id: string;
|
|
80
|
+
full_tokens: number;
|
|
81
|
+
needed_found: number;
|
|
82
|
+
needed_total: number;
|
|
83
|
+
index_tokens?: number;
|
|
84
|
+
fetch_tokens?: number;
|
|
85
|
+
index_plus_fetch_tokens?: number;
|
|
86
|
+
saving_pct?: number;
|
|
87
|
+
missed_needed?: string[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** IDs present in a v1 envelope's entries. */
|
|
91
|
+
function _entry_ids(envelope: Record<string, unknown>): Set<string> {
|
|
92
|
+
const out = new Set<string>();
|
|
93
|
+
for (const e of (envelope['entries'] as Array<Record<string, unknown>>) ?? []) {
|
|
94
|
+
out.add(String(e['id'] ?? ''));
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type RetrieveV1 = (
|
|
100
|
+
types: string[],
|
|
101
|
+
keys: string[],
|
|
102
|
+
limit?: number,
|
|
103
|
+
options?: { detail?: 'index' | 'full' },
|
|
104
|
+
) => Record<string, unknown>;
|
|
105
|
+
|
|
106
|
+
/** Optional Phase-1 surfaces, absent before P1 lands. */
|
|
107
|
+
async function _phase1(): Promise<{
|
|
108
|
+
retrieve_v1_detail: RetrieveV1 | null;
|
|
109
|
+
memory_get: ((ids: string[]) => Record<string, unknown>) | null;
|
|
110
|
+
}> {
|
|
111
|
+
const mod = (await import('./memory_lookup.js')) as Record<string, unknown>;
|
|
112
|
+
const hasDetail = typeof mod['retrieve_v1'] === 'function' && (mod['retrieve_v1'] as { length: number }).length >= 0;
|
|
113
|
+
const memoryGet = typeof mod['memory_get_v1'] === 'function'
|
|
114
|
+
? (mod['memory_get_v1'] as (ids: string[]) => Record<string, unknown>)
|
|
115
|
+
: null;
|
|
116
|
+
// Detail support is feature-detected by probing the options seam: before
|
|
117
|
+
// Phase 1, passing options is harmless (extra arg ignored) but index mode
|
|
118
|
+
// is only REAL when memory_get exists too.
|
|
119
|
+
return { retrieve_v1_detail: hasDetail ? (retrieve_v1 as unknown as RetrieveV1) : null, memory_get: memoryGet };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function run(mode: 'baseline' | 'paired' | 'check'): Promise<number> {
|
|
123
|
+
bind_fixture_root();
|
|
124
|
+
const queries = load_queries();
|
|
125
|
+
if (queries.length < 20) {
|
|
126
|
+
process.stderr.write(`memory_replay: replay set too small (${queries.length} < 20)\n`);
|
|
127
|
+
return 2;
|
|
128
|
+
}
|
|
129
|
+
const p1 = await _phase1();
|
|
130
|
+
const perQuery: PerQuery[] = [];
|
|
131
|
+
|
|
132
|
+
for (const q of queries) {
|
|
133
|
+
const full = retrieve_v1(q.types, q.keys, q.limit);
|
|
134
|
+
const fullIds = _entry_ids(full);
|
|
135
|
+
const foundNeeded = q.needed.filter((n) => fullIds.has(n));
|
|
136
|
+
const row: PerQuery = {
|
|
137
|
+
id: q.id,
|
|
138
|
+
full_tokens: envelope_tokens(full),
|
|
139
|
+
needed_found: foundNeeded.length,
|
|
140
|
+
needed_total: q.needed.length,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
if (mode !== 'baseline' && p1.retrieve_v1_detail !== null && p1.memory_get !== null) {
|
|
144
|
+
const index = p1.retrieve_v1_detail(q.types, q.keys, q.limit, { detail: 'index' });
|
|
145
|
+
const indexIds = _entry_ids(index);
|
|
146
|
+
const missed = q.needed.filter((n) => !indexIds.has(n));
|
|
147
|
+
const fetched = p1.memory_get(q.needed);
|
|
148
|
+
row.index_tokens = envelope_tokens(index);
|
|
149
|
+
row.fetch_tokens = envelope_tokens(fetched);
|
|
150
|
+
row.index_plus_fetch_tokens = row.index_tokens + row.fetch_tokens;
|
|
151
|
+
row.saving_pct =
|
|
152
|
+
Math.round((1 - row.index_plus_fetch_tokens / row.full_tokens) * 1000) / 10;
|
|
153
|
+
row.missed_needed = missed;
|
|
154
|
+
}
|
|
155
|
+
perQuery.push(row);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const agg = {
|
|
159
|
+
queries: perQuery.length,
|
|
160
|
+
full_tokens: perQuery.reduce((s, r) => s + r.full_tokens, 0),
|
|
161
|
+
needed_recall: perQuery.reduce((s, r) => s + r.needed_found, 0) /
|
|
162
|
+
perQuery.reduce((s, r) => s + r.needed_total, 0),
|
|
163
|
+
index_plus_fetch_tokens: perQuery.every((r) => r.index_plus_fetch_tokens !== undefined)
|
|
164
|
+
? perQuery.reduce((s, r) => s + (r.index_plus_fetch_tokens ?? 0), 0)
|
|
165
|
+
: null,
|
|
166
|
+
};
|
|
167
|
+
const saving_pct = agg.index_plus_fetch_tokens === null
|
|
168
|
+
? null
|
|
169
|
+
: Math.round((1 - agg.index_plus_fetch_tokens / agg.full_tokens) * 1000) / 10;
|
|
170
|
+
|
|
171
|
+
const report = {
|
|
172
|
+
schema_version: 1,
|
|
173
|
+
kind: `memory-retrieval-${mode}`,
|
|
174
|
+
roadmap: 'road-to-memory-retrieval-economy',
|
|
175
|
+
corpus_provenance:
|
|
176
|
+
'corpus-derived (council 2026-07-08 Option A) — see tests/fixtures/memory-replay/queries.yml header; claims scoped to corpus-derived replay queries',
|
|
177
|
+
tokenizer: TIKTOKEN_AVAILABLE ? 'cl100k_base (tiktoken)' : 'PROXY chars/4 — NOT evidence (D2)',
|
|
178
|
+
aggregate: { ...agg, saving_pct },
|
|
179
|
+
per_query: perQuery,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (mode === 'baseline') {
|
|
183
|
+
fs.mkdirSync(path.dirname(BASELINE_PATH), { recursive: true });
|
|
184
|
+
fs.writeFileSync(BASELINE_PATH, JSON.stringify(report, null, 2) + '\n');
|
|
185
|
+
process.stdout.write(
|
|
186
|
+
`✅ baseline pinned: ${agg.queries} queries · full=${agg.full_tokens} tok · ` +
|
|
187
|
+
`needed-recall=${(agg.needed_recall * 100).toFixed(1)}%\n`,
|
|
188
|
+
);
|
|
189
|
+
return agg.needed_recall === 1 ? 0 : 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (mode === 'paired') {
|
|
193
|
+
if (agg.index_plus_fetch_tokens === null) {
|
|
194
|
+
process.stderr.write(
|
|
195
|
+
'memory_replay: paired mode needs Phase 1 (detail + memory_get_v1) — not available\n',
|
|
196
|
+
);
|
|
197
|
+
return 2;
|
|
198
|
+
}
|
|
199
|
+
const missedTotal = perQuery.reduce((s, r) => s + (r.missed_needed?.length ?? 0), 0);
|
|
200
|
+
const out = path.join(
|
|
201
|
+
path.dirname(BASELINE_PATH),
|
|
202
|
+
'memory-retrieval-run.json',
|
|
203
|
+
);
|
|
204
|
+
const checks = {
|
|
205
|
+
'a-token-saving-ge-30pct': (saving_pct ?? 0) >= 30,
|
|
206
|
+
'b-judge-win-rate': 'OUT-OF-BAND — run check_quality_regression paired judge before flip sign-off',
|
|
207
|
+
'c-zero-missed-needed': missedTotal === 0,
|
|
208
|
+
};
|
|
209
|
+
fs.writeFileSync(out, JSON.stringify({ ...report, falsification: checks }, null, 2) + '\n');
|
|
210
|
+
process.stdout.write(
|
|
211
|
+
`paired: full=${agg.full_tokens} vs index+fetch=${agg.index_plus_fetch_tokens} ` +
|
|
212
|
+
`(saving ${saving_pct}%) · missed-needed=${missedTotal} → ${out}\n`,
|
|
213
|
+
);
|
|
214
|
+
return checks['a-token-saving-ge-30pct'] && checks['c-zero-missed-needed'] ? 0 : 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// --check: inert without a baseline; else compare index payload drift.
|
|
218
|
+
if (!fs.existsSync(BASELINE_PATH)) {
|
|
219
|
+
process.stdout.write('memory_replay --check: no baseline pinned — inert.\n');
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
if (agg.index_plus_fetch_tokens === null) {
|
|
223
|
+
// Phase 1 not landed: guard the FULL payload against >5% growth
|
|
224
|
+
// instead, so the baseline still means something pre-split.
|
|
225
|
+
const pinned = JSON.parse(fs.readFileSync(BASELINE_PATH, 'utf-8')) as {
|
|
226
|
+
aggregate: { full_tokens: number };
|
|
227
|
+
};
|
|
228
|
+
const limitTok = pinned.aggregate.full_tokens * 1.05;
|
|
229
|
+
if (agg.full_tokens > limitTok) {
|
|
230
|
+
process.stdout.write(
|
|
231
|
+
`❌ memory_replay --check: full payload ${agg.full_tokens} exceeds baseline ` +
|
|
232
|
+
`${pinned.aggregate.full_tokens} by >5%\n`,
|
|
233
|
+
);
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
process.stdout.write('✅ memory_replay --check: within baseline (+5%).\n');
|
|
237
|
+
return 0;
|
|
238
|
+
}
|
|
239
|
+
const pinned = JSON.parse(fs.readFileSync(BASELINE_PATH, 'utf-8')) as {
|
|
240
|
+
aggregate: { full_tokens: number };
|
|
241
|
+
};
|
|
242
|
+
const limitTok = pinned.aggregate.full_tokens * 1.05;
|
|
243
|
+
if ((agg.index_plus_fetch_tokens ?? 0) > limitTok) {
|
|
244
|
+
process.stdout.write(
|
|
245
|
+
`❌ memory_replay --check: index payload ${agg.index_plus_fetch_tokens} exceeds ` +
|
|
246
|
+
`baseline full ${pinned.aggregate.full_tokens} by >5%\n`,
|
|
247
|
+
);
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
250
|
+
process.stdout.write('✅ memory_replay --check: index payload within baseline (+5%).\n');
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {
|
|
255
|
+
const mode = argv.includes('--baseline')
|
|
256
|
+
? 'baseline'
|
|
257
|
+
: argv.includes('--paired')
|
|
258
|
+
? 'paired'
|
|
259
|
+
: argv.includes('--check')
|
|
260
|
+
? 'check'
|
|
261
|
+
: null;
|
|
262
|
+
if (mode === null) {
|
|
263
|
+
process.stderr.write('usage: memory_replay.ts --baseline | --paired | --check\n');
|
|
264
|
+
return 2;
|
|
265
|
+
}
|
|
266
|
+
return run(mode);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function _isCliEntry(): boolean {
|
|
270
|
+
if (process.argv[1] === undefined) return false;
|
|
271
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
272
|
+
if (import.meta.url === argvUrl) return true;
|
|
273
|
+
try {
|
|
274
|
+
return (
|
|
275
|
+
fs.realpathSync(fileURLToPath(import.meta.url)) ===
|
|
276
|
+
fs.realpathSync(path.resolve(process.argv[1]))
|
|
277
|
+
);
|
|
278
|
+
} catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (_isCliEntry()) {
|
|
284
|
+
main().then((code) => process.exit(code));
|
|
285
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestration savings report — CLI.
|
|
3
|
+
*
|
|
4
|
+
* Reads accumulated orchestration telemetry from the audit-log-v1 JSONL files
|
|
5
|
+
* and prints an aggregate token-savings report. Read-only; no network, no
|
|
6
|
+
* mutation. Handles a missing/empty audit dir gracefully.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ./scripts-run src/scripts/orchestration_savings_report [--dir <path>] [--format text|json]
|
|
10
|
+
*
|
|
11
|
+
* Default dir: agents/runtime/state/audit (the audit-log-v1 path where the
|
|
12
|
+
* orchestration layer writes telemetry lines). See
|
|
13
|
+
* `src/agent-src/contexts/execution/orchestration-telemetry.md`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import { pathToFileURL } from 'node:url';
|
|
19
|
+
|
|
20
|
+
import { aggregateOrchestrationSavings, type AuditLine, type SavingsReport } from './_lib/orchestration_savings.js';
|
|
21
|
+
|
|
22
|
+
const DEFAULT_DIR = 'agents/runtime/state/audit';
|
|
23
|
+
|
|
24
|
+
interface Options {
|
|
25
|
+
dir: string;
|
|
26
|
+
format: 'text' | 'json';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseArgs(argv: string[]): Options {
|
|
30
|
+
const opts: Options = { dir: DEFAULT_DIR, format: 'text' };
|
|
31
|
+
for (let i = 0; i < argv.length; i++) {
|
|
32
|
+
const a = argv[i];
|
|
33
|
+
if (a === undefined) continue;
|
|
34
|
+
if (a === '--dir') opts.dir = argv[++i] ?? opts.dir;
|
|
35
|
+
else if (a.startsWith('--dir=')) opts.dir = a.slice('--dir='.length);
|
|
36
|
+
else if (a === '--format') opts.format = (argv[++i] as Options['format']) ?? opts.format;
|
|
37
|
+
else if (a.startsWith('--format=')) opts.format = a.slice('--format='.length) as Options['format'];
|
|
38
|
+
}
|
|
39
|
+
if (opts.format !== 'text' && opts.format !== 'json') opts.format = 'text';
|
|
40
|
+
return opts;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Read + parse every `.jsonl` line in `dir`. Malformed lines are skipped. */
|
|
44
|
+
export function readAuditLines(dir: string): AuditLine[] {
|
|
45
|
+
if (!fs.existsSync(dir)) return [];
|
|
46
|
+
const lines: AuditLine[] = [];
|
|
47
|
+
for (const entry of fs.readdirSync(dir).sort()) {
|
|
48
|
+
if (!entry.endsWith('.jsonl')) continue;
|
|
49
|
+
const raw = fs.readFileSync(path.join(dir, entry), 'utf8');
|
|
50
|
+
for (const ln of raw.split('\n')) {
|
|
51
|
+
const trimmed = ln.trim();
|
|
52
|
+
if (!trimmed) continue;
|
|
53
|
+
try {
|
|
54
|
+
lines.push(JSON.parse(trimmed) as AuditLine);
|
|
55
|
+
} catch {
|
|
56
|
+
// Skip malformed line — a partial write must not crash the report.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return lines;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function renderText(r: SavingsReport, dir: string): string {
|
|
64
|
+
const out: string[] = [];
|
|
65
|
+
out.push('Orchestration savings report');
|
|
66
|
+
out.push(` source: ${dir}`);
|
|
67
|
+
out.push(` dispatches: ${r.dispatches} (total spawns: ${r.total_spawns})`);
|
|
68
|
+
out.push(` net token_delta: ${r.net_token_delta} (negative = net saved)`);
|
|
69
|
+
out.push(` tokens saved: ${r.tokens_saved} tokens added: ${r.tokens_added}`);
|
|
70
|
+
out.push(` provenance: measured ${r.by_provenance.measured.dispatches} (Δ ${r.by_provenance.measured.net_token_delta}) · estimated ${r.by_provenance.estimated.dispatches} (Δ ${r.by_provenance.estimated.net_token_delta})`);
|
|
71
|
+
out.push(` measured share: ${(r.measured_share * 100).toFixed(0)}%`);
|
|
72
|
+
if (Object.keys(r.by_tier).length) {
|
|
73
|
+
out.push(' by tier (Δ tokens):');
|
|
74
|
+
for (const [tier, d] of Object.entries(r.by_tier).sort()) out.push(` ${tier}: ${d}`);
|
|
75
|
+
}
|
|
76
|
+
if (Object.keys(r.by_task_class).length) {
|
|
77
|
+
out.push(' by task_class (Δ tokens):');
|
|
78
|
+
for (const [cls, d] of Object.entries(r.by_task_class).sort()) out.push(` ${cls}: ${d}`);
|
|
79
|
+
}
|
|
80
|
+
out.push(' notes:');
|
|
81
|
+
for (const n of r.notes) out.push(` - ${n}`);
|
|
82
|
+
return out.join('\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
86
|
+
const opts = parseArgs(argv);
|
|
87
|
+
const lines = readAuditLines(opts.dir);
|
|
88
|
+
const report = aggregateOrchestrationSavings(lines);
|
|
89
|
+
if (opts.format === 'json') {
|
|
90
|
+
process.stdout.write(JSON.stringify({ ...report, source: opts.dir }, null, 2) + '\n');
|
|
91
|
+
} else {
|
|
92
|
+
process.stdout.write(renderText(report, opts.dir) + '\n');
|
|
93
|
+
}
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (process.argv[1] !== undefined) {
|
|
98
|
+
const invokedUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
99
|
+
if (import.meta.url === invokedUrl) process.exit(main());
|
|
100
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/event4u-app/agent-config/scripts/schemas/domain-truth.schema.json",
|
|
4
|
+
"x-schemaVersion": "1.0.0",
|
|
5
|
+
"title": "Domain-truth fixtures (domain-truth.json)",
|
|
6
|
+
"$comment": "Domain-SOUNDNESS fixtures for non-coding default-surface skills (road-to-domain-soundness Phase 1). DISTINCT from evals.json (behavioural output quality) and triggers.json (activation): a domain-truth case asserts the embedded domain heuristic is actually CORRECT off the forged TS/PHP stacks. Every case's answer key must be traceable to a CITED external method/standard — never the skill's own output (that validates nothing). A skill lacking this file is `unvalidated`; presence + a passing run makes it `validated`.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"required": ["skill", "domain", "cases"],
|
|
10
|
+
"properties": {
|
|
11
|
+
"skill": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"pattern": "^[a-z][a-z0-9-]*$",
|
|
14
|
+
"description": "Skill slug; must match the parent skill directory name."
|
|
15
|
+
},
|
|
16
|
+
"domain": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"enum": ["finance", "founder", "ops", "content"],
|
|
19
|
+
"description": "The non-coding domain whose ground truth this fixture checks."
|
|
20
|
+
},
|
|
21
|
+
"_note": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Optional free-text note (calibration / provenance status). Advisory."
|
|
24
|
+
},
|
|
25
|
+
"cases": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"minItems": 1,
|
|
28
|
+
"description": "One or more domain-truth cases: a scenario + a domain-correct answer key with a SOURCED rationale.",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": false,
|
|
32
|
+
"required": ["id", "scenario", "check", "source"],
|
|
33
|
+
"properties": {
|
|
34
|
+
"id": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
37
|
+
"description": "Stable case slug, unique within this file."
|
|
38
|
+
},
|
|
39
|
+
"scenario": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"minLength": 1,
|
|
42
|
+
"description": "The input scenario handed to the skill under test."
|
|
43
|
+
},
|
|
44
|
+
"source": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"minLength": 1,
|
|
47
|
+
"description": "The CITED external method/standard the answer key is traceable to (a finance text, an IC framework, a named practice). A case whose key cannot cite an external basis is not a domain-truth case — it is rejected. NEVER the skill's own output."
|
|
48
|
+
},
|
|
49
|
+
"check": {
|
|
50
|
+
"description": "How soundness is judged. Deterministic where the domain has a right number; rubric where it does not (the rubric criterion must name the cited practice, graded by a pinned judge, recorded as a known-limit — never a hidden LLM-judge).",
|
|
51
|
+
"oneOf": [
|
|
52
|
+
{
|
|
53
|
+
"type": "object",
|
|
54
|
+
"additionalProperties": false,
|
|
55
|
+
"required": ["kind", "expected"],
|
|
56
|
+
"properties": {
|
|
57
|
+
"kind": { "const": "deterministic" },
|
|
58
|
+
"expected": {
|
|
59
|
+
"type": ["number", "string"],
|
|
60
|
+
"description": "The computed-correct answer (e.g. a runway in months, an NPV) with the working shown in `rationale`."
|
|
61
|
+
},
|
|
62
|
+
"tolerance": {
|
|
63
|
+
"type": "number",
|
|
64
|
+
"minimum": 0,
|
|
65
|
+
"description": "Absolute tolerance for a numeric expected value (0 = exact)."
|
|
66
|
+
},
|
|
67
|
+
"rationale": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"minLength": 1,
|
|
70
|
+
"description": "The working that derives `expected` from the cited method, so a maintainer without the domain can check the key against the source, not against the skill."
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"required": ["kind", "expected", "rationale"]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"type": "object",
|
|
77
|
+
"additionalProperties": false,
|
|
78
|
+
"required": ["kind", "criterion"],
|
|
79
|
+
"properties": {
|
|
80
|
+
"kind": { "const": "rubric" },
|
|
81
|
+
"criterion": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"minLength": 1,
|
|
84
|
+
"description": "Pass criterion phrased as \"matches <named practice>\", graded by a pinned judge. NON-deterministic — recorded as a known-limit with a witness."
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/event4u-app/agent-config/scripts/schemas/evals.schema.json",
|
|
4
|
+
"x-schemaVersion": "1.0.0",
|
|
5
|
+
"title": "Skill behavioural evals (evals.json)",
|
|
6
|
+
"$comment": "Behavioural-eval spec consumed by run_skill_evals.ts (scaffold/aggregate/report) and counted by skill_eval_coverage.ts. Distinct from triggers.json (activation/routing axis). Roadmap: road-to-skill-eval-coverage Phase 1. Assertion kinds mirror _grade_assertions() in run_skill_evals.ts exactly — add a kind there before adding it here.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"required": ["skill", "scenarios"],
|
|
10
|
+
"properties": {
|
|
11
|
+
"skill": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"pattern": "^[a-z][a-z0-9-]*$",
|
|
14
|
+
"description": "Skill slug; must match the parent skill directory name (evals/ lives under src/skills/<skill>/)."
|
|
15
|
+
},
|
|
16
|
+
"_calibration": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Optional free-text note recording calibration status / provenance (e.g. uncalibrated finding_floor placeholders). Advisory; ignored by the grader."
|
|
19
|
+
},
|
|
20
|
+
"skill_body_sha": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"pattern": "^[0-9a-f]{64}$",
|
|
23
|
+
"description": "Optional SHA-256 of the skill's SKILL.md body at the time these evals were authored/last verified. When present, lint_behavioural_eval_freshness flags the eval stale if the current SKILL.md sha differs — the body changed, so the eval may no longer exercise it. Absent = unpinned (out of scope for the staleness gate), mirroring lint_eval_freshness's upstream:null handling."
|
|
24
|
+
},
|
|
25
|
+
"scenarios": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"minItems": 1,
|
|
28
|
+
"description": "One or more behavioural cases. Each pairs a prompt fixture with deterministic pass/fail assertions on the produced output.",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": false,
|
|
32
|
+
"required": ["id", "prompt", "assertions"],
|
|
33
|
+
"properties": {
|
|
34
|
+
"id": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
37
|
+
"description": "Stable scenario slug, unique within this file (run directories are named by it)."
|
|
38
|
+
},
|
|
39
|
+
"prompt": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"minLength": 1,
|
|
42
|
+
"description": "The input fixture handed to the skill under test."
|
|
43
|
+
},
|
|
44
|
+
"assertions": {
|
|
45
|
+
"type": "array",
|
|
46
|
+
"minItems": 1,
|
|
47
|
+
"description": "Pass/fail checks on the output. Prefer deterministic kinds (contains/file_exists/finding_floor); use rubric only where a deterministic check is impossible, and pin the judge as a known limit.",
|
|
48
|
+
"items": {
|
|
49
|
+
"oneOf": [
|
|
50
|
+
{
|
|
51
|
+
"type": "object",
|
|
52
|
+
"additionalProperties": false,
|
|
53
|
+
"required": ["kind", "value"],
|
|
54
|
+
"properties": {
|
|
55
|
+
"kind": { "const": "contains" },
|
|
56
|
+
"value": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"minLength": 1,
|
|
59
|
+
"description": "Substring the output must contain (deterministic)."
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"type": "object",
|
|
65
|
+
"additionalProperties": false,
|
|
66
|
+
"required": ["kind", "path"],
|
|
67
|
+
"properties": {
|
|
68
|
+
"kind": { "const": "file_exists" },
|
|
69
|
+
"path": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"minLength": 1,
|
|
72
|
+
"description": "Path (run-relative or absolute) the skill must have produced (deterministic)."
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"type": "object",
|
|
78
|
+
"additionalProperties": false,
|
|
79
|
+
"required": ["kind", "n"],
|
|
80
|
+
"properties": {
|
|
81
|
+
"kind": { "const": "finding_floor" },
|
|
82
|
+
"n": {
|
|
83
|
+
"type": "integer",
|
|
84
|
+
"minimum": 0,
|
|
85
|
+
"description": "Minimum number of findings the output must carry (counted deterministically). 0 is a trivially-passing floor (the grader passes when count >= n) — used where the case only guards against over-reporting."
|
|
86
|
+
},
|
|
87
|
+
"pattern": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "Optional regex counted globally; without it, markdown list-item lines are counted (see _count_findings)."
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"type": "object",
|
|
95
|
+
"additionalProperties": false,
|
|
96
|
+
"required": ["kind", "criterion"],
|
|
97
|
+
"properties": {
|
|
98
|
+
"kind": { "const": "rubric" },
|
|
99
|
+
"criterion": {
|
|
100
|
+
"type": "string",
|
|
101
|
+
"minLength": 1,
|
|
102
|
+
"description": "Natural-language pass criterion graded by a sub-agent. NON-deterministic — the grader returns pass:null until a pinned judge fills it. Use only where no deterministic check exists; record the judge as a known limit."
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|