akm-cli 0.9.10 → 0.9.12
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/CHANGELOG.md +226 -0
- package/STABILITY.md +28 -15
- package/dist/assets/hints/cli-hints-full.md +1 -1
- package/dist/assets/improve-strategies/consolidate.json +1 -1
- package/dist/assets/improve-strategies/default.json +1 -1
- package/dist/assets/improve-strategies/thorough.json +1 -2
- package/dist/cli/shared.js +16 -4
- package/dist/cli.js +15 -13
- package/dist/commands/agent/agent-dispatch.js +8 -0
- package/dist/commands/command/execution-source-loader.js +25 -22
- package/dist/commands/command/portable-template.js +4 -26
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env-binding.js +10 -3
- package/dist/commands/env/env-cli.js +7 -0
- package/dist/commands/env/secret-cli.js +15 -4
- package/dist/commands/health/checks.js +209 -78
- package/dist/commands/health/improve-metrics.js +12 -0
- package/dist/commands/health.js +16 -4
- package/dist/commands/improve/distill/quality-gate.js +15 -7
- package/dist/commands/improve/distill.js +28 -12
- package/dist/commands/improve/eval-cases.js +9 -2
- package/dist/commands/improve/execution.js +1 -2
- package/dist/commands/improve/extract.js +82 -56
- package/dist/commands/improve/improve-strategies.js +26 -8
- package/dist/commands/improve/improve.js +32 -4
- package/dist/commands/improve/loop-stages.js +13 -3
- package/dist/commands/improve/preparation.js +9 -6
- package/dist/commands/improve/reflect.js +61 -77
- package/dist/commands/lint/index.js +3 -1
- package/dist/commands/migrate-cli.js +6 -4
- package/dist/commands/proposal/drain-policies.js +22 -2
- package/dist/commands/proposal/repository.js +4 -4
- package/dist/commands/proposal/validators/proposal-quality-validators.js +23 -2
- package/dist/commands/proposal/validators/proposals.js +10 -19
- package/dist/commands/read/show.js +42 -31
- package/dist/commands/registry-cli.js +4 -2
- package/dist/commands/sources/init.js +4 -8
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-clone.js +5 -7
- package/dist/commands/sources/sources-cli.js +3 -5
- package/dist/commands/tasks/tasks-cli.js +36 -12
- package/dist/commands/tasks/tasks.js +38 -35
- package/dist/commands/tasks/validate.js +186 -0
- package/dist/commands/url-checker.js +75 -16
- package/dist/commands/workflow-cli.js +17 -15
- package/dist/core/activation-policy.js +31 -3
- package/dist/core/adapter/execution-source.js +39 -11
- package/dist/core/asset/stash-meta.js +7 -41
- package/dist/core/bundle-id.js +7 -1
- package/dist/core/common.js +8 -17
- package/dist/core/config/config-schema.js +3 -23
- package/dist/core/config/config-walker.js +50 -4
- package/dist/core/config/config.js +21 -5
- package/dist/core/config/legacy-source-shape-shim.js +79 -0
- package/dist/core/config/schema/embedding.js +2 -2
- package/dist/core/config/schema/engines.js +19 -2
- package/dist/core/config/schema/index-config.js +19 -21
- package/dist/core/config/schema/primitives.js +21 -10
- package/dist/core/config/schema/sources-bundles.js +1 -6
- package/dist/core/errors.js +2 -3
- package/dist/core/improve-result.js +8 -0
- package/dist/core/improve-types.js +17 -0
- package/dist/core/json-schema.js +1 -11
- package/dist/core/maintenance-barrier.js +17 -2
- package/dist/core/paths.js +124 -15
- package/dist/core/state/migrations.js +28 -0
- package/dist/core/state-db.js +28 -1
- package/dist/core/write-source.js +6 -6
- package/dist/indexer/bundle-identity-guard.js +3 -0
- package/dist/indexer/ensure-index.js +5 -0
- package/dist/indexer/indexer.js +11 -3
- package/dist/indexer/lookup/adapter-concept-owner.js +14 -3
- package/dist/indexer/passes/metadata.js +16 -5
- package/dist/indexer/search/search-fields.js +1 -30
- package/dist/indexer/search/search-source.js +3 -2
- package/dist/integrations/agent/engine-resolution.js +107 -4
- package/dist/integrations/agent/execution-lowering.js +15 -2
- package/dist/integrations/agent/model-map.js +16 -10
- package/dist/integrations/agent/prompts.js +13 -6
- package/dist/integrations/agent/runner-dispatch.js +16 -3
- package/dist/integrations/agent/runner.js +2 -0
- package/dist/integrations/lockfile.js +22 -7
- package/dist/llm/client.js +16 -0
- package/dist/llm/index-passes.js +3 -2
- package/dist/output/shapes/passthrough.js +10 -3
- package/dist/output/shapes.js +5 -3
- package/dist/output/text/workflow-format.js +8 -1
- package/dist/scripts/akm-migrate-node.js +2028 -1613
- package/dist/scripts/akm-migrate.js +2027 -1612
- package/dist/setup/setup.js +14 -21
- package/dist/sources/include.js +150 -20
- package/dist/sources/providers/git-install.js +14 -12
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/snapshot-fetchers/website-ingest.js +54 -16
- package/dist/sources/website-url.js +12 -4
- package/dist/storage/engines/sqlite-migrations.js +40 -10
- package/dist/storage/like-pattern.js +7 -0
- package/dist/storage/repositories/extract-sessions-repository.js +23 -0
- package/dist/storage/repositories/index-connection.js +27 -10
- package/dist/storage/repositories/index-entry-schema.js +19 -2
- package/dist/storage/repositories/index-schema.js +30 -9
- package/dist/storage/repositories/proposals-repository.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +14 -7
- package/dist/storage/repositories/workflow-runs-repository.js +15 -1
- package/dist/storage/sqlite-read-snapshot.js +11 -9
- package/dist/tasks/backends/cron.js +34 -5
- package/dist/tasks/backends/launchd.js +23 -26
- package/dist/tasks/backends/schtasks.js +50 -3
- package/dist/tasks/frozen-script.js +2 -0
- package/dist/tasks/prepare/prepare.js +2 -7
- package/dist/tasks/prepare/script-capture.js +38 -6
- package/dist/tasks/schedule.js +154 -13
- package/dist/tasks/scheduler-sync.js +51 -25
- package/dist/tasks/source/task-source-v3-frozen.js +0 -1
- package/dist/tasks/source/task-source-v4.js +0 -1
- package/dist/workflows/exec/child-workflow.js +2 -3
- package/dist/workflows/exec/dispatch-redaction.js +21 -7
- package/dist/workflows/exec/exec-unit.js +3 -4
- package/dist/workflows/exec/run-workflow.js +19 -10
- package/dist/workflows/exec/step-work.js +35 -56
- package/dist/workflows/freeze/resolve-steps.js +19 -11
- package/dist/workflows/freeze/source-freeze.js +7 -0
- package/dist/workflows/freeze/targets/child-workflow.js +12 -18
- package/dist/workflows/freeze/targets/command.js +14 -2
- package/dist/workflows/ir/environment-v4.js +4 -2
- package/dist/workflows/ir/freeze-v4.js +2 -5
- package/dist/workflows/ir/plan-hash.js +0 -3
- package/dist/workflows/ir/schema-v4.js +14 -9
- package/dist/workflows/ir/schema.js +1 -3
- package/dist/workflows/resource-limits.js +35 -48
- package/dist/workflows/runtime/plan-classifier.js +89 -41
- package/dist/workflows/runtime/run-outputs.js +1 -21
- package/dist/workflows/runtime/runs.js +76 -150
- package/dist/workflows/source-files.js +28 -54
- package/dist/workflows/source-ir/program.js +2 -2
- package/dist/workflows/source-ir/semantics.js +5 -23
- package/docs/integration/bundling-akm.md +1 -1
- package/docs/migration/v0.8-to-v0.9.md +32 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +20 -0
- package/docs/reference/cli.md +105 -22
- package/docs/reference/configuration.md +12 -2
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/tasks.md +8 -0
- package/package.json +1 -1
- package/schemas/akm-config.json +11 -8
package/dist/commands/health.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import { resolveStashDir } from "../core/common.js";
|
|
6
|
+
import { daysToMs, resolveStashDir } from "../core/common.js";
|
|
7
7
|
import { loadConfig } from "../core/config/config.js";
|
|
8
8
|
import { ConfigError, UsageError } from "../core/errors.js";
|
|
9
9
|
import { readEvents } from "../core/events.js";
|
|
@@ -12,12 +12,14 @@ import { classifyPathAccess, describeInaccessiblePath } from "../core/path-acces
|
|
|
12
12
|
import { getConfigPath, getDataDir, getDbPath, getStateDbPathInDataDir } from "../core/paths.js";
|
|
13
13
|
import { listExistingTableNames, listPendingStateMigrations, openStateDatabase } from "../core/state-db.js";
|
|
14
14
|
import { DURATION_UNITS, parseDuration, parseSinceToIso } from "../core/time.js";
|
|
15
|
+
import { probeLlmEndpoint } from "../llm/client.js";
|
|
16
|
+
import { getExtractOutcomeCountsSince } from "../storage/repositories/extract-sessions-repository.js";
|
|
15
17
|
import { closeDatabase, openReadonlyExistingDatabase } from "../storage/repositories/index-connection.js";
|
|
16
18
|
import { getAllEntries } from "../storage/repositories/index-entries-repository.js";
|
|
17
19
|
import { queryTaskHistory } from "../storage/repositories/task-history-repository.js";
|
|
18
20
|
import { pkgVersion } from "../version.js";
|
|
19
21
|
import { collectImproveAdvisories } from "./health/advisories.js";
|
|
20
|
-
import { HEALTH_CHECKS, runHealthEngineProbes, runPendingStateMigrationsCheck, } from "./health/checks.js";
|
|
22
|
+
import { HEALTH_CHECKS, runHealthEngineProbes, runPendingStateMigrationsCheck, SESSION_EXTRACTION_LEDGER_WINDOW_DAYS, } from "./health/checks.js";
|
|
21
23
|
import { collectDataDirUsageAdvisory } from "./health/data-dir-usage.js";
|
|
22
24
|
import { buildImproveSkipSummary, computeWallTimeStats, isAgentTaskHistoryRow, roundRate, summarizeImproveCompleted, summarizeImproveRuns, taskFailureDetail, } from "./health/improve-metrics.js";
|
|
23
25
|
import { emptyLlmUsageAggregate, readLlmUsageAggregate } from "./health/llm-usage.js";
|
|
@@ -166,6 +168,11 @@ function gatherEgressConfigPhase() {
|
|
|
166
168
|
}
|
|
167
169
|
return { egressConfigView };
|
|
168
170
|
}
|
|
171
|
+
/** Extract-ledger outcome counts for the `session-extraction` check's window, independent of `--since`. */
|
|
172
|
+
function gatherSessionExtractionLedgerPhase(db, now) {
|
|
173
|
+
const since = new Date(now() - daysToMs(SESSION_EXTRACTION_LEDGER_WINDOW_DAYS)).toISOString();
|
|
174
|
+
return { since, rows: getExtractOutcomeCountsSince(db, since) };
|
|
175
|
+
}
|
|
169
176
|
/**
|
|
170
177
|
* Assemble the window's improve-pipeline summary: invoked/completed/skipped
|
|
171
178
|
* counts from events, the per-run result_json aggregate, wall-time stats, and
|
|
@@ -466,7 +473,7 @@ function unreadableStateDbCheck(detail) {
|
|
|
466
473
|
evidence: { detail },
|
|
467
474
|
};
|
|
468
475
|
}
|
|
469
|
-
export function akmHealth(options = {}) {
|
|
476
|
+
export async function akmHealth(options = {}) {
|
|
470
477
|
validateAkmHealthOptions(options);
|
|
471
478
|
const now = options.now ?? (() => Date.now());
|
|
472
479
|
const since = parseHealthSince(options.since);
|
|
@@ -519,12 +526,16 @@ export function akmHealth(options = {}) {
|
|
|
519
526
|
logsDb = undefined;
|
|
520
527
|
}
|
|
521
528
|
try {
|
|
529
|
+
// Network probes overlap the local database phases below; awaited where consumed.
|
|
530
|
+
const engineProbesPromise = runHealthEngineProbes({ probeReachable: options.probe ? probeLlmEndpoint : undefined });
|
|
531
|
+
engineProbesPromise.catch(() => undefined);
|
|
522
532
|
const taskHistory = gatherTaskHistoryPhase(db, logsDb, since, stateDbPath, now);
|
|
523
533
|
const { tableNames, missingTables, probe } = taskHistory;
|
|
524
534
|
const { egressConfigView } = gatherEgressConfigPhase();
|
|
525
535
|
const { improveSummary } = gatherImproveSummaryPhase(db, stateDbPath, since, now);
|
|
526
536
|
advisories.push(...gatherAncillaryAdvisories(db, stateDbPath, since, improveSummary, options, egressConfigView));
|
|
527
|
-
const
|
|
537
|
+
const sessionExtractionLedger = gatherSessionExtractionLedgerPhase(db, now);
|
|
538
|
+
const engineProbes = await engineProbesPromise;
|
|
528
539
|
// Run the ordered health-check registry. Each check projects the shared
|
|
529
540
|
// context computed above into one HealthCheckResult; `channel` routes it to
|
|
530
541
|
// hardChecks or advisories. Declaration order in HEALTH_CHECKS is the
|
|
@@ -544,6 +555,7 @@ export function akmHealth(options = {}) {
|
|
|
544
555
|
stuckActiveTasks: taskHistory.stuckActiveTasks,
|
|
545
556
|
worstTaskFailRate: taskHistory.worstTaskFailRate,
|
|
546
557
|
sessionExtraction: improveSummary.sessionExtraction,
|
|
558
|
+
sessionExtractionLedger,
|
|
547
559
|
autoAccept: improveSummary.autoAccept,
|
|
548
560
|
engineProbes,
|
|
549
561
|
};
|
|
@@ -15,6 +15,7 @@ import { timestampForFilename } from "../../../core/common.js";
|
|
|
15
15
|
import { ConfigError } from "../../../core/errors.js";
|
|
16
16
|
import { appendEvent } from "../../../core/events.js";
|
|
17
17
|
import { parseEmbeddedJsonResponse } from "../../../core/parse.js";
|
|
18
|
+
import { getDistillRejectedDir } from "../../../core/paths.js";
|
|
18
19
|
import { withStateDb } from "../../../core/state-db.js";
|
|
19
20
|
import { recordWrittenPath } from "../../../core/write-provenance.js";
|
|
20
21
|
import { callStructured } from "../../../llm/structured-call.js";
|
|
@@ -199,7 +200,7 @@ async function runQualityJudge(feature, config, prompt, chat, options = {}) {
|
|
|
199
200
|
parsed.score < 1 ||
|
|
200
201
|
parsed.score > 5 ||
|
|
201
202
|
typeof parsed.reason !== "string") {
|
|
202
|
-
return { pass: false, score: -1, reason: "judge parse failed —
|
|
203
|
+
return { pass: false, score: -1, reason: "judge parse failed — routed to review", reviewNeeded: true };
|
|
203
204
|
}
|
|
204
205
|
// D-5 / #388: Three-band system (MT-Bench arXiv:2306.05685 — ~±0.5 judge variance).
|
|
205
206
|
// >= 3.5: auto-queue as pending (pass: true)
|
|
@@ -218,7 +219,7 @@ async function runQualityJudge(feature, config, prompt, chat, options = {}) {
|
|
|
218
219
|
// content verdict. Provider/runtime failures retain the fail-closed result.
|
|
219
220
|
if (error instanceof ConfigError)
|
|
220
221
|
throw error;
|
|
221
|
-
return { pass: false, score: -1, reason: "judge timeout/error —
|
|
222
|
+
return { pass: false, score: -1, reason: "judge timeout/error — routed to review", reviewNeeded: true };
|
|
222
223
|
}
|
|
223
224
|
}
|
|
224
225
|
/**
|
|
@@ -242,8 +243,9 @@ export async function runReflectQualityJudge(config, candidateContent, sourceCon
|
|
|
242
243
|
}
|
|
243
244
|
// ── Quality-rejection helper ─────────────────────────────────────────────────
|
|
244
245
|
/**
|
|
245
|
-
* Write a rejected lesson to
|
|
246
|
-
* quality-rejected event, and
|
|
246
|
+
* Write a rejected lesson to `$STATE/improve/distill-rejected/<stash>/`
|
|
247
|
+
* (itlackey/akm#890), append a `distill_invoked` quality-rejected event, and
|
|
248
|
+
* return the `quality_rejected` envelope.
|
|
247
249
|
*
|
|
248
250
|
* @param stash - Root stash directory.
|
|
249
251
|
* @param inputRef - The original input ref (for the event).
|
|
@@ -257,13 +259,19 @@ export async function runReflectQualityJudge(config, candidateContent, sourceCon
|
|
|
257
259
|
export function writeQualityRejection(stash, inputRef, proposalRef, content, score, reason, extraMeta = {}, eligibilitySource, eventsCtx) {
|
|
258
260
|
// D-5 / #388: reviewNeeded flag selects "review_needed" vs "quality_rejected" outcome.
|
|
259
261
|
const outcome = extraMeta.reviewNeeded ? "review_needed" : "quality_rejected";
|
|
260
|
-
const rejectDir =
|
|
262
|
+
const rejectDir = getDistillRejectedDir(stash);
|
|
261
263
|
fs.mkdirSync(rejectDir, { recursive: true });
|
|
262
264
|
const ts = timestampForFilename();
|
|
263
265
|
const rejectPath = path.join(rejectDir, `${ts}-${proposalRef.replace(/[:/\\]/g, "-")}.md`);
|
|
264
266
|
fs.writeFileSync(rejectPath, `---\nscore: ${score}\nreason: ${reason}\noutcome: ${outcome}\n---\n\n${content}`, "utf8");
|
|
265
|
-
// #652
|
|
266
|
-
//
|
|
267
|
+
// #652 / itlackey/akm#890: journal it even though it now lands under
|
|
268
|
+
// `$STATE`, outside the stash's git repo — `result.writtenPaths` reports
|
|
269
|
+
// every path a run touched, in or out of the stash (describeRunWrittenPaths
|
|
270
|
+
// in improve.ts falls back to the absolute path for anything outside the
|
|
271
|
+
// stash root), and the auto-sync commit's own containment check
|
|
272
|
+
// (resolveSyncPathSet's `relativeWrittenPath`) already drops anything
|
|
273
|
+
// outside `repoDir` from what gets staged — recording it here cannot cause
|
|
274
|
+
// it to be committed.
|
|
267
275
|
recordWrittenPath(rejectPath);
|
|
268
276
|
appendEvent({
|
|
269
277
|
eventType: "distill_invoked",
|
|
@@ -778,7 +778,7 @@ export async function akmDistill(options) {
|
|
|
778
778
|
eventsCtx: options.eventsCtx,
|
|
779
779
|
}));
|
|
780
780
|
}
|
|
781
|
-
const
|
|
781
|
+
const assembled = assembleAndValidateDistillContent({
|
|
782
782
|
raw,
|
|
783
783
|
effectiveProposalKind,
|
|
784
784
|
inputRef,
|
|
@@ -789,7 +789,11 @@ export async function akmDistill(options) {
|
|
|
789
789
|
filteredFeedbackCount,
|
|
790
790
|
eligMeta,
|
|
791
791
|
eventsCtx: options.eventsCtx,
|
|
792
|
+
stash,
|
|
792
793
|
});
|
|
794
|
+
if ("rejection" in assembled)
|
|
795
|
+
return withNotices(assembled.rejection);
|
|
796
|
+
const { content, descriptionSwapped } = assembled;
|
|
793
797
|
const gate = await applyDistillQualityGate({
|
|
794
798
|
config,
|
|
795
799
|
options,
|
|
@@ -968,7 +972,7 @@ async function emitDistillLessonProposal(args) {
|
|
|
968
972
|
* throwing a `UsageError` on any finding. Extracted verbatim from `akmDistill`.
|
|
969
973
|
*/
|
|
970
974
|
function assembleAndValidateDistillContent(args) {
|
|
971
|
-
const { raw, effectiveProposalKind, inputRef, durableInputRef, itemRef, effectiveLessonRef, exclusionSet, filteredFeedbackCount, eligMeta, eventsCtx, } = args;
|
|
975
|
+
const { raw, effectiveProposalKind, inputRef, durableInputRef, itemRef, effectiveLessonRef, exclusionSet, filteredFeedbackCount, eligMeta, eventsCtx, stash, } = args;
|
|
972
976
|
// Structured-output path: when the provider honoured the JSON schema, `raw`
|
|
973
977
|
// is a JSON object string (not a markdown blob). Try to parse it and assemble
|
|
974
978
|
// the canonical `---\nfm\n---\n\nbody` form before using the markdown
|
|
@@ -1003,18 +1007,19 @@ function assembleAndValidateDistillContent(args) {
|
|
|
1003
1007
|
content = repairLessonDescriptionTruncation(content);
|
|
1004
1008
|
}
|
|
1005
1009
|
// Parse + lint the lesson before creating the proposal. The lint is the
|
|
1006
|
-
// canonical gate for required frontmatter (v1 spec §13)
|
|
1007
|
-
//
|
|
1008
|
-
// `distill_invoked` so
|
|
1009
|
-
|
|
1010
|
+
// canonical gate for required frontmatter (v1 spec §13): a field that is
|
|
1011
|
+
// genuinely missing or empty means there is no valid asset to write, so
|
|
1012
|
+
// that failure stays a hard reject — but still emit `distill_invoked` so
|
|
1013
|
+
// the failure is observable.
|
|
1014
|
+
const structuralFindings = effectiveProposalKind === "knowledge"
|
|
1010
1015
|
? validateKnowledgeContent(content, inputRef)
|
|
1011
1016
|
: lintLessonContent(content, `distill:${inputRef}`).findings;
|
|
1012
1017
|
// Additional lesson-only quality validators — reject the systematic failure
|
|
1013
1018
|
// modes seen across 323 archived rejected proposals (see distill/content-repair).
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
if (
|
|
1019
|
+
const qualityFindings = effectiveProposalKind !== "knowledge" && structuralFindings.length === 0
|
|
1020
|
+
? collectLessonQualityFindings(content, inputRef)
|
|
1021
|
+
: [];
|
|
1022
|
+
if (structuralFindings.length > 0) {
|
|
1018
1023
|
appendEvent({
|
|
1019
1024
|
eventType: "distill_invoked",
|
|
1020
1025
|
// Use item_ref when resolved, otherwise the input conceptId.
|
|
@@ -1023,16 +1028,27 @@ function assembleAndValidateDistillContent(args) {
|
|
|
1023
1028
|
outcome: "validation_failed",
|
|
1024
1029
|
proposalRef: effectiveLessonRef,
|
|
1025
1030
|
proposalKind: effectiveProposalKind,
|
|
1026
|
-
findingKinds:
|
|
1031
|
+
findingKinds: structuralFindings.map((f) => f.kind),
|
|
1027
1032
|
...(exclusionSet.size > 0 ? { filteredFeedbackCount } : {}),
|
|
1028
1033
|
...eligMeta,
|
|
1029
1034
|
},
|
|
1030
1035
|
}, eventsCtx);
|
|
1031
|
-
const message =
|
|
1036
|
+
const message = structuralFindings.map((f) => f.message).join("\n");
|
|
1032
1037
|
throw new UsageError(`Distilled ${effectiveProposalKind} failed validation:\n${message}`, "MISSING_REQUIRED_ARGUMENT", effectiveProposalKind === "knowledge"
|
|
1033
1038
|
? "Knowledge proposals require a non-empty markdown body."
|
|
1034
1039
|
: "Lessons require non-empty `description` and `when_to_use` frontmatter fields. See v1 spec §13.");
|
|
1035
1040
|
}
|
|
1041
|
+
if (qualityFindings.length > 0) {
|
|
1042
|
+
return {
|
|
1043
|
+
rejection: writeQualityRejection(stash, inputRef, effectiveLessonRef, content, 2.0, // below auto-accept threshold, signals review needed — no judge score exists for a structural/heuristic finding
|
|
1044
|
+
qualityFindings.map((f) => f.message).join("\n"), {
|
|
1045
|
+
reviewNeeded: true,
|
|
1046
|
+
proposalKind: effectiveProposalKind,
|
|
1047
|
+
findingKinds: qualityFindings.map((f) => f.kind),
|
|
1048
|
+
...(exclusionSet.size > 0 ? { filteredFeedbackCount } : {}),
|
|
1049
|
+
}, eligMeta.eligibilitySource, eventsCtx),
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1036
1052
|
return { content, descriptionSwapped };
|
|
1037
1053
|
}
|
|
1038
1054
|
/**
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { writeFileAtomic } from "../../core/common.js";
|
|
7
|
+
import { getEvalCasesDir } from "../../core/paths.js";
|
|
7
8
|
import { recordWrittenPath } from "../../core/write-provenance.js";
|
|
8
9
|
export function writeEvalCase(stashDir, evalCase) {
|
|
9
|
-
const evalDir =
|
|
10
|
+
const evalDir = getEvalCasesDir(stashDir);
|
|
10
11
|
fs.mkdirSync(evalDir, { recursive: true });
|
|
11
12
|
const fileName = `${evalCase.slug}.md`;
|
|
12
13
|
const filePath = path.join(evalDir, fileName);
|
|
@@ -29,11 +30,17 @@ Use it as a regression test: future improve runs on this ref should not produce
|
|
|
29
30
|
output that would be rejected for the same reason.
|
|
30
31
|
`;
|
|
31
32
|
writeFileAtomic(filePath, content);
|
|
33
|
+
// itlackey/akm#890: journal it even though it now lands under `$STATE`,
|
|
34
|
+
// outside the stash's git repo — `result.writtenPaths` still reports it
|
|
35
|
+
// (as an absolute path; see describeRunWrittenPaths in improve.ts), and
|
|
36
|
+
// the auto-sync commit's own containment check already excludes anything
|
|
37
|
+
// outside the stash from what gets staged, so recording it here cannot
|
|
38
|
+
// cause it to be committed.
|
|
32
39
|
recordWrittenPath(filePath);
|
|
33
40
|
return filePath;
|
|
34
41
|
}
|
|
35
42
|
export function countEvalCases(stashDir) {
|
|
36
|
-
const evalDir =
|
|
43
|
+
const evalDir = getEvalCasesDir(stashDir);
|
|
37
44
|
if (!fs.existsSync(evalDir))
|
|
38
45
|
return 0;
|
|
39
46
|
try {
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { deepMergeConfig } from "../../core/config/deep-merge.js";
|
|
5
|
-
import { ConfigError } from "../../core/errors.js";
|
|
6
5
|
import { lowerResolvedExecutionRequest } from "../../integrations/agent/execution-lowering.js";
|
|
7
6
|
import { prepareInlineExecution } from "../../integrations/agent/inline-execution.js";
|
|
8
7
|
function own(value, key) {
|
|
@@ -51,7 +50,7 @@ export function resolveImproveLlmExecution(options) {
|
|
|
51
50
|
if (!resolved)
|
|
52
51
|
return null;
|
|
53
52
|
if (resolved.runner.kind !== "llm") {
|
|
54
|
-
|
|
53
|
+
return null;
|
|
55
54
|
}
|
|
56
55
|
return { runner: resolved.runner, notices: resolved.notices };
|
|
57
56
|
}
|
|
@@ -31,6 +31,7 @@ import { getImproveProcessConfig, loadConfig } from "../../core/config/config.js
|
|
|
31
31
|
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
32
32
|
import { appendEvent } from "../../core/events.js";
|
|
33
33
|
import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync, } from "../../core/file-lock.js";
|
|
34
|
+
import { EXTRACT_INFRASTRUCTURE_SKIP_REASONS } from "../../core/improve-types.js";
|
|
34
35
|
import { tryAcquireMaintenanceBarrier } from "../../core/maintenance-barrier.js";
|
|
35
36
|
import { redactErrorBody } from "../../core/redaction.js";
|
|
36
37
|
import { resolveStashStandards } from "../../core/standards/resolve-stash-standards.js";
|
|
@@ -214,6 +215,24 @@ export function resolveStandaloneExtractPlan(config, selection) {
|
|
|
214
215
|
});
|
|
215
216
|
}
|
|
216
217
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
218
|
+
/** An extract envelope for a run that processed no sessions; `engine`/`engineKind` only once a runner is resolved. */
|
|
219
|
+
function emptyExtractResult(args) {
|
|
220
|
+
return {
|
|
221
|
+
schemaVersion: 1,
|
|
222
|
+
ok: args.ok,
|
|
223
|
+
shape: "extract-result",
|
|
224
|
+
dryRun: args.dryRun,
|
|
225
|
+
type: args.type,
|
|
226
|
+
sessionsProcessed: 0,
|
|
227
|
+
sessionsSkipped: 0,
|
|
228
|
+
candidatesCreated: 0,
|
|
229
|
+
proposals: [],
|
|
230
|
+
sessions: [],
|
|
231
|
+
warnings: [args.warning],
|
|
232
|
+
durationMs: Date.now() - args.startMs,
|
|
233
|
+
...(args.llmRunner ? { engine: args.llmRunner.engine, engineKind: args.llmRunner.kind } : {}),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
217
236
|
/**
|
|
218
237
|
* Parse a since-string into an absolute ms-epoch cutoff. Accepts:
|
|
219
238
|
* - ISO timestamps (parsed via Date.parse)
|
|
@@ -853,6 +872,7 @@ function recordExtractSessionOutcome(args) {
|
|
|
853
872
|
preFilterInputCount: result.preFilter.inputCount,
|
|
854
873
|
preFilterOutputCount: result.preFilter.outputCount,
|
|
855
874
|
preFilterTruncatedCount: result.preFilter.truncatedCount,
|
|
875
|
+
engine: result.engine,
|
|
856
876
|
...(result.skipReason ? { skipReason: result.skipReason } : {}),
|
|
857
877
|
...(result.sessionLogPath ? { logPath: result.sessionLogPath } : {}),
|
|
858
878
|
...(result.sessionAssetRef ? { sessionAssetRef: result.sessionAssetRef } : {}),
|
|
@@ -864,7 +884,8 @@ function recordExtractSessionOutcome(args) {
|
|
|
864
884
|
}
|
|
865
885
|
}
|
|
866
886
|
function accountExtractSessionResult(result, triageEnabled, output) {
|
|
867
|
-
output.
|
|
887
|
+
const stamped = { ...result, engine: output.engine };
|
|
888
|
+
output.sessions.push(stamped);
|
|
868
889
|
if (triageEnabled) {
|
|
869
890
|
const preempted = result.skipReason === "read_failed" ||
|
|
870
891
|
result.skipReason === "too_short" ||
|
|
@@ -883,6 +904,7 @@ function accountExtractSessionResult(result, triageEnabled, output) {
|
|
|
883
904
|
else
|
|
884
905
|
output.processedCount += 1;
|
|
885
906
|
output.allProposalIds.push(...result.proposalIds);
|
|
907
|
+
return stamped;
|
|
886
908
|
}
|
|
887
909
|
/**
|
|
888
910
|
* Iterate the discovered candidate sessions: enforce the per-run cap, take the
|
|
@@ -913,6 +935,7 @@ async function runExtractSessionLoop(args) {
|
|
|
913
935
|
standardsContext: extractStandardsContext,
|
|
914
936
|
};
|
|
915
937
|
const output = {
|
|
938
|
+
engine: llmRunner.engine,
|
|
916
939
|
sessions: [],
|
|
917
940
|
processedCount: 0,
|
|
918
941
|
skippedCount: 0,
|
|
@@ -947,14 +970,14 @@ async function runExtractSessionLoop(args) {
|
|
|
947
970
|
break;
|
|
948
971
|
const { summary } = plan;
|
|
949
972
|
if (plan.kind === "skip") {
|
|
950
|
-
accountExtractSessionResult(plan.result, triage.enabled, output);
|
|
973
|
+
const accounted = accountExtractSessionResult(plan.result, triage.enabled, output);
|
|
951
974
|
recordExtractSessionOutcome({
|
|
952
975
|
stateDb,
|
|
953
976
|
trackingEnabled,
|
|
954
977
|
dryRun,
|
|
955
978
|
harness: harness.name,
|
|
956
979
|
summary,
|
|
957
|
-
result:
|
|
980
|
+
result: accounted,
|
|
958
981
|
sourceRun,
|
|
959
982
|
});
|
|
960
983
|
continue;
|
|
@@ -988,14 +1011,14 @@ async function runExtractSessionLoop(args) {
|
|
|
988
1011
|
triage,
|
|
989
1012
|
});
|
|
990
1013
|
if ("skip" in executionGate) {
|
|
991
|
-
accountExtractSessionResult(executionGate.skip, triage.enabled, output);
|
|
1014
|
+
const accounted = accountExtractSessionResult(executionGate.skip, triage.enabled, output);
|
|
992
1015
|
recordExtractSessionOutcome({
|
|
993
1016
|
stateDb,
|
|
994
1017
|
trackingEnabled,
|
|
995
1018
|
dryRun,
|
|
996
1019
|
harness: harness.name,
|
|
997
1020
|
summary,
|
|
998
|
-
result:
|
|
1021
|
+
result: accounted,
|
|
999
1022
|
sourceRun,
|
|
1000
1023
|
});
|
|
1001
1024
|
refillModelSlot();
|
|
@@ -1009,14 +1032,14 @@ async function runExtractSessionLoop(args) {
|
|
|
1009
1032
|
for (const warning of result.warnings)
|
|
1010
1033
|
topLevelWarnings.push(`session ${summary.sessionId}: ${warning}`);
|
|
1011
1034
|
}
|
|
1012
|
-
accountExtractSessionResult(result, triage.enabled, output);
|
|
1035
|
+
const accounted = accountExtractSessionResult(result, triage.enabled, output);
|
|
1013
1036
|
recordExtractSessionOutcome({
|
|
1014
1037
|
stateDb,
|
|
1015
1038
|
trackingEnabled,
|
|
1016
1039
|
dryRun,
|
|
1017
1040
|
harness: harness.name,
|
|
1018
1041
|
summary,
|
|
1019
|
-
result,
|
|
1042
|
+
result: accounted,
|
|
1020
1043
|
sourceRun,
|
|
1021
1044
|
});
|
|
1022
1045
|
}
|
|
@@ -1098,9 +1121,11 @@ function resolveExtractRunConfig(options, config, extractProcess, activeProfile)
|
|
|
1098
1121
|
// Cap on NEW sessions LLM-processed per run; 0 disables. Absent = default.
|
|
1099
1122
|
// Bounds per-run wall time / LLM cost so a backlog can't push a run past its
|
|
1100
1123
|
// task timeout — the overflow stays unseen and is picked up by later runs.
|
|
1101
|
-
const maxSessionsPerRun =
|
|
1102
|
-
?
|
|
1103
|
-
:
|
|
1124
|
+
const maxSessionsPerRun = options.since
|
|
1125
|
+
? 0
|
|
1126
|
+
: typeof extractProcess?.maxSessionsPerRun === "number"
|
|
1127
|
+
? extractProcess.maxSessionsPerRun
|
|
1128
|
+
: DEFAULT_MAX_SESSIONS_PER_RUN;
|
|
1104
1129
|
// Default discovery window — process config can override the built-in 24h.
|
|
1105
1130
|
const effectiveSince = options.since ?? extractProcess?.defaultSince;
|
|
1106
1131
|
// #626 — resolve the triage gate config once per run. Default-off → the
|
|
@@ -1166,7 +1191,7 @@ function resolveExtractRunConfig(options, config, extractProcess, activeProfile)
|
|
|
1166
1191
|
* not-found envelope) or the discovery-window listing. Extracted verbatim from
|
|
1167
1192
|
* `akmExtract`; the 48h default-since floor and location filter are unchanged.
|
|
1168
1193
|
*/
|
|
1169
|
-
function discoverExtractCandidates(options, harness, effectiveSince, startMs, dryRun) {
|
|
1194
|
+
function discoverExtractCandidates(options, harness, effectiveSince, startMs, dryRun, llmRunner) {
|
|
1170
1195
|
if (options.sessionId) {
|
|
1171
1196
|
const all = harness.listSessions({
|
|
1172
1197
|
...(options.location ? { location: options.location } : {}),
|
|
@@ -1174,20 +1199,14 @@ function discoverExtractCandidates(options, harness, effectiveSince, startMs, dr
|
|
|
1174
1199
|
const target = all.find((s) => s.sessionId === options.sessionId);
|
|
1175
1200
|
if (!target) {
|
|
1176
1201
|
return {
|
|
1177
|
-
notFound: {
|
|
1178
|
-
schemaVersion: 1,
|
|
1202
|
+
notFound: emptyExtractResult({
|
|
1179
1203
|
ok: false,
|
|
1180
|
-
shape: "extract-result",
|
|
1181
1204
|
dryRun,
|
|
1182
1205
|
type: options.type,
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
sessions: [],
|
|
1188
|
-
warnings: [`session ${options.sessionId} not found for harness ${options.type}`],
|
|
1189
|
-
durationMs: Date.now() - startMs,
|
|
1190
|
-
},
|
|
1206
|
+
warning: `session ${options.sessionId} not found for harness ${options.type}`,
|
|
1207
|
+
startMs,
|
|
1208
|
+
llmRunner,
|
|
1209
|
+
}),
|
|
1191
1210
|
};
|
|
1192
1211
|
}
|
|
1193
1212
|
return { candidates: [target] };
|
|
@@ -1286,6 +1305,28 @@ function emitExtractTriageEvent(args) {
|
|
|
1286
1305
|
},
|
|
1287
1306
|
}, eventsCtx);
|
|
1288
1307
|
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Count every session's `skipReason` (#912) and push one warning line per
|
|
1310
|
+
* infrastructure reason in {@link EXTRACT_INFRASTRUCTURE_SKIP_REASONS}.
|
|
1311
|
+
* `undefined` when nothing was skipped, so the envelope carries no key.
|
|
1312
|
+
*/
|
|
1313
|
+
function buildExtractSkipAggregate(sessions, engine, warnings) {
|
|
1314
|
+
const counts = {};
|
|
1315
|
+
for (const session of sessions) {
|
|
1316
|
+
if (!session.skipReason)
|
|
1317
|
+
continue;
|
|
1318
|
+
counts[session.skipReason] = (counts[session.skipReason] ?? 0) + 1;
|
|
1319
|
+
}
|
|
1320
|
+
if (Object.keys(counts).length === 0)
|
|
1321
|
+
return undefined;
|
|
1322
|
+
const total = sessions.length;
|
|
1323
|
+
for (const reason of EXTRACT_INFRASTRUCTURE_SKIP_REASONS) {
|
|
1324
|
+
const n = counts[reason];
|
|
1325
|
+
if (n)
|
|
1326
|
+
warnings.push(`${n} of ${total} sessions skipped: ${reason} (engine "${engine}")`);
|
|
1327
|
+
}
|
|
1328
|
+
return counts;
|
|
1329
|
+
}
|
|
1289
1330
|
export async function akmExtract(options) {
|
|
1290
1331
|
const startMs = Date.now();
|
|
1291
1332
|
if (!options.type || options.type.trim() === "") {
|
|
@@ -1310,20 +1351,13 @@ export async function akmExtract(options) {
|
|
|
1310
1351
|
(options.improveProfile ? resolveProcessEnabled("extract", options.improveProfile) : true);
|
|
1311
1352
|
// Feature-gate early so we get a clean "skipped because disabled" envelope.
|
|
1312
1353
|
if (!extractEnabled) {
|
|
1313
|
-
return {
|
|
1314
|
-
schemaVersion: 1,
|
|
1354
|
+
return emptyExtractResult({
|
|
1315
1355
|
ok: true,
|
|
1316
|
-
shape: "extract-result",
|
|
1317
1356
|
dryRun,
|
|
1318
1357
|
type: options.type,
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
proposals: [],
|
|
1323
|
-
sessions: [],
|
|
1324
|
-
warnings: ["extract is disabled by the selected improve strategy"],
|
|
1325
|
-
durationMs: Date.now() - startMs,
|
|
1326
|
-
};
|
|
1358
|
+
warning: "extract is disabled by the selected improve strategy",
|
|
1359
|
+
startMs,
|
|
1360
|
+
});
|
|
1327
1361
|
}
|
|
1328
1362
|
const { timeoutMs, llmRunner, onNotices, getNotices, maxTotalChars, minContentChars, maxSessionsPerRun, effectiveSince, triage, sessionIndexing, } = resolveExtractRunConfig(options, config, extractProcess, activeProfile);
|
|
1329
1363
|
// WI-9.10: construct this run's RunContext (extracted to
|
|
@@ -1331,39 +1365,27 @@ export async function akmExtract(options) {
|
|
|
1331
1365
|
const ctx = buildExtractRunContext({ options, config, stashDir, dryRun, sourceRun, llmRunner });
|
|
1332
1366
|
const harness = resolveHarness(options.type, options.harnesses);
|
|
1333
1367
|
if (!harness) {
|
|
1334
|
-
return {
|
|
1335
|
-
schemaVersion: 1,
|
|
1368
|
+
return emptyExtractResult({
|
|
1336
1369
|
ok: false,
|
|
1337
|
-
shape: "extract-result",
|
|
1338
1370
|
dryRun,
|
|
1339
1371
|
type: options.type,
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
sessions: [],
|
|
1345
|
-
warnings: [`no available harness matches type "${options.type}" (check that the platform is installed)`],
|
|
1346
|
-
durationMs: Date.now() - startMs,
|
|
1347
|
-
};
|
|
1372
|
+
warning: `no available harness matches type "${options.type}" (check that the platform is installed)`,
|
|
1373
|
+
startMs,
|
|
1374
|
+
llmRunner,
|
|
1375
|
+
});
|
|
1348
1376
|
}
|
|
1349
1377
|
if (!harness.isAvailable()) {
|
|
1350
|
-
return {
|
|
1351
|
-
schemaVersion: 1,
|
|
1378
|
+
return emptyExtractResult({
|
|
1352
1379
|
ok: false,
|
|
1353
|
-
shape: "extract-result",
|
|
1354
1380
|
dryRun,
|
|
1355
1381
|
type: options.type,
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
sessions: [],
|
|
1361
|
-
warnings: [`harness ${options.type} is registered but reports not-available (no session data on this machine)`],
|
|
1362
|
-
durationMs: Date.now() - startMs,
|
|
1363
|
-
};
|
|
1382
|
+
warning: `harness ${options.type} is registered but reports not-available (no session data on this machine)`,
|
|
1383
|
+
startMs,
|
|
1384
|
+
llmRunner,
|
|
1385
|
+
});
|
|
1364
1386
|
}
|
|
1365
1387
|
// Decide which sessions to process: explicit sessionId OR discovery via since.
|
|
1366
|
-
const discovery = discoverExtractCandidates(options, harness, effectiveSince, startMs, dryRun);
|
|
1388
|
+
const discovery = discoverExtractCandidates(options, harness, effectiveSince, startMs, dryRun, llmRunner);
|
|
1367
1389
|
if ("notFound" in discovery)
|
|
1368
1390
|
return discovery.notFound;
|
|
1369
1391
|
const candidates = discovery.candidates;
|
|
@@ -1449,6 +1471,7 @@ export async function akmExtract(options) {
|
|
|
1449
1471
|
if (loopResult.deferred > 0) {
|
|
1450
1472
|
topLevelWarnings.push(`Reached maxSessionsPerRun=${maxSessionsPerRun}; ${loopResult.deferred} session(s) deferred to a later run.`);
|
|
1451
1473
|
}
|
|
1474
|
+
const skipReasons = buildExtractSkipAggregate(sessions, llmRunner.engine, topLevelWarnings);
|
|
1452
1475
|
emitExtractTriageEvent({
|
|
1453
1476
|
modelPlanCount,
|
|
1454
1477
|
triageEnabled: triage.enabled,
|
|
@@ -1475,6 +1498,9 @@ export async function akmExtract(options) {
|
|
|
1475
1498
|
warnings: topLevelWarnings,
|
|
1476
1499
|
durationMs: Date.now() - startMs,
|
|
1477
1500
|
...(getNotices().length > 0 ? { notices: getNotices() } : {}),
|
|
1501
|
+
...(skipReasons ? { skipReasons } : {}),
|
|
1502
|
+
engine: llmRunner.engine,
|
|
1503
|
+
engineKind: llmRunner.kind,
|
|
1478
1504
|
};
|
|
1479
1505
|
}
|
|
1480
1506
|
/**
|
|
@@ -88,39 +88,56 @@ export function resolveImprovePlan(name, config, options = {}) {
|
|
|
88
88
|
}
|
|
89
89
|
function buildImprovePlan(strategy, config, options) {
|
|
90
90
|
const processes = {};
|
|
91
|
+
const engineUnavailable = [];
|
|
91
92
|
for (const processName of Object.keys(IMPROVE_PROCESS_ENGINE_CAPABILITIES)) {
|
|
92
|
-
const
|
|
93
|
-
const enabled =
|
|
93
|
+
const sourceProcessConfig = strategy.config.processes?.[processName] ?? {};
|
|
94
|
+
const enabled = sourceProcessConfig.enabled === true;
|
|
94
95
|
let runner = null;
|
|
95
96
|
let notices = [];
|
|
96
97
|
if (IMPROVE_PROCESS_ENGINE_CAPABILITIES[processName] !== "llm" || !enabled) {
|
|
97
|
-
processes[processName] = Object.freeze({ enabled, config:
|
|
98
|
+
processes[processName] = Object.freeze({ enabled, config: cloneAndFreeze(sourceProcessConfig), runner });
|
|
98
99
|
continue;
|
|
99
100
|
}
|
|
100
101
|
// Validation itself is structural and always runs. Only its optional repair
|
|
101
102
|
// step needs a model, so disabling repair must not create an LLM preflight.
|
|
102
|
-
|
|
103
|
+
const skipsRepairEngine = processName === "validation" && options.repairValidationFailures === false;
|
|
104
|
+
if (!skipsRepairEngine) {
|
|
103
105
|
const resolved = resolveImproveLlmExecution({
|
|
104
106
|
config,
|
|
105
107
|
profile: strategy.config,
|
|
106
|
-
process:
|
|
108
|
+
process: sourceProcessConfig,
|
|
107
109
|
processName,
|
|
108
110
|
});
|
|
109
111
|
runner = resolved?.runner ?? null;
|
|
110
112
|
notices = resolved?.notices ?? [];
|
|
111
113
|
}
|
|
112
|
-
if (!runner && !
|
|
113
|
-
|
|
114
|
+
if (!runner && !skipsRepairEngine) {
|
|
115
|
+
const configKey = `improve.strategies.${strategy.name}.processes.${processName}.engine`;
|
|
116
|
+
engineUnavailable.push({
|
|
117
|
+
process: processName,
|
|
118
|
+
configKey,
|
|
119
|
+
reason: `requires an LLM engine that is not configured. Set defaults.llmEngine or ${configKey}`,
|
|
120
|
+
});
|
|
121
|
+
processes[processName] = Object.freeze({
|
|
122
|
+
enabled: false,
|
|
123
|
+
config: cloneAndFreeze({ ...sourceProcessConfig, enabled: false }),
|
|
124
|
+
runner: null,
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
114
127
|
}
|
|
115
128
|
if (runner)
|
|
116
129
|
runner = cloneAndFreeze(runner);
|
|
117
130
|
processes[processName] = Object.freeze({
|
|
118
131
|
enabled,
|
|
119
|
-
config:
|
|
132
|
+
config: cloneAndFreeze(sourceProcessConfig),
|
|
120
133
|
runner,
|
|
121
134
|
...(notices.length > 0 ? { notices: cloneAndFreeze(notices) } : {}),
|
|
122
135
|
});
|
|
123
136
|
}
|
|
137
|
+
if (engineUnavailable.length > 0 && !Object.values(processes).some((process) => process.enabled)) {
|
|
138
|
+
const names = engineUnavailable.map((item) => `"${item.process}"`).join(", ");
|
|
139
|
+
throw new ConfigError(`No improve process can run: ${names} ${engineUnavailable.length === 1 ? "requires" : "require"} an LLM engine that is not configured. Set defaults.llmEngine, or the per-process engine key named for each.`, "LLM_NOT_CONFIGURED");
|
|
140
|
+
}
|
|
124
141
|
const triage = strategy.config.processes?.triage;
|
|
125
142
|
const judgmentEnabled = triage?.judgment?.enabled === true;
|
|
126
143
|
const triageJudgmentResolution = processes.triage.enabled && judgmentEnabled
|
|
@@ -156,5 +173,6 @@ function buildImprovePlan(strategy, config, options) {
|
|
|
156
173
|
...(triageJudgmentResolution?.notices.length
|
|
157
174
|
? { triageJudgmentNotices: cloneAndFreeze(triageJudgmentResolution.notices) }
|
|
158
175
|
: {}),
|
|
176
|
+
engineUnavailable: Object.freeze(engineUnavailable),
|
|
159
177
|
});
|
|
160
178
|
}
|