onto-mcp 0.4.11 → 0.4.13
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/.onto/authority/core-lexicon.yaml +1 -1
- package/.onto/authority/supported-models.yaml +153 -0
- package/.onto/domains/accounting-kr/competency_qs.md +153 -153
- package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
- package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
- package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +202 -49
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
- package/.onto/processes/review/nesting-batch-worker-contract.md +1 -1
- package/.onto/processes/review/productized-live-path.md +9 -5
- package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
- package/.onto/processes/review/review-target-profile-contract.md +47 -4
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
- package/.onto/processes/shared/target-material-kind-contract.md +1 -1
- package/README.md +64 -1
- package/dist/cli.js +11 -1
- package/dist/core-api/reconstruct-api.js +679 -18
- package/dist/core-api/review-api.js +70 -5
- package/dist/core-api/review-progress.js +7 -0
- package/dist/core-api/runtime-observation.js +7 -0
- package/dist/core-runtime/artifact-io.js +131 -0
- package/dist/core-runtime/cli/claude-code-review-unit-executor.js +21 -9
- package/dist/core-runtime/cli/claude-nesting-batch-worker.js +4 -2
- package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
- package/dist/core-runtime/cli/prepare-review-session.js +34 -1
- package/dist/core-runtime/cli/render-review-final-output.js +2 -1
- package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
- package/dist/core-runtime/cli/run-seats.js +18 -0
- package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
- package/dist/core-runtime/cli/unit-resubmit.js +269 -0
- package/dist/core-runtime/cli/worker-structured-output.js +80 -2
- package/dist/core-runtime/discovery/host-detection.js +71 -22
- package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
- package/dist/core-runtime/discovery/review-cert-record.js +442 -0
- package/dist/core-runtime/discovery/review-execution-units.js +22 -0
- package/dist/core-runtime/discovery/seat-inventory.js +167 -0
- package/dist/core-runtime/discovery/settings-chain.js +311 -52
- package/dist/core-runtime/discovery/supported-models.js +289 -25
- package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
- package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
- package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
- package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
- package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
- package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
- package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
- package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
- package/dist/core-runtime/effort-calibration-ingest.js +326 -0
- package/dist/core-runtime/effort-calibration-reconstruct.js +47 -0
- package/dist/core-runtime/effort-calibration-review.js +22 -0
- package/dist/core-runtime/effort-calibration-sweep.js +110 -0
- package/dist/core-runtime/effort-frontier.js +134 -0
- package/dist/core-runtime/llm/claude-bin.js +77 -0
- package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
- package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
- package/dist/core-runtime/llm/llm-caller.js +316 -35
- package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
- package/dist/core-runtime/llm/model-switcher.js +1 -1
- package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
- package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
- package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
- package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
- package/dist/core-runtime/onboard/cli-host.js +5 -2
- package/dist/core-runtime/onboard/configure-provider.js +33 -1
- package/dist/core-runtime/onboard/host-target.js +28 -3
- package/dist/core-runtime/onboard/register.js +54 -12
- package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +37 -0
- package/dist/core-runtime/reconstruct/benchmark-evidence.js +16 -0
- package/dist/core-runtime/reconstruct/claim-projection-validation.js +19 -5
- package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
- package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
- package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
- package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
- package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
- package/dist/core-runtime/reconstruct/directive-validation.js +4 -3
- package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
- package/dist/core-runtime/reconstruct/execution-telemetry.js +84 -9
- package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
- package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
- package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
- package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
- package/dist/core-runtime/reconstruct/material-admission-validation.js +20 -5
- package/dist/core-runtime/reconstruct/material-profile-validation.js +24 -6
- package/dist/core-runtime/reconstruct/materialize-preparation.js +380 -23
- package/dist/core-runtime/reconstruct/maturation-validation.js +1522 -67
- package/dist/core-runtime/reconstruct/mock-llm-realization.js +120 -1
- package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
- package/dist/core-runtime/reconstruct/ontology-seed-validation.js +39 -7
- package/dist/core-runtime/reconstruct/output-budget.js +6 -0
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +112 -5
- package/dist/core-runtime/reconstruct/post-seed-validation.js +122 -11
- package/dist/core-runtime/reconstruct/proof-authority-validation.js +2 -6
- package/dist/core-runtime/reconstruct/purpose-authority-validation.js +119 -7
- package/dist/core-runtime/reconstruct/record.js +49 -3
- package/dist/core-runtime/reconstruct/registry-verification-validation.js +41 -5
- package/dist/core-runtime/reconstruct/run-control-validation.js +1019 -37
- package/dist/core-runtime/reconstruct/run.js +5409 -517
- package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +56 -8
- package/dist/core-runtime/reconstruct/semantic-quality-gate.js +112 -7
- package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +53 -5
- package/dist/core-runtime/reconstruct/source-observations.js +166 -0
- package/dist/core-runtime/reconstruct/source-safety-validation.js +77 -188
- package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +50 -6
- package/dist/core-runtime/reconstruct/terminal-validation.js +145 -13
- package/dist/core-runtime/review/continuation-plan.js +12 -5
- package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
- package/dist/core-runtime/review/materializers.js +100 -11
- package/dist/core-runtime/review/nesting-batch.js +6 -2
- package/dist/core-runtime/review/obligation-shardability.js +138 -0
- package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
- package/dist/core-runtime/review/review-artifact-utils.js +219 -7
- package/dist/core-runtime/review/review-execution-steps.js +32 -8
- package/dist/core-runtime/review/review-progress-contract.js +8 -0
- package/dist/core-runtime/review/review-prompt-budget.js +97 -0
- package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
- package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
- package/dist/core-runtime/route-identity.js +182 -0
- package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
- package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
- package/dist/core-runtime/target-material-kind.js +58 -0
- package/dist/mcp/server.js +27 -5
- package/dist/mcp/tool-schemas.js +8 -0
- package/dist/tui/app.js +167 -0
- package/dist/tui/data/event-follower.js +116 -0
- package/dist/tui/data/node-detail.js +42 -0
- package/dist/tui/data/projection-poll.js +26 -0
- package/dist/tui/data/session-discovery.js +65 -0
- package/dist/tui/index.js +108 -0
- package/dist/tui/screens/log.js +25 -0
- package/dist/tui/screens/node-detail.js +17 -0
- package/dist/tui/screens/session-selector.js +26 -0
- package/dist/tui/screens/workflow-tree.js +90 -0
- package/dist/tui/view-model/reconstruct-adapter.js +162 -0
- package/dist/tui/view-model/review-adapter.js +270 -0
- package/dist/tui/view-model/tree-view-model.js +19 -0
- package/package.json +20 -3
- package/settings.example.json +77 -16
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconstruct effort-calibration adapter (P3). Distills a reconstruct
|
|
3
|
+
* golden-fixture quality-gate result into the pipeline-agnostic gate signal the
|
|
4
|
+
* sweep aggregator consumes.
|
|
5
|
+
*
|
|
6
|
+
* Reconstruct quality is golden-fixture-anchored and pipeline-WIDE: a sweep run
|
|
7
|
+
* pins one effort knob — the global semantic-author effort (the "author" stage)
|
|
8
|
+
* or the answer-support judge effort (the "judge" stage) — and measures the
|
|
9
|
+
* whole-run golden gate, attributing the result to that stage. The "gate" is the
|
|
10
|
+
* golden gate's overall pass; "quality" is a continuous 0..1 "finding 양·질"
|
|
11
|
+
* proxy synthesized from the gate's three optimization axes:
|
|
12
|
+
* - Q1 expected-concept recall (양·질 of the authored seed),
|
|
13
|
+
* - Q2 competency-question support rate, and
|
|
14
|
+
* - Q3 dropped-question health (fraction of authored questions still assessed;
|
|
15
|
+
* the runtime batches instead of dropping, so this is normally 1.0).
|
|
16
|
+
* Each axis is a 0..1 health fraction; equal-weight mean keeps the score on the
|
|
17
|
+
* same scale as the review adapter's check-pass fraction. The binary pass/fail
|
|
18
|
+
* stays owned by `passed`; this score only feeds the frontier's plateau search.
|
|
19
|
+
*
|
|
20
|
+
* A run that produced no quality verdict — `rejected` (missing telemetry
|
|
21
|
+
* provenance) or `not_applicable` (mock realization incompatible with the
|
|
22
|
+
* fixture) — yields passed=null and qualityScore=null, mirroring review's
|
|
23
|
+
* unjudged treatment: non-passing in the quorum and contributing no quality.
|
|
24
|
+
*
|
|
25
|
+
* Pure — no live LLM and no IO; aggregation and report assembly live in
|
|
26
|
+
* `effort-calibration-sweep.ts`.
|
|
27
|
+
*/
|
|
28
|
+
/** Q3 as a 0..1 health fraction: share of authored questions still assessed. */
|
|
29
|
+
function q3Health(q3) {
|
|
30
|
+
const authored = q3.authored_question_count;
|
|
31
|
+
if (authored <= 0)
|
|
32
|
+
return 1; // no questions to drop → vacuously healthy
|
|
33
|
+
const kept = authored - q3.dropped_question_count;
|
|
34
|
+
return Math.max(0, Math.min(1, kept / authored));
|
|
35
|
+
}
|
|
36
|
+
/** Distill the calibration gate signal from a reconstruct golden gate result. */
|
|
37
|
+
export function reconstructRunGateSignal(gate) {
|
|
38
|
+
// passed/failed are the only quality verdicts; rejected/not_applicable mean no
|
|
39
|
+
// verdict was produced (provenance reject or mock-incompatible fixture) → null.
|
|
40
|
+
const passed = gate.status === "passed" ? true : gate.status === "failed" ? false : null;
|
|
41
|
+
// q1/q2/q3 are populated together (passed/failed) or absent together
|
|
42
|
+
// (rejected/not_applicable); a verdict without metrics leaves quality null.
|
|
43
|
+
const qualityScore = gate.q1 && gate.q2 && gate.q3
|
|
44
|
+
? (gate.q1.recall + gate.q2.support_rate + q3Health(gate.q3)) / 3
|
|
45
|
+
: null;
|
|
46
|
+
return { passed, qualityScore };
|
|
47
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review effort-calibration adapter (P2). Distills a review unit-sweep run's
|
|
3
|
+
* whole-review semantic quality gate into the pipeline-agnostic gate signal the
|
|
4
|
+
* sweep aggregator consumes.
|
|
5
|
+
*
|
|
6
|
+
* Review quality is review-WIDE and fixture-anchored: the unit-sweep varies one
|
|
7
|
+
* LLM unit's effort over a fixed base effort and measures the whole-review
|
|
8
|
+
* semantic quality gate, attributing the result to that unit. So the swept
|
|
9
|
+
* review unit is the "stage", the "gate" is the gate's overall pass, and
|
|
10
|
+
* "quality" is the fraction of the 12-check gate that passed (a continuous 0..1
|
|
11
|
+
* "finding 양·질" proxy). Pure — no live LLM and no IO; aggregation and report
|
|
12
|
+
* assembly live in `effort-calibration-sweep.ts`.
|
|
13
|
+
*/
|
|
14
|
+
/** Distill the calibration gate signal from a full semantic-quality-gate result. */
|
|
15
|
+
export function reviewRunGateSignal(gate) {
|
|
16
|
+
const passed = gate.status === "not_applicable" ? null : gate.status === "passed";
|
|
17
|
+
const total = gate.checks.length;
|
|
18
|
+
const qualityScore = total
|
|
19
|
+
? gate.checks.filter((c) => c.status === "passed").length / total
|
|
20
|
+
: null;
|
|
21
|
+
return { passed, qualityScore };
|
|
22
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effort-calibration sweep aggregation — pipeline-agnostic.
|
|
3
|
+
*
|
|
4
|
+
* A sweep run is one (swept stage, candidate effort) trial whose result has been
|
|
5
|
+
* distilled to a gate signal by a pipeline-specific adapter (review or
|
|
6
|
+
* reconstruct). This module owns the part that does NOT depend on the pipeline:
|
|
7
|
+
* grouping runs into per-stage per-effort buckets, aggregating each bucket into
|
|
8
|
+
* the `EffortSample` series the frontier classifier consumes, and assembling the
|
|
9
|
+
* per-model report. The pipeline adapters own only the gate-signal distillation
|
|
10
|
+
* (their gate result types differ); the frontier classifier owns the decision.
|
|
11
|
+
*
|
|
12
|
+
* Pure — no live LLM and no IO. The sweep harness (scripts/) supplies the runs
|
|
13
|
+
* and writes the report artifact.
|
|
14
|
+
*/
|
|
15
|
+
import { classifyEffortCalibration, } from "./effort-frontier.js";
|
|
16
|
+
const mean = (xs) => xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0;
|
|
17
|
+
const stdev = (xs) => {
|
|
18
|
+
if (xs.length < 2)
|
|
19
|
+
return 0;
|
|
20
|
+
const m = mean(xs);
|
|
21
|
+
return Math.sqrt(mean(xs.map((x) => (x - m) ** 2)));
|
|
22
|
+
};
|
|
23
|
+
const meanCost = (costs) => {
|
|
24
|
+
if (costs.length === 0)
|
|
25
|
+
return undefined;
|
|
26
|
+
const avg = (pick) => {
|
|
27
|
+
const vals = costs
|
|
28
|
+
.map(pick)
|
|
29
|
+
.filter((v) => typeof v === "number");
|
|
30
|
+
return vals.length ? mean(vals) : undefined;
|
|
31
|
+
};
|
|
32
|
+
const promptChars = avg((c) => c.promptChars);
|
|
33
|
+
const outputChars = avg((c) => c.outputChars);
|
|
34
|
+
const providerTokens = avg((c) => c.providerTokens);
|
|
35
|
+
const durationMs = avg((c) => c.durationMs);
|
|
36
|
+
const out = {};
|
|
37
|
+
if (promptChars !== undefined)
|
|
38
|
+
out.promptChars = promptChars;
|
|
39
|
+
if (outputChars !== undefined)
|
|
40
|
+
out.outputChars = outputChars;
|
|
41
|
+
if (providerTokens !== undefined)
|
|
42
|
+
out.providerTokens = providerTokens;
|
|
43
|
+
if (durationMs !== undefined)
|
|
44
|
+
out.durationMs = durationMs;
|
|
45
|
+
return out;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Aggregate sweep runs into per-stage EffortSample series, each ordered
|
|
49
|
+
* ascending by `effortOrder` (efforts absent from the order are dropped — no
|
|
50
|
+
* silent reordering). Stages are sorted by id for a deterministic report.
|
|
51
|
+
*
|
|
52
|
+
* Unjudged (passed === null) runs count as NON-passing in gatePassRate: an
|
|
53
|
+
* effort can only clear the quorum if every run produced a passing verdict, so a
|
|
54
|
+
* bucket that mixes a pass with an unjudged/mock run is downgraded rather than
|
|
55
|
+
* given a free pass (the denominator is the full run count, matching `runs`).
|
|
56
|
+
* Quality is averaged only over runs whose gate produced a score.
|
|
57
|
+
*/
|
|
58
|
+
export function aggregateEffortSweep(runs, effortOrder) {
|
|
59
|
+
const rank = new Map(effortOrder.map((e, i) => [e, i]));
|
|
60
|
+
const byStage = new Map();
|
|
61
|
+
for (const r of runs) {
|
|
62
|
+
if (!rank.has(r.effort))
|
|
63
|
+
continue; // effort not in canonical order → drop
|
|
64
|
+
let byEffort = byStage.get(r.stage);
|
|
65
|
+
if (!byEffort) {
|
|
66
|
+
byEffort = new Map();
|
|
67
|
+
byStage.set(r.stage, byEffort);
|
|
68
|
+
}
|
|
69
|
+
const list = byEffort.get(r.effort) ?? [];
|
|
70
|
+
list.push(r);
|
|
71
|
+
byEffort.set(r.effort, list);
|
|
72
|
+
}
|
|
73
|
+
const stages = [...byStage.entries()].map(([stage, byEffort]) => {
|
|
74
|
+
const samples = [...byEffort.entries()].map(([effort, group]) => {
|
|
75
|
+
// Denominator is the full run count: unjudged (passed === null) runs are
|
|
76
|
+
// non-passing so missing scored evidence cannot overstate viability.
|
|
77
|
+
const passedRuns = group.filter((r) => r.gate.passed === true).length;
|
|
78
|
+
const gatePassRate = group.length ? passedRuns / group.length : 0;
|
|
79
|
+
const qualities = group
|
|
80
|
+
.map((r) => r.gate.qualityScore)
|
|
81
|
+
.filter((q) => typeof q === "number");
|
|
82
|
+
const cost = meanCost(group
|
|
83
|
+
.map((r) => r.cost)
|
|
84
|
+
.filter((c) => c !== undefined));
|
|
85
|
+
return {
|
|
86
|
+
effort,
|
|
87
|
+
gatePassRate,
|
|
88
|
+
quality: mean(qualities),
|
|
89
|
+
qualityStdev: stdev(qualities),
|
|
90
|
+
runs: group.length,
|
|
91
|
+
...(cost ? { cost } : {}),
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
samples.sort((a, b) => rank.get(a.effort) - rank.get(b.effort));
|
|
95
|
+
return { stage, samples };
|
|
96
|
+
});
|
|
97
|
+
stages.sort((a, b) => (a.stage < b.stage ? -1 : a.stage > b.stage ? 1 : 0));
|
|
98
|
+
return stages;
|
|
99
|
+
}
|
|
100
|
+
/** Build a per-model effort-calibration report from sweep runs (pure). */
|
|
101
|
+
export function buildEffortCalibrationReport(args) {
|
|
102
|
+
return classifyEffortCalibration({
|
|
103
|
+
pipeline: args.pipeline,
|
|
104
|
+
provider: args.provider,
|
|
105
|
+
model: args.model,
|
|
106
|
+
route: args.route,
|
|
107
|
+
thresholds: args.thresholds,
|
|
108
|
+
stages: aggregateEffortSweep(args.runs, args.effortOrder),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effort frontier calibration — pure, deterministic core.
|
|
3
|
+
*
|
|
4
|
+
* For each LLM stage of a pipeline (review or reconstruct), given a series of
|
|
5
|
+
* per-effort aggregated samples (one per effort level, aggregated over N runs),
|
|
6
|
+
* classify the reasoning-effort frontier:
|
|
7
|
+
*
|
|
8
|
+
* - minViableEffort — the lowest effort whose gate passes (>= passQuorum of
|
|
9
|
+
* runs). Below it the stage fails.
|
|
10
|
+
* - effectiveMaxEffort — the lowest gate-passing effort beyond which no higher
|
|
11
|
+
* effort improves quality by more than plateauThreshold
|
|
12
|
+
* (the knee of the cost/quality curve). Above it more
|
|
13
|
+
* effort buys ~nothing.
|
|
14
|
+
* - recommendedEffort — effectiveMaxEffort: the cheapest effort at the quality
|
|
15
|
+
* plateau that still passes the gate.
|
|
16
|
+
*
|
|
17
|
+
* Pipeline-agnostic: callers supply already-aggregated samples (quality is a
|
|
18
|
+
* pipeline-defined "finding 양·질" metric, higher = better) in ascending effort
|
|
19
|
+
* order. No live LLM calls and no I/O — fully unit-testable. The benchmark/sweep
|
|
20
|
+
* harnesses (scripts/) produce the samples; this module decides the frontier and
|
|
21
|
+
* assembles the per-model calibration record that is re-run per new model.
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_PASS_QUORUM = 1;
|
|
24
|
+
// Tolerance for the plateau comparison. Quality deltas from averaged 0..1
|
|
25
|
+
// metrics carry float noise (e.g. 0.90 - 0.85 === 0.050000000000000044), so a
|
|
26
|
+
// gain mathematically equal to the threshold must NOT count as an improvement —
|
|
27
|
+
// the contract treats gains at or below the threshold as a plateau.
|
|
28
|
+
const QUALITY_EPSILON = 1e-9;
|
|
29
|
+
const curveOf = (samples) => samples.map((s) => ({
|
|
30
|
+
effort: s.effort,
|
|
31
|
+
gatePassRate: s.gatePassRate,
|
|
32
|
+
quality: s.quality,
|
|
33
|
+
qualityStdev: s.qualityStdev,
|
|
34
|
+
runs: s.runs,
|
|
35
|
+
...(s.cost ? { cost: s.cost } : {}),
|
|
36
|
+
}));
|
|
37
|
+
/**
|
|
38
|
+
* Classify the effort frontier for one stage. `samples` must be in ascending
|
|
39
|
+
* effort order (cheapest first); the caller owns the effort ordering.
|
|
40
|
+
*/
|
|
41
|
+
export function classifyStageFrontier(stage, samples, thresholds) {
|
|
42
|
+
const passQuorum = thresholds.passQuorum ?? DEFAULT_PASS_QUORUM;
|
|
43
|
+
const plateau = thresholds.plateauThreshold;
|
|
44
|
+
const curve = curveOf(samples);
|
|
45
|
+
if (samples.length === 0) {
|
|
46
|
+
return {
|
|
47
|
+
stage,
|
|
48
|
+
minViableEffort: null,
|
|
49
|
+
effectiveMaxEffort: null,
|
|
50
|
+
recommendedEffort: null,
|
|
51
|
+
plateauReached: false,
|
|
52
|
+
rationale: "no samples provided",
|
|
53
|
+
curve,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// min viable: the lowest effort whose gate clears the quorum.
|
|
57
|
+
const minViableIdx = samples.findIndex((s) => s.gatePassRate >= passQuorum);
|
|
58
|
+
if (minViableIdx === -1) {
|
|
59
|
+
return {
|
|
60
|
+
stage,
|
|
61
|
+
minViableEffort: null,
|
|
62
|
+
effectiveMaxEffort: null,
|
|
63
|
+
recommendedEffort: null,
|
|
64
|
+
plateauReached: false,
|
|
65
|
+
rationale: `gate never reaches passQuorum=${passQuorum} at any tested effort`,
|
|
66
|
+
curve,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// candidates: gate-passing efforts at or above min viable, ascending. (A
|
|
70
|
+
// higher effort can dip below the quorum, e.g. overthinking; such efforts are
|
|
71
|
+
// not viable recommendations and are excluded from the plateau search.)
|
|
72
|
+
const candidates = samples
|
|
73
|
+
.slice(minViableIdx)
|
|
74
|
+
.filter((s) => s.gatePassRate >= passQuorum);
|
|
75
|
+
// effective max: the lowest candidate whose quality is not beaten by more than
|
|
76
|
+
// the plateau threshold by ANY higher candidate (checking all higher ones, not
|
|
77
|
+
// just the adjacent, makes this robust to non-monotonic noise). The highest
|
|
78
|
+
// candidate satisfies this vacuously, so a result always exists.
|
|
79
|
+
let effIdx = candidates.length - 1;
|
|
80
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
81
|
+
const base = candidates[i].quality;
|
|
82
|
+
const beatenLater = candidates
|
|
83
|
+
.slice(i + 1)
|
|
84
|
+
.some((c) => c.quality - base > plateau + QUALITY_EPSILON);
|
|
85
|
+
if (!beatenLater) {
|
|
86
|
+
effIdx = i;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const minViable = samples[minViableIdx].effort;
|
|
91
|
+
const effective = candidates[effIdx].effort;
|
|
92
|
+
const highestViable = candidates[candidates.length - 1].effort;
|
|
93
|
+
const plateauReached = candidates.length > 1 && effective !== highestViable;
|
|
94
|
+
let rationale;
|
|
95
|
+
if (candidates.length === 1) {
|
|
96
|
+
rationale = `only "${minViable}" is viable in the tested range; plateau indeterminate`;
|
|
97
|
+
}
|
|
98
|
+
else if (plateauReached) {
|
|
99
|
+
rationale = `gate first passes at "${minViable}"; quality plateaus at "${effective}" (no higher effort gains >${plateau})`;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
rationale = `gate first passes at "${minViable}"; quality still climbing at the top of the viable range "${highestViable}" — plateau not captured; re-run with higher effort if available`;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
stage,
|
|
106
|
+
minViableEffort: minViable,
|
|
107
|
+
effectiveMaxEffort: effective,
|
|
108
|
+
recommendedEffort: effective,
|
|
109
|
+
plateauReached,
|
|
110
|
+
rationale,
|
|
111
|
+
curve,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Assemble a per-model calibration record from per-stage sample series. The
|
|
116
|
+
* caller (harness) stamps identity/time metadata when serializing the artifact;
|
|
117
|
+
* this function stays deterministic (no clock, no I/O).
|
|
118
|
+
*/
|
|
119
|
+
export function classifyEffortCalibration(args) {
|
|
120
|
+
return {
|
|
121
|
+
pipeline: args.pipeline,
|
|
122
|
+
provider: args.provider,
|
|
123
|
+
model: args.model,
|
|
124
|
+
route: args.route,
|
|
125
|
+
// Persist the *effective* thresholds (resolve the default quorum) so a
|
|
126
|
+
// serialized record is self-describing — a reader/replay can tell an
|
|
127
|
+
// all-runs quorum from a relaxed one without knowing this module's default.
|
|
128
|
+
thresholds: {
|
|
129
|
+
passQuorum: args.thresholds.passQuorum ?? DEFAULT_PASS_QUORUM,
|
|
130
|
+
plateauThreshold: args.thresholds.plateauThreshold,
|
|
131
|
+
},
|
|
132
|
+
stages: args.stages.map((s) => classifyStageFrontier(s.stage, s.samples, args.thresholds)),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the Claude Code CLI binary path, accommodating per-user install
|
|
6
|
+
* locations. Anthropic OAuth review/reconstruct workers spawn this binary, and
|
|
7
|
+
* its location varies by machine and install method — and a host like Claude
|
|
8
|
+
* Desktop may launch the server with a minimal `PATH` that does not include the
|
|
9
|
+
* user's `claude`. So a bare `spawn("claude")` is not reliable across users.
|
|
10
|
+
*
|
|
11
|
+
* Resolution order (first hit wins):
|
|
12
|
+
* 1. `ONTO_CLAUDE_BIN` — explicit operator override (absolute path or name).
|
|
13
|
+
* 2. `claude` discoverable on `PATH` (as an executable file — shell aliases
|
|
14
|
+
* and functions are never consulted because spawn does not use them).
|
|
15
|
+
* 3. Common Claude Code install locations (native installer, older local
|
|
16
|
+
* install, Homebrew).
|
|
17
|
+
* 4. The literal `"claude"` — so `spawn` fails with the existing actionable
|
|
18
|
+
* ENOENT message ("Claude Code CLI not found … set ONTO_CLAUDE_BIN") rather
|
|
19
|
+
* than this resolver inventing a different error.
|
|
20
|
+
*
|
|
21
|
+
* Pure function of `env`: resolution is a handful of `stat`/`access` syscalls and
|
|
22
|
+
* each call site invokes it at most once per spawn (or once at module load), so
|
|
23
|
+
* it is NOT cached — a cache keyed on nothing would return a stale path when a
|
|
24
|
+
* later call passes a different `env.PATH` (the injected `env` would silently
|
|
25
|
+
* stop controlling discovery). The same applies to the codex OAuth direct route
|
|
26
|
+
* (`callCodexCli` still spawns a bare `"codex"`); extending this resolver to
|
|
27
|
+
* codex is a symmetric, deferred follow-up.
|
|
28
|
+
*/
|
|
29
|
+
function isExecutableFile(candidate) {
|
|
30
|
+
try {
|
|
31
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
32
|
+
return fs.statSync(candidate).isFile();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** `claude` resolvable as an executable file on `PATH` (not a shell alias). */
|
|
39
|
+
function findClaudeOnPath(env) {
|
|
40
|
+
const rawPath = env.PATH ?? env.Path ?? "";
|
|
41
|
+
for (const dir of rawPath.split(path.delimiter)) {
|
|
42
|
+
if (!dir)
|
|
43
|
+
continue;
|
|
44
|
+
const candidate = path.join(dir, "claude");
|
|
45
|
+
if (isExecutableFile(candidate))
|
|
46
|
+
return candidate;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
/** Common Claude Code install locations, checked when PATH lookup misses. */
|
|
51
|
+
function commonInstallLocations() {
|
|
52
|
+
const home = os.homedir();
|
|
53
|
+
return [
|
|
54
|
+
path.join(home, ".local", "bin", "claude"), // native installer symlink
|
|
55
|
+
path.join(home, ".claude", "local", "claude"), // older local install
|
|
56
|
+
"/opt/homebrew/bin/claude", // Homebrew (Apple silicon)
|
|
57
|
+
"/usr/local/bin/claude", // Homebrew (Intel) / manual
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
export function resolveClaudeBin(env = process.env) {
|
|
61
|
+
const override = env.ONTO_CLAUDE_BIN?.trim();
|
|
62
|
+
if (override)
|
|
63
|
+
return override;
|
|
64
|
+
const onPath = findClaudeOnPath(env);
|
|
65
|
+
if (onPath)
|
|
66
|
+
return onPath;
|
|
67
|
+
for (const location of commonInstallLocations()) {
|
|
68
|
+
if (isExecutableFile(location))
|
|
69
|
+
return location;
|
|
70
|
+
}
|
|
71
|
+
// Not found — return the bare name so spawn ENOENT carries the actionable
|
|
72
|
+
// "set ONTO_CLAUDE_BIN" guidance.
|
|
73
|
+
// POSIX-scoped: the bundle targets macOS/Linux (manifest compatibility.platforms
|
|
74
|
+
// = darwin/linux) and discovers a `claude` binary (no Windows `claude.exe` /
|
|
75
|
+
// %LOCALAPPDATA% lookup). Windows is out of scope here.
|
|
76
|
+
return "claude";
|
|
77
|
+
}
|