crewhaus 0.1.7 → 0.2.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/README.md +10 -3
- package/dist/advice-apply.d.ts +182 -0
- package/dist/advice-apply.js +286 -0
- package/dist/advise-rules.d.ts +348 -0
- package/dist/advise-rules.js +905 -0
- package/dist/alert-sink.d.ts +48 -0
- package/dist/alert-sink.js +86 -0
- package/dist/approval-gate.d.ts +127 -0
- package/dist/approval-gate.js +254 -0
- package/dist/audit-verify.d.ts +69 -0
- package/dist/audit-verify.js +97 -0
- package/dist/autodistill.d.ts +113 -0
- package/dist/autodistill.js +256 -0
- package/dist/channel-provision.d.ts +360 -0
- package/dist/channel-provision.js +881 -0
- package/dist/ci-scaffold.d.ts +31 -0
- package/dist/ci-scaffold.js +343 -0
- package/dist/compile-check.d.ts +90 -0
- package/dist/compile-check.js +285 -0
- package/dist/compliance-schedule.d.ts +35 -0
- package/dist/compliance-schedule.js +36 -0
- package/dist/context-pressure.d.ts +80 -0
- package/dist/context-pressure.js +166 -0
- package/dist/dataset-mine.d.ts +172 -0
- package/dist/dataset-mine.js +403 -0
- package/dist/datasets.d.ts +124 -0
- package/dist/datasets.js +260 -0
- package/dist/deploy-canary.d.ts +83 -0
- package/dist/deploy-canary.js +87 -0
- package/dist/doctor-checks.d.ts +33 -0
- package/dist/doctor-checks.js +92 -0
- package/dist/doctor-detect.d.ts +108 -0
- package/dist/doctor-detect.js +214 -0
- package/dist/doctor-fix.d.ts +81 -0
- package/dist/doctor-fix.js +164 -0
- package/dist/egress-triage.d.ts +121 -0
- package/dist/egress-triage.js +261 -0
- package/dist/eval-bridge.d.ts +114 -0
- package/dist/eval-bridge.js +158 -0
- package/dist/eval-coverage.d.ts +140 -0
- package/dist/eval-coverage.js +428 -0
- package/dist/eval-history.d.ts +48 -0
- package/dist/eval-history.js +157 -0
- package/dist/eval-matrix.d.ts +80 -0
- package/dist/eval-matrix.js +182 -0
- package/dist/eval-sentinel.d.ts +65 -0
- package/dist/eval-sentinel.js +132 -0
- package/dist/faq.d.ts +68 -0
- package/dist/faq.js +168 -0
- package/dist/feedback.d.ts +246 -0
- package/dist/feedback.js +612 -0
- package/dist/fewshot.d.ts +83 -0
- package/dist/fewshot.js +158 -0
- package/dist/fleet.d.ts +207 -0
- package/dist/fleet.js +488 -0
- package/dist/flywheel.d.ts +193 -0
- package/dist/flywheel.js +519 -0
- package/dist/graders-suggest.d.ts +186 -0
- package/dist/graders-suggest.js +658 -0
- package/dist/incident.d.ts +99 -0
- package/dist/incident.js +217 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +11727 -740
- package/dist/init-interactive.d.ts +105 -0
- package/dist/init-interactive.js +208 -0
- package/dist/intents.d.ts +105 -0
- package/dist/intents.js +292 -0
- package/dist/judge-calibrate.d.ts +137 -0
- package/dist/judge-calibrate.js +247 -0
- package/dist/justification-calibrate.d.ts +150 -0
- package/dist/justification-calibrate.js +262 -0
- package/dist/justification-gate.d.ts +27 -6
- package/dist/justification-gate.js +30 -6
- package/dist/knowledge-sync.d.ts +179 -0
- package/dist/knowledge-sync.js +551 -0
- package/dist/lessons.d.ts +87 -0
- package/dist/lessons.js +207 -0
- package/dist/lint.d.ts +127 -0
- package/dist/lint.js +226 -0
- package/dist/loadtest.d.ts +114 -0
- package/dist/loadtest.js +196 -0
- package/dist/marketplace-cli.d.ts +110 -0
- package/dist/marketplace-cli.js +250 -0
- package/dist/mcp-doctor.d.ts +121 -0
- package/dist/mcp-doctor.js +249 -0
- package/dist/model-scan.d.ts +116 -0
- package/dist/model-scan.js +226 -0
- package/dist/onchain-tune.d.ts +164 -0
- package/dist/onchain-tune.js +346 -0
- package/dist/permissions-suggest.d.ts +126 -0
- package/dist/permissions-suggest.js +333 -0
- package/dist/pii-tune.d.ts +107 -0
- package/dist/pii-tune.js +122 -0
- package/dist/propose.d.ts +117 -0
- package/dist/propose.js +184 -0
- package/dist/refresh-goldens.d.ts +82 -0
- package/dist/refresh-goldens.js +221 -0
- package/dist/regression-pin.d.ts +160 -0
- package/dist/regression-pin.js +281 -0
- package/dist/retention.d.ts +193 -0
- package/dist/retention.js +607 -0
- package/dist/retire.d.ts +118 -0
- package/dist/retire.js +291 -0
- package/dist/right-size.d.ts +100 -0
- package/dist/right-size.js +123 -0
- package/dist/scaffold-evals.d.ts +138 -0
- package/dist/scaffold-evals.js +410 -0
- package/dist/scope-audit-drift.d.ts +139 -0
- package/dist/scope-audit-drift.js +260 -0
- package/dist/security-corpus.d.ts +237 -0
- package/dist/security-corpus.js +516 -0
- package/dist/security-digest.d.ts +173 -0
- package/dist/security-digest.js +650 -0
- package/dist/sessions-index.d.ts +27 -0
- package/dist/sessions-index.js +51 -0
- package/dist/slo-doctor.d.ts +67 -0
- package/dist/slo-doctor.js +119 -0
- package/dist/slo-sink.d.ts +96 -0
- package/dist/slo-sink.js +107 -0
- package/dist/spec-changelog.d.ts +102 -0
- package/dist/spec-changelog.js +237 -0
- package/dist/state-backup.d.ts +223 -0
- package/dist/state-backup.js +648 -0
- package/dist/tools-cli.d.ts +170 -0
- package/dist/tools-cli.js +298 -0
- package/dist/triage.d.ts +202 -0
- package/dist/triage.js +403 -0
- package/dist/upgrade.d.ts +57 -0
- package/dist/upgrade.js +113 -0
- package/dist/version.d.ts +6 -0
- package/dist/version.js +27 -0
- package/dist/voice-eval.d.ts +138 -0
- package/dist/voice-eval.js +309 -0
- package/dist/watch.d.ts +58 -0
- package/dist/watch.js +97 -0
- package/package.json +89 -64
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Item 18 — `crewhaus tools` namespace: builtin discovery + usage audit.
|
|
3
|
+
* The pure, side-effect-free half of the tool advisor. Three sub-commands:
|
|
4
|
+
*
|
|
5
|
+
* list — every builtin's name/description/scope/ioCapability/
|
|
6
|
+
* readOnly/destructive, from the RegisteredTool metadata
|
|
7
|
+
* the runtime already carries.
|
|
8
|
+
* suggest <spec> — rank builtins against `agent.instructions` by a
|
|
9
|
+
* deterministic keyword match (no model — the tool
|
|
10
|
+
* implication is the same shape scaffold-evals uses).
|
|
11
|
+
* audit — mine `tool_stats` + `tool_use` events across sessions
|
|
12
|
+
* to propose (a) removing tools never called, (b) flagging
|
|
13
|
+
* chronically failing tools, (c) learned read-only
|
|
14
|
+
* candidates (many clean calls). ADVICE-ONLY: `tools:` is
|
|
15
|
+
* not in OPTIMIZABLE_PATHS, so every edit is a human-review
|
|
16
|
+
* suggestion, never an eval-gated auto-apply.
|
|
17
|
+
*
|
|
18
|
+
* Everything here is pure so it is unit-testable; all filesystem access
|
|
19
|
+
* (loadToolMap, session reads, spec reads) lives in `apps/cli/src/index.ts`,
|
|
20
|
+
* mirroring `advise-rules.ts`.
|
|
21
|
+
*
|
|
22
|
+
* Casing bridge (load-bearing): a spec's `tools:` list uses the camelCase
|
|
23
|
+
* BUILTIN_TOOL_MAP keys (`read`, `webFetch`), but a session log's
|
|
24
|
+
* `tool_stats.toolName` / `tool_use.name` carry the RegisteredTool's
|
|
25
|
+
* PascalCase `.name` (`Read`, `WebFetch`). The audit correlates the two
|
|
26
|
+
* through the tool map (spec key → `.name`), so a tool named in the spec is
|
|
27
|
+
* matched to its runtime call stats regardless of the two casings.
|
|
28
|
+
*/
|
|
29
|
+
import type { RegisteredTool } from "@crewhaus/tool-catalog";
|
|
30
|
+
import type { SessionEvents } from "./advise-rules";
|
|
31
|
+
/** One row of `tools list`, projected from a RegisteredTool. */
|
|
32
|
+
export type ToolListRow = {
|
|
33
|
+
/** The camelCase spec/map key (what a user writes in `tools:`). */
|
|
34
|
+
readonly key: string;
|
|
35
|
+
/** The RegisteredTool's PascalCase `.name` (what session logs record). */
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly description: string;
|
|
38
|
+
readonly readOnly: boolean;
|
|
39
|
+
readonly destructive: boolean;
|
|
40
|
+
readonly scope: string;
|
|
41
|
+
readonly ioCapability?: string;
|
|
42
|
+
readonly requiresSandbox: boolean;
|
|
43
|
+
};
|
|
44
|
+
/** Project a tool map (key → RegisteredTool) into sorted list rows. */
|
|
45
|
+
export declare function buildToolList(toolMap: Readonly<Record<string, RegisteredTool>>): ToolListRow[];
|
|
46
|
+
/** One line per tool for the CLI's text mode. */
|
|
47
|
+
export declare function formatToolListLines(rows: ReadonlyArray<ToolListRow>): string[];
|
|
48
|
+
/**
|
|
49
|
+
* Deterministic keyword → tool implication. Each tool key maps to the
|
|
50
|
+
* lowercased keywords whose presence in an agent's instructions implies the
|
|
51
|
+
* tool is likely wanted. Kept intentionally small and obvious (no model, no
|
|
52
|
+
* fuzzy match) — the same posture scaffold-evals uses when it derives
|
|
53
|
+
* `expected_tools` from a task. Unknown/custom tools never appear here (only
|
|
54
|
+
* builtins are suggestible); a builtin with no keywords is never suggested.
|
|
55
|
+
*/
|
|
56
|
+
export declare const TOOL_KEYWORDS: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
57
|
+
export type ToolSuggestion = {
|
|
58
|
+
readonly key: string;
|
|
59
|
+
readonly name: string;
|
|
60
|
+
/** The instruction keywords that implied this tool. */
|
|
61
|
+
readonly matchedKeywords: ReadonlyArray<string>;
|
|
62
|
+
/** True when the spec already lists this tool (already-satisfied). */
|
|
63
|
+
readonly alreadyPresent: boolean;
|
|
64
|
+
};
|
|
65
|
+
export type ToolSuggestResult = {
|
|
66
|
+
/** Implied-but-missing tools, ranked by keyword-hit count then key. */
|
|
67
|
+
readonly missing: ReadonlyArray<ToolSuggestion>;
|
|
68
|
+
/** Implied tools already in the spec (reported for confidence). */
|
|
69
|
+
readonly present: ReadonlyArray<ToolSuggestion>;
|
|
70
|
+
/** Tools in the spec that no keyword implied (candidate over-grants). */
|
|
71
|
+
readonly unimplied: ReadonlyArray<string>;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Rank builtins against an agent's instructions. `specTools` is the spec's
|
|
75
|
+
* current `tools:` list (camelCase keys, possibly empty); `toolKeys` is the
|
|
76
|
+
* set of resolvable builtin keys (so a suggestion never names a tool the
|
|
77
|
+
* runtime can't load). Pure and deterministic.
|
|
78
|
+
*/
|
|
79
|
+
export declare function suggestTools(instructions: string, specTools: ReadonlyArray<string>, toolKeys: ReadonlyArray<string>, toolMap: Readonly<Record<string, RegisteredTool>>): ToolSuggestResult;
|
|
80
|
+
export declare function formatSuggestLines(result: ToolSuggestResult): string[];
|
|
81
|
+
/** Per-tool aggregate over a session set's `tool_stats` lines, keyed by the
|
|
82
|
+
* PascalCase runtime `.name`. */
|
|
83
|
+
export type ToolUsageStats = {
|
|
84
|
+
calls: number;
|
|
85
|
+
errors: number;
|
|
86
|
+
totalDurationMs: number;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Fold session events into per-tool usage stats. Reads `tool_stats` lines
|
|
90
|
+
* (the durable per-call mirror); tolerant of old-vintage logs (no such
|
|
91
|
+
* lines → empty map). Keyed by `toolName` exactly as recorded (PascalCase).
|
|
92
|
+
*/
|
|
93
|
+
export declare function buildToolUsage(sessions: ReadonlyArray<SessionEvents>): ReadonlyMap<string, ToolUsageStats>;
|
|
94
|
+
export type AuditThresholds = {
|
|
95
|
+
/** Tools with ≥ this many calls whose error rate crosses `failRate` are flagged. */
|
|
96
|
+
readonly failMinCalls: number;
|
|
97
|
+
readonly failRate: number;
|
|
98
|
+
/** Clean (error-free) calls at/above which a non-readOnly tool is a
|
|
99
|
+
* learned-readOnly candidate. */
|
|
100
|
+
readonly readOnlyMinCleanCalls: number;
|
|
101
|
+
};
|
|
102
|
+
export declare const DEFAULT_AUDIT_THRESHOLDS: AuditThresholds;
|
|
103
|
+
export type ToolAuditFinding =
|
|
104
|
+
/** A tool granted in the spec but never called across the mined sessions. */
|
|
105
|
+
{
|
|
106
|
+
readonly kind: "unused";
|
|
107
|
+
readonly key: string;
|
|
108
|
+
readonly name: string;
|
|
109
|
+
}
|
|
110
|
+
/** A granted tool whose error rate crosses the threshold. */
|
|
111
|
+
| {
|
|
112
|
+
readonly kind: "failing";
|
|
113
|
+
readonly key: string;
|
|
114
|
+
readonly name: string;
|
|
115
|
+
readonly calls: number;
|
|
116
|
+
readonly errors: number;
|
|
117
|
+
readonly rate: number;
|
|
118
|
+
}
|
|
119
|
+
/** A non-readOnly tool with many clean calls — candidate readOnly reclassify. */
|
|
120
|
+
| {
|
|
121
|
+
readonly kind: "learned-read-only";
|
|
122
|
+
readonly key: string;
|
|
123
|
+
readonly name: string;
|
|
124
|
+
readonly calls: number;
|
|
125
|
+
};
|
|
126
|
+
export type ToolAuditResult = {
|
|
127
|
+
readonly sessionIds: ReadonlyArray<string>;
|
|
128
|
+
readonly findings: ReadonlyArray<ToolAuditFinding>;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Audit a spec's granted tools against observed usage. Pure.
|
|
132
|
+
*
|
|
133
|
+
* - `specTools`: the spec's `tools:` list (camelCase keys). When the spec
|
|
134
|
+
* grants NO explicit list (empty), the "unused" check is skipped — an
|
|
135
|
+
* agent with the default toolset shouldn't be told to remove tools it
|
|
136
|
+
* never declared.
|
|
137
|
+
* - `usage`: `buildToolUsage` output (PascalCase-keyed).
|
|
138
|
+
* - `toolMap`: resolves a spec key → RegisteredTool (for `.name` +
|
|
139
|
+
* `readOnly`), bridging the two casings.
|
|
140
|
+
*
|
|
141
|
+
* Findings are advice-only (`tools:` is not optimizer-whitelisted).
|
|
142
|
+
*/
|
|
143
|
+
export declare function auditTools(opts: {
|
|
144
|
+
readonly sessions: ReadonlyArray<SessionEvents>;
|
|
145
|
+
readonly specTools: ReadonlyArray<string>;
|
|
146
|
+
readonly usage: ReadonlyMap<string, ToolUsageStats>;
|
|
147
|
+
readonly toolMap: Readonly<Record<string, RegisteredTool>>;
|
|
148
|
+
readonly hasExplicitToolList: boolean;
|
|
149
|
+
readonly thresholds?: Partial<AuditThresholds>;
|
|
150
|
+
}): ToolAuditResult;
|
|
151
|
+
export declare function formatAuditLines(result: ToolAuditResult): string[];
|
|
152
|
+
/**
|
|
153
|
+
* The canonical set of built-in tool KEYS the CLI runtime can resolve at
|
|
154
|
+
* `crewhaus run` time — the exact key set of `loadToolMap()` in
|
|
155
|
+
* `apps/cli/src/index.ts`. It MUST equal `BUILTIN_TOOL_MAP`'s keys in
|
|
156
|
+
* `packages/target-cli/src/index.ts`: that map decides which `tools:` names
|
|
157
|
+
* COMPILE, this one decides which RUN, and a name in one but not the other is
|
|
158
|
+
* a latent break (compiles then crashes, or runs a name the emitter rejects).
|
|
159
|
+
* The sync test in `tools-cli.test.ts` asserts the two are equal; `loadToolMap`
|
|
160
|
+
* is built to cover exactly these keys.
|
|
161
|
+
*/
|
|
162
|
+
export declare const CLI_RUNTIME_TOOL_KEYS: ReadonlyArray<string>;
|
|
163
|
+
/**
|
|
164
|
+
* Compare two tool-name key sets. Returns the symmetric difference so the
|
|
165
|
+
* sync test can report exactly which names drifted. Empty arrays ⇒ in sync.
|
|
166
|
+
*/
|
|
167
|
+
export declare function diffToolMapKeys(a: ReadonlyArray<string>, b: ReadonlyArray<string>): {
|
|
168
|
+
readonly onlyInA: ReadonlyArray<string>;
|
|
169
|
+
readonly onlyInB: ReadonlyArray<string>;
|
|
170
|
+
};
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { payloadOf } from "./advise-rules";
|
|
2
|
+
/** Project a tool map (key → RegisteredTool) into sorted list rows. */
|
|
3
|
+
export function buildToolList(toolMap) {
|
|
4
|
+
return Object.entries(toolMap)
|
|
5
|
+
.map(([key, t]) => ({
|
|
6
|
+
key,
|
|
7
|
+
name: t.name,
|
|
8
|
+
description: t.description,
|
|
9
|
+
readOnly: t.readOnly,
|
|
10
|
+
destructive: t.destructive,
|
|
11
|
+
scope: t.scope,
|
|
12
|
+
...(t.ioCapability !== undefined ? { ioCapability: t.ioCapability } : {}),
|
|
13
|
+
requiresSandbox: t.requiresSandbox,
|
|
14
|
+
}))
|
|
15
|
+
.sort((a, b) => a.key.localeCompare(b.key));
|
|
16
|
+
}
|
|
17
|
+
/** One line per tool for the CLI's text mode. */
|
|
18
|
+
export function formatToolListLines(rows) {
|
|
19
|
+
const lines = [];
|
|
20
|
+
for (const r of rows) {
|
|
21
|
+
const flags = [
|
|
22
|
+
r.readOnly ? "read-only" : undefined,
|
|
23
|
+
r.destructive ? "destructive" : undefined,
|
|
24
|
+
r.scope === "external" ? "external" : undefined,
|
|
25
|
+
r.ioCapability !== undefined ? `io:${r.ioCapability}` : undefined,
|
|
26
|
+
r.requiresSandbox ? "sandbox" : undefined,
|
|
27
|
+
].filter((f) => f !== undefined);
|
|
28
|
+
const tags = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
|
|
29
|
+
lines.push(`${r.key} (${r.name})${tags}`);
|
|
30
|
+
lines.push(` ${r.description}`);
|
|
31
|
+
}
|
|
32
|
+
return lines;
|
|
33
|
+
}
|
|
34
|
+
// -------- tools suggest --------
|
|
35
|
+
/**
|
|
36
|
+
* Deterministic keyword → tool implication. Each tool key maps to the
|
|
37
|
+
* lowercased keywords whose presence in an agent's instructions implies the
|
|
38
|
+
* tool is likely wanted. Kept intentionally small and obvious (no model, no
|
|
39
|
+
* fuzzy match) — the same posture scaffold-evals uses when it derives
|
|
40
|
+
* `expected_tools` from a task. Unknown/custom tools never appear here (only
|
|
41
|
+
* builtins are suggestible); a builtin with no keywords is never suggested.
|
|
42
|
+
*/
|
|
43
|
+
export const TOOL_KEYWORDS = Object.freeze({
|
|
44
|
+
read: ["read", "open file", "file contents", "inspect file", "cat "],
|
|
45
|
+
write: ["write file", "create file", "save to", "write to disk", "generate a file"],
|
|
46
|
+
edit: ["edit", "modify file", "patch", "replace in", "change the file"],
|
|
47
|
+
glob: ["glob", "find files", "list files", "match files", "file pattern"],
|
|
48
|
+
grep: ["grep", "search for", "search the", "find in files", "look for", "search code"],
|
|
49
|
+
bash: ["bash", "shell", "run command", "execute command", "terminal", "run a script"],
|
|
50
|
+
todoWrite: ["todo", "task list", "track tasks", "checklist"],
|
|
51
|
+
webFetch: ["fetch url", "fetch a url", "webpage", "web page", "http get", "download page"],
|
|
52
|
+
webSearch: ["web search", "search the web", "search online", "look up online", "google"],
|
|
53
|
+
readImage: ["image", "screenshot", "read an image", "vision", "picture"],
|
|
54
|
+
fetch: ["fetch", "api call", "rest api", "http request", "call an endpoint"],
|
|
55
|
+
python: ["python", "run python", "data analysis", "numpy", "pandas"],
|
|
56
|
+
javascript: ["javascript", "run javascript", "node script", "eval js"],
|
|
57
|
+
shell: ["shell command", "sh -c", "posix shell"],
|
|
58
|
+
imageGenerate: ["generate image", "create image", "dall-e", "image generation", "draw"],
|
|
59
|
+
ingestDocument: ["ingest", "parse document", "read document", "pdf", "docx", "extract text"],
|
|
60
|
+
codegraphSearch: ["codegraph", "code search", "symbol search", "find symbol"],
|
|
61
|
+
codegraphCallers: ["callers of", "who calls", "find callers"],
|
|
62
|
+
codegraphCallees: ["callees", "what does it call", "find callees"],
|
|
63
|
+
codegraphImpact: ["impact analysis", "blast radius", "affected by"],
|
|
64
|
+
});
|
|
65
|
+
/**
|
|
66
|
+
* Rank builtins against an agent's instructions. `specTools` is the spec's
|
|
67
|
+
* current `tools:` list (camelCase keys, possibly empty); `toolKeys` is the
|
|
68
|
+
* set of resolvable builtin keys (so a suggestion never names a tool the
|
|
69
|
+
* runtime can't load). Pure and deterministic.
|
|
70
|
+
*/
|
|
71
|
+
export function suggestTools(instructions, specTools, toolKeys, toolMap) {
|
|
72
|
+
const haystack = instructions.toLowerCase();
|
|
73
|
+
const present = new Set(specTools);
|
|
74
|
+
const known = new Set(toolKeys);
|
|
75
|
+
const missing = [];
|
|
76
|
+
const alreadyPresent = [];
|
|
77
|
+
for (const [key, keywords] of Object.entries(TOOL_KEYWORDS)) {
|
|
78
|
+
if (!known.has(key))
|
|
79
|
+
continue; // never suggest an unresolvable tool
|
|
80
|
+
const matched = keywords.filter((kw) => haystack.includes(kw));
|
|
81
|
+
if (matched.length === 0)
|
|
82
|
+
continue;
|
|
83
|
+
const suggestion = {
|
|
84
|
+
key,
|
|
85
|
+
name: toolMap[key]?.name ?? key,
|
|
86
|
+
matchedKeywords: matched,
|
|
87
|
+
alreadyPresent: present.has(key),
|
|
88
|
+
};
|
|
89
|
+
(suggestion.alreadyPresent ? alreadyPresent : missing).push(suggestion);
|
|
90
|
+
}
|
|
91
|
+
const rank = (a, b) => {
|
|
92
|
+
const diff = b.matchedKeywords.length - a.matchedKeywords.length;
|
|
93
|
+
return diff !== 0 ? diff : a.key.localeCompare(b.key);
|
|
94
|
+
};
|
|
95
|
+
missing.sort(rank);
|
|
96
|
+
alreadyPresent.sort(rank);
|
|
97
|
+
// Spec tools no keyword implied — possible over-grants worth reviewing.
|
|
98
|
+
const impliedKeys = new Set([...missing, ...alreadyPresent].map((s) => s.key));
|
|
99
|
+
const unimplied = specTools.filter((t) => !impliedKeys.has(t)).sort();
|
|
100
|
+
return { missing, present: alreadyPresent, unimplied };
|
|
101
|
+
}
|
|
102
|
+
export function formatSuggestLines(result) {
|
|
103
|
+
const lines = [];
|
|
104
|
+
if (result.missing.length === 0) {
|
|
105
|
+
lines.push("no additional builtins implied by the instructions");
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
lines.push("implied but not in tools:");
|
|
109
|
+
for (const s of result.missing) {
|
|
110
|
+
lines.push(` + ${s.key} (${s.name}) — matched: ${s.matchedKeywords.join(", ")}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (result.present.length > 0) {
|
|
114
|
+
lines.push("implied and already present:");
|
|
115
|
+
for (const s of result.present) {
|
|
116
|
+
lines.push(` · ${s.key} — matched: ${s.matchedKeywords.join(", ")}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (result.unimplied.length > 0) {
|
|
120
|
+
lines.push(`in tools: but not implied by instructions (review — possible over-grant): ${result.unimplied.join(", ")}`);
|
|
121
|
+
}
|
|
122
|
+
return lines;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Fold session events into per-tool usage stats. Reads `tool_stats` lines
|
|
126
|
+
* (the durable per-call mirror); tolerant of old-vintage logs (no such
|
|
127
|
+
* lines → empty map). Keyed by `toolName` exactly as recorded (PascalCase).
|
|
128
|
+
*/
|
|
129
|
+
export function buildToolUsage(sessions) {
|
|
130
|
+
const usage = new Map();
|
|
131
|
+
for (const session of sessions) {
|
|
132
|
+
for (const obj of session.objects) {
|
|
133
|
+
const stats = payloadOf(obj, "tool_stats");
|
|
134
|
+
if (stats === undefined || typeof stats["toolName"] !== "string")
|
|
135
|
+
continue;
|
|
136
|
+
const s = usage.get(stats["toolName"]) ?? { calls: 0, errors: 0, totalDurationMs: 0 };
|
|
137
|
+
s.calls += 1;
|
|
138
|
+
if (stats["isError"] === true)
|
|
139
|
+
s.errors += 1;
|
|
140
|
+
if (typeof stats["durationMs"] === "number")
|
|
141
|
+
s.totalDurationMs += stats["durationMs"];
|
|
142
|
+
usage.set(stats["toolName"], s);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return usage;
|
|
146
|
+
}
|
|
147
|
+
export const DEFAULT_AUDIT_THRESHOLDS = Object.freeze({
|
|
148
|
+
failMinCalls: 5,
|
|
149
|
+
failRate: 0.5,
|
|
150
|
+
readOnlyMinCleanCalls: 10,
|
|
151
|
+
});
|
|
152
|
+
/**
|
|
153
|
+
* Audit a spec's granted tools against observed usage. Pure.
|
|
154
|
+
*
|
|
155
|
+
* - `specTools`: the spec's `tools:` list (camelCase keys). When the spec
|
|
156
|
+
* grants NO explicit list (empty), the "unused" check is skipped — an
|
|
157
|
+
* agent with the default toolset shouldn't be told to remove tools it
|
|
158
|
+
* never declared.
|
|
159
|
+
* - `usage`: `buildToolUsage` output (PascalCase-keyed).
|
|
160
|
+
* - `toolMap`: resolves a spec key → RegisteredTool (for `.name` +
|
|
161
|
+
* `readOnly`), bridging the two casings.
|
|
162
|
+
*
|
|
163
|
+
* Findings are advice-only (`tools:` is not optimizer-whitelisted).
|
|
164
|
+
*/
|
|
165
|
+
export function auditTools(opts) {
|
|
166
|
+
const t = { ...DEFAULT_AUDIT_THRESHOLDS, ...(opts.thresholds ?? {}) };
|
|
167
|
+
const findings = [];
|
|
168
|
+
// Index usage by the RegisteredTool `.name` (already the usage key) and
|
|
169
|
+
// build a spec-key → name resolver.
|
|
170
|
+
const nameFor = (key) => opts.toolMap[key]?.name ?? key;
|
|
171
|
+
// (a) unused grants — only when the spec declared an explicit list.
|
|
172
|
+
if (opts.hasExplicitToolList) {
|
|
173
|
+
for (const key of opts.specTools) {
|
|
174
|
+
const name = nameFor(key);
|
|
175
|
+
const stats = opts.usage.get(name);
|
|
176
|
+
if (stats === undefined || stats.calls === 0) {
|
|
177
|
+
findings.push({ kind: "unused", key, name });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// (b) chronically failing tools — over ALL observed tools, mapped back to a
|
|
182
|
+
// spec key when we can (falls back to the runtime name otherwise).
|
|
183
|
+
const nameToKey = new Map();
|
|
184
|
+
for (const [key, tool] of Object.entries(opts.toolMap))
|
|
185
|
+
nameToKey.set(tool.name, key);
|
|
186
|
+
for (const [name, stats] of opts.usage) {
|
|
187
|
+
if (stats.calls < t.failMinCalls)
|
|
188
|
+
continue;
|
|
189
|
+
const rate = stats.errors / stats.calls;
|
|
190
|
+
if (rate < t.failRate)
|
|
191
|
+
continue;
|
|
192
|
+
findings.push({
|
|
193
|
+
kind: "failing",
|
|
194
|
+
key: nameToKey.get(name) ?? name,
|
|
195
|
+
name,
|
|
196
|
+
calls: stats.calls,
|
|
197
|
+
errors: stats.errors,
|
|
198
|
+
rate,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// (c) learned read-only candidates — a tool NOT declared readOnly that has
|
|
202
|
+
// many error-free calls. Signals it may be safe to reclassify (which
|
|
203
|
+
// would let the permission engine auto-allow it in auto mode).
|
|
204
|
+
for (const [name, stats] of opts.usage) {
|
|
205
|
+
if (stats.errors > 0)
|
|
206
|
+
continue;
|
|
207
|
+
if (stats.calls < t.readOnlyMinCleanCalls)
|
|
208
|
+
continue;
|
|
209
|
+
const key = nameToKey.get(name);
|
|
210
|
+
if (key === undefined)
|
|
211
|
+
continue; // an unmapped runtime name — can't propose a spec-key edit
|
|
212
|
+
const tool = opts.toolMap[key];
|
|
213
|
+
if (tool === undefined || tool.readOnly)
|
|
214
|
+
continue; // already read-only → nothing to learn
|
|
215
|
+
findings.push({ kind: "learned-read-only", key, name, calls: stats.calls });
|
|
216
|
+
}
|
|
217
|
+
// Deterministic order: unused, then failing (worst rate first), then
|
|
218
|
+
// learned-read-only, each alphabetized within its group.
|
|
219
|
+
const groupRank = (f) => f.kind === "unused" ? 0 : f.kind === "failing" ? 1 : 2;
|
|
220
|
+
findings.sort((a, b) => {
|
|
221
|
+
const g = groupRank(a) - groupRank(b);
|
|
222
|
+
if (g !== 0)
|
|
223
|
+
return g;
|
|
224
|
+
if (a.kind === "failing" && b.kind === "failing") {
|
|
225
|
+
const d = b.rate - a.rate;
|
|
226
|
+
if (d !== 0)
|
|
227
|
+
return d;
|
|
228
|
+
}
|
|
229
|
+
return a.key.localeCompare(b.key);
|
|
230
|
+
});
|
|
231
|
+
return { sessionIds: opts.sessions.map((s) => s.sessionId), findings };
|
|
232
|
+
}
|
|
233
|
+
export function formatAuditLines(result) {
|
|
234
|
+
const lines = [];
|
|
235
|
+
if (result.findings.length === 0) {
|
|
236
|
+
lines.push("no tool-usage findings — grants look well-matched to observed calls");
|
|
237
|
+
return lines;
|
|
238
|
+
}
|
|
239
|
+
for (const f of result.findings) {
|
|
240
|
+
switch (f.kind) {
|
|
241
|
+
case "unused":
|
|
242
|
+
lines.push(`[remove?] ${f.key} (${f.name}) — granted but never called in the mined sessions; drop it from tools: unless it's for a path these sessions didn't exercise`);
|
|
243
|
+
break;
|
|
244
|
+
case "failing":
|
|
245
|
+
lines.push(`[failing] ${f.key} (${f.name}) — ${f.errors}/${f.calls} calls errored (${(f.rate * 100).toFixed(0)}%); investigate inputs/backend or swap the tool`);
|
|
246
|
+
break;
|
|
247
|
+
case "learned-read-only":
|
|
248
|
+
lines.push(`[read-only?] ${f.key} (${f.name}) — ${f.calls} clean calls, 0 errors; if it never mutates, mark it readOnly so auto mode can auto-allow it`);
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return lines;
|
|
253
|
+
}
|
|
254
|
+
// -------- map-sync guard --------
|
|
255
|
+
/**
|
|
256
|
+
* The canonical set of built-in tool KEYS the CLI runtime can resolve at
|
|
257
|
+
* `crewhaus run` time — the exact key set of `loadToolMap()` in
|
|
258
|
+
* `apps/cli/src/index.ts`. It MUST equal `BUILTIN_TOOL_MAP`'s keys in
|
|
259
|
+
* `packages/target-cli/src/index.ts`: that map decides which `tools:` names
|
|
260
|
+
* COMPILE, this one decides which RUN, and a name in one but not the other is
|
|
261
|
+
* a latent break (compiles then crashes, or runs a name the emitter rejects).
|
|
262
|
+
* The sync test in `tools-cli.test.ts` asserts the two are equal; `loadToolMap`
|
|
263
|
+
* is built to cover exactly these keys.
|
|
264
|
+
*/
|
|
265
|
+
export const CLI_RUNTIME_TOOL_KEYS = Object.freeze([
|
|
266
|
+
"read",
|
|
267
|
+
"write",
|
|
268
|
+
"edit",
|
|
269
|
+
"glob",
|
|
270
|
+
"grep",
|
|
271
|
+
"bash",
|
|
272
|
+
"todoWrite",
|
|
273
|
+
"webFetch",
|
|
274
|
+
"webSearch",
|
|
275
|
+
"readImage",
|
|
276
|
+
"fetch",
|
|
277
|
+
"python",
|
|
278
|
+
"javascript",
|
|
279
|
+
"shell",
|
|
280
|
+
"imageGenerate",
|
|
281
|
+
"ingestDocument",
|
|
282
|
+
"codegraphSearch",
|
|
283
|
+
"codegraphCallers",
|
|
284
|
+
"codegraphCallees",
|
|
285
|
+
"codegraphImpact",
|
|
286
|
+
]);
|
|
287
|
+
/**
|
|
288
|
+
* Compare two tool-name key sets. Returns the symmetric difference so the
|
|
289
|
+
* sync test can report exactly which names drifted. Empty arrays ⇒ in sync.
|
|
290
|
+
*/
|
|
291
|
+
export function diffToolMapKeys(a, b) {
|
|
292
|
+
const setA = new Set(a);
|
|
293
|
+
const setB = new Set(b);
|
|
294
|
+
return {
|
|
295
|
+
onlyInA: [...setA].filter((k) => !setB.has(k)).sort(),
|
|
296
|
+
onlyInB: [...setB].filter((k) => !setA.has(k)).sort(),
|
|
297
|
+
};
|
|
298
|
+
}
|
package/dist/triage.d.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { DatasetRegistry } from "@crewhaus/dataset-registry";
|
|
2
|
+
import type { Sample } from "@crewhaus/eval-dataset";
|
|
3
|
+
import { type ArbiterAction, type FailingSample, type FailureClass } from "@crewhaus/eval-optimizer-orchestrator";
|
|
4
|
+
import type { SampleResult } from "@crewhaus/eval-runner";
|
|
5
|
+
import { type PinRegressionsResult } from "./regression-pin";
|
|
6
|
+
/** Fixed class order for every printed/rendered counts line. */
|
|
7
|
+
export declare const FAILURE_CLASS_ORDER: ReadonlyArray<FailureClass>;
|
|
8
|
+
/** One failing sample's arbiter verdict, joined back to its sample id. */
|
|
9
|
+
export type SampleVerdict = {
|
|
10
|
+
readonly sampleId: string;
|
|
11
|
+
readonly class: FailureClass;
|
|
12
|
+
readonly action: ArbiterAction;
|
|
13
|
+
readonly reason: string;
|
|
14
|
+
};
|
|
15
|
+
/** The persisted shape of `<runDir>/verdicts.json`. */
|
|
16
|
+
export type RunVerdicts = {
|
|
17
|
+
readonly source: "failure-arbiter";
|
|
18
|
+
readonly runId: string;
|
|
19
|
+
readonly arbitratedAt: string;
|
|
20
|
+
/** Number of failing samples arbitrated. */
|
|
21
|
+
readonly total: number;
|
|
22
|
+
readonly counts: Readonly<Record<FailureClass, number>>;
|
|
23
|
+
readonly dominantClass: FailureClass;
|
|
24
|
+
readonly recommendedAction: ArbiterAction;
|
|
25
|
+
readonly verdicts: ReadonlyArray<SampleVerdict>;
|
|
26
|
+
};
|
|
27
|
+
/** A failing result: errored, or graded and not passed (errors also grade
|
|
28
|
+
* `passed: false`, but the explicit disjunction keeps the intent legible). */
|
|
29
|
+
export declare function isFailing(result: SampleResult): boolean;
|
|
30
|
+
/** True when the failure is an ERROR — the invoker failed or a grader
|
|
31
|
+
* threw — rather than an honest graded verdict on the agent's output. */
|
|
32
|
+
export declare function isErroredResult(result: SampleResult): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Join one failing `SampleResult` back to its dataset `Sample` and shape
|
|
35
|
+
* the pair into the arbiter's `FailingSample` input. See the module doc
|
|
36
|
+
* for the `expected_output` → `reference` mapping and the (deliberately)
|
|
37
|
+
* absent `graderOutput`. A grader-infra failure (`graderError`, e.g. a
|
|
38
|
+
* judge 429 on both attempts) surfaces as `errorMessage` too, so the
|
|
39
|
+
* arbiter's transient-marker rule can classify it as noise. A missing
|
|
40
|
+
* dataset sample (should not happen — the CLI tees every sample the runner
|
|
41
|
+
* sees) degrades to a minimal stand-in with no reference rather than
|
|
42
|
+
* skipping the sample.
|
|
43
|
+
*/
|
|
44
|
+
export declare function toFailingSample(result: SampleResult, sample: Sample | undefined): FailingSample;
|
|
45
|
+
/**
|
|
46
|
+
* Classify every failing sample of a completed eval run. Returns undefined
|
|
47
|
+
* when nothing failed — there is nothing to arbitrate, and persisting
|
|
48
|
+
* `aggregate([])`'s degenerate roll-up (dominant contract-ambiguity over
|
|
49
|
+
* zero samples) would mislead.
|
|
50
|
+
*/
|
|
51
|
+
export declare function triageEvalRun(opts: {
|
|
52
|
+
readonly samples: ReadonlyArray<SampleResult>;
|
|
53
|
+
readonly samplesById: ReadonlyMap<string, Sample>;
|
|
54
|
+
readonly runId: string;
|
|
55
|
+
readonly now?: () => Date;
|
|
56
|
+
}): RunVerdicts | undefined;
|
|
57
|
+
/** `triage: 2 bug, 1 spec-gap, 3 noise, 1 contract-ambiguity` — all four
|
|
58
|
+
* classes always print (stable shape for log scrapers), in fixed order. */
|
|
59
|
+
export declare function formatTriageSummary(counts: Readonly<Record<FailureClass, number>>): string;
|
|
60
|
+
/** Persist verdicts as `<outDir>/verdicts.json`; returns the path. */
|
|
61
|
+
export declare function writeRunVerdicts(outDir: string, verdicts: RunVerdicts): string;
|
|
62
|
+
export type EvalTriageOptions = {
|
|
63
|
+
readonly samples: ReadonlyArray<SampleResult>;
|
|
64
|
+
/** The dataset samples the runner actually saw (see {@link tapSamples}). */
|
|
65
|
+
readonly samplesById: ReadonlyMap<string, Sample>;
|
|
66
|
+
readonly runId: string;
|
|
67
|
+
/** Run directory that already holds results.json; verdicts.json lands here. */
|
|
68
|
+
readonly outDir: string;
|
|
69
|
+
readonly specName: string;
|
|
70
|
+
/** Dataset name for pin provenance (post-union name when a suite was unioned). */
|
|
71
|
+
readonly sourceDataset: string;
|
|
72
|
+
/** Registry for the promoteRegression pin; omit to skip pinning entirely. */
|
|
73
|
+
readonly registry?: DatasetRegistry;
|
|
74
|
+
/** `!--no-regressions` — false skips the promoteRegression pin (the user
|
|
75
|
+
* opted out of regression-suite integration); triage itself still runs. */
|
|
76
|
+
readonly pin?: boolean;
|
|
77
|
+
/** Line sink; defaults to stdout. */
|
|
78
|
+
readonly write?: (line: string) => void;
|
|
79
|
+
/** Warning sink; defaults to stderr. */
|
|
80
|
+
readonly warn?: (line: string) => void;
|
|
81
|
+
/** Clock override for deterministic tests. */
|
|
82
|
+
readonly now?: () => Date;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* The post-eval triage flow: arbitrate → persist verdicts.json → print the
|
|
86
|
+
* one-line summary → pin the promoteRegression (bug-class) samples. Never
|
|
87
|
+
* throws — each stage is isolated so e.g. an unwritable run dir still
|
|
88
|
+
* leaves the verdicts available for the HTML report, and a broken registry
|
|
89
|
+
* only skips the pin. Returns the verdicts (for the report renderer), or
|
|
90
|
+
* undefined when nothing failed or classification itself broke.
|
|
91
|
+
*/
|
|
92
|
+
export declare function finishEvalTriage(opts: EvalTriageOptions): Promise<RunVerdicts | undefined>;
|
|
93
|
+
/**
|
|
94
|
+
* Error-rate threshold past which a run is presumed infrastructure-failed
|
|
95
|
+
* (credentials revoked mid-run, provider outage) even when some samples
|
|
96
|
+
* still failed on grades — a mass of "bug" verdicts from an outage must not
|
|
97
|
+
* feed the regression pin.
|
|
98
|
+
*/
|
|
99
|
+
export declare const INFRA_FAILURE_ERROR_RATE = 0.5;
|
|
100
|
+
/**
|
|
101
|
+
* Run-level outage guard, mirroring eval-matrix's `cellCrashReason`: when
|
|
102
|
+
* every FAILING sample is an error (invoker or grader), or the run's error
|
|
103
|
+
* rate exceeds {@link INFRA_FAILURE_ERROR_RATE}, the run looks
|
|
104
|
+
* infrastructure-failed and triage must not pin anything from it. Returns
|
|
105
|
+
* the human-readable reason, or undefined when the run looks real.
|
|
106
|
+
*/
|
|
107
|
+
export declare function runLooksInfrastructureFailed(samples: ReadonlyArray<SampleResult>): string | undefined;
|
|
108
|
+
/** True when an arbiter action asks for regression promotion. */
|
|
109
|
+
export declare function actionPromotesRegression(action: ArbiterAction): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Pin every sample whose verdict action carries `promoteRegression: true`
|
|
112
|
+
* into the per-spec regression suite, tagging the pin's provenance with
|
|
113
|
+
* `source: "failure-arbiter"` (see regression-pin's metadata.regression_pin).
|
|
114
|
+
* Samples the dataset tee somehow missed are skipped — a pin must append
|
|
115
|
+
* the sample as it lives in the source dataset, or not at all.
|
|
116
|
+
*
|
|
117
|
+
* Two candidate filters (see the module doc):
|
|
118
|
+
* - `datasetMemberIds` — samples the run's own dataset already contains are
|
|
119
|
+
* never pinned: zero coverage gain, and the resulting suite-version churn
|
|
120
|
+
* is what armed the fail→pin→retry gate-disarm loop.
|
|
121
|
+
* - `erroredIds` — samples whose failure is an ERROR are never pinned: an
|
|
122
|
+
* outage (e.g. "401 invalid x-api-key", which the arbiter's default rule
|
|
123
|
+
* labels "bug") is not a regression to guard.
|
|
124
|
+
*/
|
|
125
|
+
export declare function promoteArbiterSamples(opts: {
|
|
126
|
+
readonly registry: DatasetRegistry;
|
|
127
|
+
readonly specName: string;
|
|
128
|
+
readonly verdicts: RunVerdicts;
|
|
129
|
+
readonly samplesById: ReadonlyMap<string, Sample>;
|
|
130
|
+
readonly sourceDataset: string;
|
|
131
|
+
readonly runId: string;
|
|
132
|
+
/** Ids already in the run's dataset — never pinned (zero coverage gain). */
|
|
133
|
+
readonly datasetMemberIds?: ReadonlySet<string>;
|
|
134
|
+
/** Ids whose failure is an ERROR (invoker/grader) — never pinned. */
|
|
135
|
+
readonly erroredIds?: ReadonlySet<string>;
|
|
136
|
+
/** Warning sink, forwarded to `pinRecoveredSamples`; defaults to stderr. */
|
|
137
|
+
readonly warn?: (line: string) => void;
|
|
138
|
+
readonly now?: () => Date;
|
|
139
|
+
}): Promise<PinRegressionsResult>;
|
|
140
|
+
/** The exclusion policy for the mutator's failure signal: noise (flaky
|
|
141
|
+
* infra) and contract-ambiguity (bad gold) waste mutation budget; bug and
|
|
142
|
+
* spec-gap are real prompt-fixable signal and stay. NOTE: a
|
|
143
|
+
* contract-ambiguity verdict is only actually excluded when it is backed
|
|
144
|
+
* by structured grader evidence — see {@link hasStructuredAmbiguityEvidence}
|
|
145
|
+
* and the module doc (the no-reference heuristic fires for MOST failures
|
|
146
|
+
* on judge-graded datasets, and excluding those would starve the mutator). */
|
|
147
|
+
export declare function isExcludedClass(cls: FailureClass): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* True when a contract-ambiguity verdict is backed by the grader's OWN
|
|
150
|
+
* structured output (`acceptable` / `multipleAcceptable`) rather than the
|
|
151
|
+
* arbiter's no-reference heuristic. Today `toFailingSample` never fabricates
|
|
152
|
+
* `graderOutput` (GradeResult carries no structured output), so this lights
|
|
153
|
+
* up as soon as a grader supplies it — until then every contract-ambiguity
|
|
154
|
+
* verdict in the optimize loop is heuristic and stays in the mutator signal.
|
|
155
|
+
*/
|
|
156
|
+
export declare function hasStructuredAmbiguityEvidence(failing: FailingSample): boolean;
|
|
157
|
+
export type FitnessTriage = {
|
|
158
|
+
/** Sample ids to withhold from the mutator's failure signal this call
|
|
159
|
+
* (freshly classified noise + evidence-backed contract-ambiguity, plus
|
|
160
|
+
* every previously excluded id seen again in this result set). */
|
|
161
|
+
readonly excluded: ReadonlySet<string>;
|
|
162
|
+
/** This call's fresh arbitration counts (queued ids are not re-counted). */
|
|
163
|
+
readonly counts: Readonly<Record<FailureClass, number>>;
|
|
164
|
+
/** Newly classified contract-ambiguity samples (id + arbiter reason).
|
|
165
|
+
* The caller appends ALL of these to its printed dataset-fix queue, but
|
|
166
|
+
* only the `fromGraderEvidence` ones to its sticky exclusion set — the
|
|
167
|
+
* heuristic (no-gold) ones stay in the mutator's failure signal. */
|
|
168
|
+
readonly ambiguous: ReadonlyArray<{
|
|
169
|
+
readonly sampleId: string;
|
|
170
|
+
readonly reason: string;
|
|
171
|
+
readonly fromGraderEvidence: boolean;
|
|
172
|
+
}>;
|
|
173
|
+
/** Previously excluded ids excluded again without re-arbitration. */
|
|
174
|
+
readonly carried: number;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Arbitrate one fitness call's results for the optimize loop. Ids already
|
|
178
|
+
* in `alreadyAmbiguous` (the caller's sticky exclusion set — evidence-backed
|
|
179
|
+
* contract-ambiguity only) are excluded WITHOUT re-arbitration ("exclude
|
|
180
|
+
* them from mutation targets across iterations"): a different candidate's
|
|
181
|
+
* output could flip the class, but the underlying dataset/contract problem
|
|
182
|
+
* is unchanged, so the exclusion is sticky.
|
|
183
|
+
*/
|
|
184
|
+
export declare function triageFitnessSamples(opts: {
|
|
185
|
+
readonly samples: ReadonlyArray<SampleResult>;
|
|
186
|
+
readonly samplesById: ReadonlyMap<string, Sample>;
|
|
187
|
+
readonly alreadyAmbiguous: ReadonlySet<string>;
|
|
188
|
+
}): FitnessTriage;
|
|
189
|
+
/** One observable log line per fitness call that excluded or queued
|
|
190
|
+
* anything; undefined when the triage was a no-op (keep the log quiet). */
|
|
191
|
+
export declare function formatFitnessTriageLine(t: FitnessTriage): string | undefined;
|
|
192
|
+
/** Render the end-of-optimize dataset-fix queue (contract-ambiguity ids +
|
|
193
|
+
* arbiter rationale). Empty queue → no lines (print nothing). */
|
|
194
|
+
export declare function formatDatasetFixQueue(queue: ReadonlyMap<string, string>): string[];
|
|
195
|
+
/**
|
|
196
|
+
* Tee an async sample stream into `sink` (first occurrence of each id
|
|
197
|
+
* wins, matching the union's primary-wins dedupe) while passing every
|
|
198
|
+
* sample through unchanged. Lets the eval flow hand the runner a stream
|
|
199
|
+
* AND retain the Sample objects triage/pinning need afterwards — the
|
|
200
|
+
* runner materializes the stream anyway, so this adds no memory ceiling.
|
|
201
|
+
*/
|
|
202
|
+
export declare function tapSamples(source: AsyncIterable<Sample>, sink: Map<string, Sample>): AsyncIterable<Sample>;
|