akm-cli 0.9.1 → 0.9.2-alpha.2
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 +103 -28
- package/README.md +3 -1
- package/SECURITY.md +1 -1
- package/STABILITY.md +1 -1
- package/dist/akm +2 -2
- package/dist/akm-migrate +2 -2
- package/dist/assets/hints/cli-hints-full.md +14 -9
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/models.json +35 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
- package/dist/assets/tasks/core/extract.yml +6 -5
- package/dist/assets/tasks/core/improve.yml +6 -5
- package/dist/assets/tasks/core/index-refresh.yml +6 -5
- package/dist/assets/tasks/core/sync.yml +6 -5
- package/dist/assets/tasks/core/version-check.yml +6 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
- package/dist/cli/confirm.js +2 -2
- package/dist/cli/parse-args.js +3 -24
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli.js +11 -9
- package/dist/commands/agent/agent-dispatch.js +55 -89
- package/dist/commands/agent/contribute-cli.js +12 -45
- package/dist/commands/command/builtin-action.js +32 -0
- package/dist/commands/command/command-cli.js +99 -0
- package/dist/commands/command/command-execution.js +308 -0
- package/dist/commands/command/execution-source-loader.js +176 -0
- package/dist/commands/command/portable-template.js +60 -0
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env.js +4 -2
- package/dist/commands/feedback-cli.js +1 -1
- package/dist/commands/health/checks.js +241 -29
- package/dist/commands/health/html-report.js +0 -14
- package/dist/commands/health/report-view-model.js +0 -1
- package/dist/commands/health/surfaces.js +6 -7
- package/dist/commands/health/types.js +0 -2
- package/dist/commands/health.js +63 -18
- package/dist/commands/improve/collapse-detector.js +5 -6
- package/dist/commands/improve/consolidate.js +251 -214
- package/dist/commands/improve/distill/promote-memory.js +71 -34
- package/dist/commands/improve/distill/quality-gate.js +17 -5
- package/dist/commands/improve/distill.js +232 -155
- package/dist/commands/improve/eligibility.js +112 -79
- package/dist/commands/improve/execution.js +57 -0
- package/dist/commands/improve/extract-cli.js +5 -5
- package/dist/commands/improve/extract-prompt.js +64 -22
- package/dist/commands/improve/extract.js +608 -360
- package/dist/commands/improve/improve-strategies.js +43 -14
- package/dist/commands/improve/improve.js +249 -29
- package/dist/commands/improve/loop-stages.js +11 -17
- package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
- package/dist/commands/improve/outcome-loop.js +22 -38
- package/dist/commands/improve/planner.js +134 -0
- package/dist/commands/improve/preparation.js +730 -409
- package/dist/commands/improve/reflect.js +386 -223
- package/dist/commands/improve/run-context.js +3 -4
- package/dist/commands/improve/salience.js +6 -58
- package/dist/commands/improve/session-asset.js +12 -12
- package/dist/commands/lint/index.js +101 -29
- package/dist/commands/migrate-cli.js +11 -69
- package/dist/commands/migration-tool.js +6 -9
- package/dist/commands/models-cli.js +27 -0
- package/dist/commands/proposal/drain.js +258 -186
- package/dist/commands/proposal/proposal-cli.js +32 -10
- package/dist/commands/proposal/proposal.js +2 -5
- package/dist/commands/proposal/propose.js +192 -172
- package/dist/commands/proposal/repository.js +54 -91
- package/dist/commands/proposal/validators/proposal-validators.js +9 -7
- package/dist/commands/read/curate.js +53 -22
- package/dist/commands/read/registry-search.js +25 -9
- package/dist/commands/read/remember-cli.js +14 -2
- package/dist/commands/read/search.js +10 -4
- package/dist/commands/read/show.js +139 -153
- package/dist/commands/registry-cli.js +16 -7
- package/dist/commands/remember.js +33 -18
- package/dist/commands/sources/add-cli.js +19 -178
- package/dist/commands/sources/bundle-cli.js +15 -3
- package/dist/commands/sources/dangerous-env-audit.js +135 -0
- package/dist/commands/sources/info.js +2 -1
- package/dist/commands/sources/installed-stashes.js +901 -177
- package/dist/commands/sources/schema-repair.js +174 -95
- package/dist/commands/sources/self-update.js +30 -74
- package/dist/commands/sources/source-add.js +3 -5
- package/dist/commands/sources/sources-cli.js +2 -15
- package/dist/commands/sources/update-transaction.js +220 -0
- package/dist/commands/tasks/tasks-cli.js +3 -3
- package/dist/commands/tasks/tasks.js +736 -317
- package/dist/commands/workflow-cli.js +2 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
- package/dist/core/adapter/adapters/akm-adapter.js +85 -35
- package/dist/core/adapter/adapters/akm-lint.js +54 -39
- package/dist/core/adapter/adapters/akm-metadata.js +45 -45
- package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
- package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
- package/dist/core/adapter/adapters/index.js +0 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
- package/dist/core/adapter/adapters/okf-adapter.js +4 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
- package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
- package/dist/core/adapter/execution-source.js +308 -0
- package/dist/core/adapter/recognize-match.js +36 -13
- package/dist/core/adapter/registry.js +0 -9
- package/dist/core/asset/stash-meta.js +94 -4
- package/dist/core/common.js +6 -11
- package/dist/core/config/config-io.js +3 -3
- package/dist/core/config/config-schema.js +18 -40
- package/dist/core/config/config-sources.js +11 -21
- package/dist/core/config/config-walker.js +31 -13
- package/dist/core/config/config.js +23 -26
- package/dist/core/config/schema/engines.js +8 -7
- package/dist/core/config/schema/improve-processes.js +29 -5
- package/dist/core/config/schema/index-config.js +0 -27
- package/dist/core/config/schema/primitives.js +1 -23
- package/dist/core/config/schema/sources-bundles.js +13 -16
- package/dist/core/errors.js +2 -0
- package/dist/core/events.js +68 -32
- package/dist/core/extra-params.js +1 -0
- package/dist/core/improve-result.js +315 -0
- package/dist/core/lesson-lint.js +0 -6
- package/dist/core/maintenance-barrier.js +4 -4
- package/dist/core/network-policy.js +152 -0
- package/dist/core/paths.js +1 -1
- package/dist/core/recognition-util.js +4 -4
- package/dist/core/registry-url.js +456 -0
- package/dist/core/state/migrations.js +161 -47
- package/dist/core/state-db.js +453 -80
- package/dist/core/system-error.js +32 -0
- package/dist/core/time.js +2 -12
- package/dist/core/write-source.js +0 -18
- package/dist/execution/directory-identity.js +52 -0
- package/dist/execution/executable-identity.js +107 -0
- package/dist/execution/guarded-source.js +398 -0
- package/dist/execution/json.js +95 -0
- package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
- package/dist/execution/record.js +55 -0
- package/dist/execution/resolved-request.js +730 -0
- package/dist/execution/source.js +320 -0
- package/dist/indexer/bundle-identity-guard.js +5 -4
- package/dist/indexer/db/graph-db.js +33 -0
- package/dist/indexer/graph/graph-boost.js +3 -4
- package/dist/indexer/graph/graph-extraction.js +562 -373
- package/dist/indexer/index-written-assets.js +78 -39
- package/dist/indexer/indexer.js +471 -432
- package/dist/indexer/installations.js +6 -0
- package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
- package/dist/indexer/materialize-embeddings.js +155 -0
- package/dist/indexer/passes/memory-inference.js +227 -174
- package/dist/indexer/passes/metadata.js +263 -118
- package/dist/indexer/scan/doc-to-entry.js +7 -10
- package/dist/indexer/scan/drain-dir.js +51 -23
- package/dist/indexer/search/db-search.js +156 -50
- package/dist/indexer/search/fts-query.js +40 -40
- package/dist/indexer/search/ranking.js +36 -1
- package/dist/indexer/search/search-attribution.js +3 -1
- package/dist/indexer/search/search-fields.js +23 -14
- package/dist/indexer/search/search-hit-enrichers.js +1 -1
- package/dist/indexer/search/search-source.js +7 -16
- package/dist/indexer/search/semantic-status.js +10 -1
- package/dist/indexer/usage/show-usage.js +105 -0
- package/dist/indexer/usage/usage-events.js +7 -2
- package/dist/indexer/walk/matchers.js +40 -10
- package/dist/indexer/walk/path-resolver.js +5 -2
- package/dist/indexer/walk/walker.js +20 -2
- package/dist/integrations/agent/builder-shared.js +3 -6
- package/dist/integrations/agent/conversation-fallback.js +16 -0
- package/dist/integrations/agent/engine-resolution.js +87 -87
- package/dist/integrations/agent/execution-cascade.js +566 -0
- package/dist/integrations/agent/execution-definitions.js +211 -0
- package/dist/integrations/agent/execution-lowering.js +811 -0
- package/dist/integrations/agent/execution-preparation.js +67 -0
- package/dist/integrations/agent/index.js +0 -2
- package/dist/integrations/agent/inline-execution.js +74 -0
- package/dist/integrations/agent/model-map.js +515 -0
- package/dist/integrations/agent/persona-fallback.js +30 -0
- package/dist/integrations/agent/request-lowering.js +186 -0
- package/dist/integrations/agent/runner-dispatch.js +230 -37
- package/dist/integrations/agent/runner.js +12 -83
- package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
- package/dist/integrations/harnesses/claude/index.js +1 -5
- package/dist/integrations/harnesses/claude/session-log.js +3 -33
- package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +4 -44
- package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
- package/dist/integrations/harnesses/opencode/index.js +0 -2
- package/dist/integrations/harnesses/opencode/session-log.js +14 -204
- package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
- package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/shared.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -3
- package/dist/integrations/lockfile.js +82 -79
- package/dist/integrations/session-logs/index.js +6 -17
- package/dist/integrations/session-logs/provider-base.js +1 -29
- package/dist/llm/client.js +10 -5
- package/dist/llm/embedder.js +6 -7
- package/dist/llm/embedders/local.js +37 -88
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +75 -50
- package/dist/llm/index-passes.js +43 -5
- package/dist/llm/memory-infer.js +8 -6
- package/dist/llm/metadata-enhance.js +5 -3
- package/dist/llm/structured-call.js +122 -25
- package/dist/output/format-exempt.js +1 -1
- package/dist/output/render-registry.js +0 -16
- package/dist/output/renderers.js +12 -7
- package/dist/output/shapes/curate.js +1 -0
- package/dist/output/shapes/helpers.js +10 -2
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/command-format.js +31 -33
- package/dist/output/text/health-format.js +1 -29
- package/dist/output/text/migrate.js +6 -56
- package/dist/output/text/proposal-format.js +16 -1
- package/dist/output/text/workflow-format.js +16 -0
- package/dist/registry/network.js +279 -0
- package/dist/registry/pinned-request-helper.js +247 -0
- package/dist/registry/pinned-transport.js +717 -0
- package/dist/registry/providers/skills-sh.js +18 -6
- package/dist/registry/providers/static-index.js +20 -7
- package/dist/registry/resolve.js +53 -28
- package/dist/scripts/akm-migrate-node.js +19334 -52269
- package/dist/scripts/akm-migrate.js +19270 -51612
- package/dist/setup/registry-stash-loader.js +64 -20
- package/dist/setup/semantic-assets.js +9 -34
- package/dist/setup/setup.js +12 -30
- package/dist/setup/source-identity.js +17 -0
- package/dist/setup/steps/sources.js +36 -15
- package/dist/setup/steps/tasks.js +39 -11
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/providers/npm.js +2 -2
- package/dist/sources/providers/provider-utils.js +4 -3
- package/dist/sources/providers/website.js +11 -7
- package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
- package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
- package/dist/sources/website-url.js +73 -0
- package/dist/storage/engines/sqlite-migrations.js +81 -26
- package/dist/storage/managed-db.js +27 -24
- package/dist/storage/repositories/events-repository.js +3 -0
- package/dist/storage/repositories/index-connection.js +42 -10
- package/dist/storage/repositories/index-entries-repository.js +203 -229
- package/dist/storage/repositories/index-entry-mapper.js +8 -12
- package/dist/storage/repositories/index-entry-schema.js +255 -0
- package/dist/storage/repositories/index-fts-repository.js +64 -71
- package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
- package/dist/storage/repositories/index-meta-repository.js +0 -11
- package/dist/storage/repositories/index-schema.js +74 -350
- package/dist/storage/repositories/index-utility-repository.js +12 -17
- package/dist/storage/repositories/index-vec-repository.js +56 -7
- package/dist/storage/repositories/proposals-repository.js +4 -127
- package/dist/storage/repositories/registry-cache.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +20 -40
- package/dist/storage/repositories/workflow-runs-repository.js +228 -129
- package/dist/storage/sqlite-read-snapshot.js +148 -0
- package/dist/tasks/backends/cron.js +170 -42
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +787 -202
- package/dist/tasks/backends/schtasks.js +282 -83
- package/dist/tasks/embedded.js +7 -7
- package/dist/tasks/frozen-script.js +50 -0
- package/dist/tasks/resolve-akm-bin.js +5 -1
- package/dist/tasks/runner.js +239 -251
- package/dist/tasks/runtime-v3.js +281 -0
- package/dist/tasks/scheduler-binding.js +272 -0
- package/dist/tasks/scheduler-invocation.js +57 -43
- package/dist/tasks/scheduler-sync.js +654 -0
- package/dist/tasks/source-v3.js +752 -0
- package/dist/tasks/standalone-script-entry.js +5 -0
- package/dist/tasks/task-id.js +29 -0
- package/dist/workflows/authoring/authoring.js +15 -32
- package/dist/workflows/exec/dispatch-redaction.js +14 -8
- package/dist/workflows/exec/exec-unit.js +7 -28
- package/dist/workflows/exec/frozen-judge.js +57 -89
- package/dist/workflows/exec/lowering-notices.js +23 -0
- package/dist/workflows/exec/native-executor.js +301 -458
- package/dist/workflows/exec/param-secrets.js +4 -3
- package/dist/workflows/exec/run-workflow.js +26 -32
- package/dist/workflows/exec/step-work.js +105 -109
- package/dist/workflows/exec/unit-dispatch.js +103 -27
- package/dist/workflows/exec/unit-writer.js +3 -3
- package/dist/workflows/exec/worktree.js +2 -2
- package/dist/workflows/ir/compile.js +86 -72
- package/dist/workflows/ir/environment-v4.js +328 -0
- package/dist/workflows/ir/freeze-v4.js +122 -0
- package/dist/workflows/ir/plan-hash.js +13 -7
- package/dist/workflows/ir/schema-v4.js +525 -0
- package/dist/workflows/ir/schema.js +25 -284
- package/dist/workflows/ir/source-freeze-v4.js +506 -0
- package/dist/workflows/parser.js +27 -24
- package/dist/workflows/program/schema.js +1 -2
- package/dist/workflows/renderer.js +42 -29
- package/dist/workflows/resource-limits.js +4 -5
- package/dist/workflows/runtime/agent-identity.js +11 -13
- package/dist/workflows/runtime/plan-classifier.js +8 -8
- package/dist/workflows/runtime/runs.js +27 -43
- package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
- package/dist/workflows/source-files.js +373 -0
- package/dist/workflows/source-ir/compile.js +196 -0
- package/dist/workflows/source-ir/github-yaml.js +577 -0
- package/dist/workflows/source-ir/ordering.js +38 -0
- package/dist/workflows/source-ir/program.js +50 -0
- package/dist/workflows/source-ir/result.js +26 -0
- package/dist/workflows/source-ir/schema.js +772 -0
- package/dist/workflows/source-ir/semantics.js +242 -0
- package/dist/workflows/source-ir/uses.js +14 -0
- package/docs/README.md +2 -0
- package/docs/migration/README.md +3 -1
- package/docs/migration/release-notes/0.9.2.md +55 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/migration/v0.8-to-v0.9.md +76 -1077
- package/docs/migration/v0.9.0-troubleshooting.md +104 -516
- package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +230 -98
- package/docs/reference/configuration.md +159 -36
- package/docs/reference/data-and-telemetry.md +19 -1
- package/docs/reference/supported-formats.md +23 -3
- package/docs/reference/tasks.md +182 -0
- package/docs/reference/workflow-schema.md +91 -40
- package/docs/reference/workflows.md +33 -6
- package/package.json +10 -6
- package/schemas/akm-config.json +372 -224
- package/schemas/akm-task.json +324 -80
- package/schemas/akm-workflow.json +6 -9
- package/dist/core/migration-operation.js +0 -75
- package/dist/integrations/agent/model-aliases.js +0 -74
- package/dist/tasks/parser.js +0 -380
- package/dist/tasks/schema.js +0 -123
- package/dist/tasks/validator.js +0 -80
- package/dist/workflows/ir/freeze.js +0 -320
- package/dist/workflows/runtime/document-cache.js +0 -13
|
@@ -10,12 +10,13 @@ import { NotFoundError, rethrowIfTestIsolationError, UsageError } from "../../co
|
|
|
10
10
|
import { readEvents } from "../../core/events.js";
|
|
11
11
|
import { isPathAbsent } from "../../core/path-access.js";
|
|
12
12
|
import { getDbPath } from "../../core/paths.js";
|
|
13
|
-
import { deriveInstallations } from "../../indexer/installations.js";
|
|
14
|
-
import {
|
|
13
|
+
import { deriveInstallations, deriveWritableBundleIds } from "../../indexer/installations.js";
|
|
14
|
+
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
15
15
|
import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
|
|
16
16
|
import { closeDatabase, openExistingDatabase, openReadonlyExistingDatabase, } from "../../storage/repositories/index-connection.js";
|
|
17
17
|
import { getAllEntries } from "../../storage/repositories/index-entries-repository.js";
|
|
18
18
|
import { getUtilityScoresByIds } from "../../storage/repositories/index-utility-repository.js";
|
|
19
|
+
import { SqliteReadSnapshotUnavailableError } from "../../storage/sqlite-read-snapshot.js";
|
|
19
20
|
import { isDistillRefusedInputType } from "./distill.js";
|
|
20
21
|
import { isStrategyFilteredForAllPasses } from "./improve-strategies.js";
|
|
21
22
|
import { parseMemoryRef } from "./memory/derived-ref.js";
|
|
@@ -36,9 +37,37 @@ import { improveStateReadRefs } from "./source-identity.js";
|
|
|
36
37
|
*/
|
|
37
38
|
function openEligibilityDb(readOnly) {
|
|
38
39
|
if (readOnly)
|
|
39
|
-
return openReadonlyExistingDatabase();
|
|
40
|
+
return openReadonlyExistingDatabase(undefined, { isolatedSnapshot: true });
|
|
40
41
|
return isPathAbsent(getDbPath()) ? undefined : openExistingDatabase();
|
|
41
42
|
}
|
|
43
|
+
function describeIndexSnapshot(readOnly, status) {
|
|
44
|
+
if (status === "ready") {
|
|
45
|
+
return {
|
|
46
|
+
status,
|
|
47
|
+
reason: readOnly ? "loaded a non-mutating point-in-time copy of the existing index" : "loaded the prepared index",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (status === "missing") {
|
|
51
|
+
return {
|
|
52
|
+
status,
|
|
53
|
+
reason: readOnly
|
|
54
|
+
? "index.db is missing; dry-run uses an empty snapshot and does not create it"
|
|
55
|
+
: "index.db is missing after index preparation; the selector uses an empty snapshot",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
status,
|
|
60
|
+
reason: readOnly
|
|
61
|
+
? "index.db has no entries table; dry-run uses an empty snapshot and does not migrate it"
|
|
62
|
+
: "index.db has no entries table; the selector uses an empty snapshot",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function describeUnavailableSnapshot(error) {
|
|
66
|
+
return {
|
|
67
|
+
status: "incompatible",
|
|
68
|
+
reason: `index.db cannot provide a stable non-mutating snapshot (${error.message}); dry-run uses an empty snapshot`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
42
71
|
export function resolveImproveScope(scope) {
|
|
43
72
|
const trimmed = scope?.trim();
|
|
44
73
|
if (!trimmed)
|
|
@@ -95,17 +124,33 @@ export async function collectEligibleRefsReadOnly(scope, stashDir, improveProfil
|
|
|
95
124
|
}
|
|
96
125
|
async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, readOnly, configOverride) {
|
|
97
126
|
const config = configOverride ?? loadConfig();
|
|
127
|
+
let sources;
|
|
128
|
+
try {
|
|
129
|
+
sources = resolveSourceEntries(stashDir, config);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 }, strategyFilteredRefs: [] };
|
|
133
|
+
}
|
|
134
|
+
if (sources.length === 0) {
|
|
135
|
+
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 }, strategyFilteredRefs: [] };
|
|
136
|
+
}
|
|
137
|
+
const installations = deriveInstallations(sources);
|
|
138
|
+
const writableBundleIds = deriveWritableBundleIds(sources);
|
|
98
139
|
if (scope.mode === "ref" && scope.value) {
|
|
99
|
-
const writableDirs = new Set(getWritableStashDirs(stashDir, config).map((dir) => path.resolve(dir)));
|
|
100
140
|
let db;
|
|
101
141
|
try {
|
|
102
142
|
db = openEligibilityDb(readOnly);
|
|
103
143
|
if (!db) {
|
|
104
|
-
return {
|
|
144
|
+
return {
|
|
145
|
+
plannedRefs: [],
|
|
146
|
+
memorySummary: { eligible: 0, derived: 0 },
|
|
147
|
+
strategyFilteredRefs: [],
|
|
148
|
+
indexSnapshot: describeIndexSnapshot(readOnly, "missing"),
|
|
149
|
+
};
|
|
105
150
|
}
|
|
151
|
+
const indexSnapshot = describeIndexSnapshot(readOnly, "ready");
|
|
106
152
|
const entries = getAllEntries(db);
|
|
107
|
-
const entriesByItemRef = new Map(entries.
|
|
108
|
-
const installations = deriveInstallations(resolveSourceEntries(stashDir, config));
|
|
153
|
+
const entriesByItemRef = new Map(entries.map((entry) => [entry.itemRef, entry]));
|
|
109
154
|
const resolved = resolveRef(scope.value, {
|
|
110
155
|
defaultBundle: config.defaultBundle,
|
|
111
156
|
bundles: installations.map((installation) => ({
|
|
@@ -116,27 +161,46 @@ async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, rea
|
|
|
116
161
|
const indexed = entriesByItemRef.get(`${resolved.bundle}//${resolved.conceptId}`);
|
|
117
162
|
if (!indexed?.bundleId || !indexed.conceptId || !fs.existsSync(indexed.filePath)) {
|
|
118
163
|
if (await findAssetFilePath(scope.value, stashDir)) {
|
|
119
|
-
return {
|
|
164
|
+
return {
|
|
165
|
+
plannedRefs: [],
|
|
166
|
+
memorySummary: { eligible: 0, derived: 0 },
|
|
167
|
+
strategyFilteredRefs: [],
|
|
168
|
+
indexSnapshot,
|
|
169
|
+
};
|
|
120
170
|
}
|
|
121
171
|
throw new NotFoundError(`Asset not found in the selected writable source: ${scope.value}`, "ASSET_NOT_FOUND");
|
|
122
172
|
}
|
|
123
|
-
if (!
|
|
124
|
-
|
|
125
|
-
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 }, strategyFilteredRefs: [] };
|
|
173
|
+
if (!writableBundleIds.has(indexed.bundleId)) {
|
|
174
|
+
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 }, strategyFilteredRefs: [], indexSnapshot };
|
|
126
175
|
}
|
|
127
|
-
const itemRef = `${indexed.bundleId}//${indexed.conceptId}`;
|
|
128
176
|
return {
|
|
129
|
-
plannedRefs: [
|
|
177
|
+
plannedRefs: [
|
|
178
|
+
{ ref: indexed.conceptId, itemRef: indexed.itemRef, reason: "scope-ref", filePath: indexed.filePath },
|
|
179
|
+
],
|
|
130
180
|
memorySummary: {
|
|
131
181
|
eligible: indexed.entry.type === "memory" ? 1 : 0,
|
|
132
182
|
derived: indexed.entry.type === "memory" && indexed.entry.name.endsWith(".derived") ? 1 : 0,
|
|
133
183
|
},
|
|
134
184
|
strategyFilteredRefs: [],
|
|
185
|
+
indexSnapshot,
|
|
135
186
|
};
|
|
136
187
|
}
|
|
137
188
|
catch (error) {
|
|
189
|
+
if (readOnly && error instanceof SqliteReadSnapshotUnavailableError) {
|
|
190
|
+
return {
|
|
191
|
+
plannedRefs: [],
|
|
192
|
+
memorySummary: { eligible: 0, derived: 0 },
|
|
193
|
+
strategyFilteredRefs: [],
|
|
194
|
+
indexSnapshot: describeUnavailableSnapshot(error),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
138
197
|
if (error instanceof Error && /no such table:\s*entries/i.test(error.message)) {
|
|
139
|
-
return {
|
|
198
|
+
return {
|
|
199
|
+
plannedRefs: [],
|
|
200
|
+
memorySummary: { eligible: 0, derived: 0 },
|
|
201
|
+
strategyFilteredRefs: [],
|
|
202
|
+
indexSnapshot: describeIndexSnapshot(readOnly, "incompatible"),
|
|
203
|
+
};
|
|
140
204
|
}
|
|
141
205
|
throw error;
|
|
142
206
|
}
|
|
@@ -145,39 +209,19 @@ async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, rea
|
|
|
145
209
|
closeDatabase(db);
|
|
146
210
|
}
|
|
147
211
|
}
|
|
148
|
-
let sources;
|
|
149
|
-
try {
|
|
150
|
-
sources = resolveSourceEntries(stashDir, config);
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 }, strategyFilteredRefs: [] };
|
|
154
|
-
}
|
|
155
|
-
if (sources.length === 0) {
|
|
156
|
-
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 }, strategyFilteredRefs: [] };
|
|
157
|
-
}
|
|
158
|
-
// Only operate on writable sources — never mutate read-only registry caches
|
|
159
|
-
// or remote stashes that the user did not mark writable.
|
|
160
|
-
let writableDirs;
|
|
161
|
-
try {
|
|
162
|
-
writableDirs = getWritableStashDirs(stashDir, config);
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
writableDirs = sources.slice(0, 1).map((s) => s.path); // fallback: primary only
|
|
166
|
-
}
|
|
167
|
-
const writableDirSet = new Set(writableDirs.map((d) => path.resolve(d)));
|
|
168
212
|
let db;
|
|
169
213
|
try {
|
|
170
214
|
db = openEligibilityDb(readOnly);
|
|
171
215
|
if (!db) {
|
|
172
|
-
return {
|
|
216
|
+
return {
|
|
217
|
+
plannedRefs: [],
|
|
218
|
+
memorySummary: { eligible: 0, derived: 0 },
|
|
219
|
+
strategyFilteredRefs: [],
|
|
220
|
+
indexSnapshot: describeIndexSnapshot(readOnly, "missing"),
|
|
221
|
+
};
|
|
173
222
|
}
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
if (!isEntryInScope(indexed.stashDir, indexed.filePath, stashDir))
|
|
177
|
-
return false;
|
|
178
|
-
// Then restrict to writable sources only.
|
|
179
|
-
return isEntryInWritableSource(indexed.stashDir, indexed.filePath, writableDirSet);
|
|
180
|
-
});
|
|
223
|
+
const indexSnapshot = describeIndexSnapshot(readOnly, "ready");
|
|
224
|
+
const entries = getAllEntries(db, scope.mode === "type" ? scope.value : undefined).filter((indexed) => writableBundleIds.has(indexed.bundleId));
|
|
181
225
|
const planned = new Map();
|
|
182
226
|
const strategyFiltered = new Map();
|
|
183
227
|
let memoryEligible = 0;
|
|
@@ -198,7 +242,7 @@ async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, rea
|
|
|
198
242
|
}
|
|
199
243
|
// Derive the durable item_ref from indexed provenance without another query.
|
|
200
244
|
// A provenance-free row uses the short conceptId ref for improve state.
|
|
201
|
-
const itemRef = indexed.
|
|
245
|
+
const itemRef = indexed.itemRef;
|
|
202
246
|
const isDerived = indexed.entry.name.endsWith(".derived");
|
|
203
247
|
// `.derived` memories are LLM-inferred and intentionally skip reflect
|
|
204
248
|
// (see the synthetic `derived-memory-reflect-skipped` branch in the
|
|
@@ -242,13 +286,27 @@ async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, rea
|
|
|
242
286
|
plannedRefs: [...planned.values()],
|
|
243
287
|
memorySummary: { eligible: memoryEligible, derived: memoryDerived },
|
|
244
288
|
strategyFilteredRefs: [...strategyFiltered.values()],
|
|
289
|
+
indexSnapshot,
|
|
245
290
|
};
|
|
246
291
|
}
|
|
247
292
|
catch (error) {
|
|
248
293
|
// Empty-stash setup paths can open index.db before its schema exists.
|
|
249
294
|
rethrowIfTestIsolationError(error);
|
|
295
|
+
if (readOnly && error instanceof SqliteReadSnapshotUnavailableError) {
|
|
296
|
+
return {
|
|
297
|
+
plannedRefs: [],
|
|
298
|
+
memorySummary: { eligible: 0, derived: 0 },
|
|
299
|
+
strategyFilteredRefs: [],
|
|
300
|
+
indexSnapshot: describeUnavailableSnapshot(error),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
250
303
|
if (error instanceof Error && /no such table:\s*entries/i.test(error.message)) {
|
|
251
|
-
return {
|
|
304
|
+
return {
|
|
305
|
+
plannedRefs: [],
|
|
306
|
+
memorySummary: { eligible: 0, derived: 0 },
|
|
307
|
+
strategyFilteredRefs: [],
|
|
308
|
+
indexSnapshot: describeIndexSnapshot(readOnly, "incompatible"),
|
|
309
|
+
};
|
|
252
310
|
}
|
|
253
311
|
throw error;
|
|
254
312
|
}
|
|
@@ -257,33 +315,6 @@ async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, rea
|
|
|
257
315
|
closeDatabase(db);
|
|
258
316
|
}
|
|
259
317
|
}
|
|
260
|
-
export function isEntryInScope(entryStashDir, filePath, stashDir) {
|
|
261
|
-
if (!stashDir)
|
|
262
|
-
return true;
|
|
263
|
-
const resolvedEntryStashDir = path.resolve(entryStashDir);
|
|
264
|
-
const resolvedFilePath = path.resolve(filePath);
|
|
265
|
-
const resolvedScopeStashDir = path.resolve(stashDir);
|
|
266
|
-
return (resolvedEntryStashDir === resolvedScopeStashDir ||
|
|
267
|
-
resolvedEntryStashDir.startsWith(`${resolvedScopeStashDir}${path.sep}`) ||
|
|
268
|
-
resolvedFilePath.startsWith(`${resolvedScopeStashDir}${path.sep}`));
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Return true when the indexed entry belongs to one of the writable source
|
|
272
|
-
* directories. Entries from read-only registry caches or remote stashes that
|
|
273
|
-
* the user has not marked writable must never enter the improve/distill loop.
|
|
274
|
-
*/
|
|
275
|
-
export function isEntryInWritableSource(entryStashDir, filePath, writableDirSet) {
|
|
276
|
-
const resolvedEntryStashDir = path.resolve(entryStashDir);
|
|
277
|
-
const resolvedFilePath = path.resolve(filePath);
|
|
278
|
-
for (const writableDir of writableDirSet) {
|
|
279
|
-
if (resolvedEntryStashDir === writableDir ||
|
|
280
|
-
resolvedEntryStashDir.startsWith(`${writableDir}${path.sep}`) ||
|
|
281
|
-
resolvedFilePath.startsWith(`${writableDir}${path.sep}`)) {
|
|
282
|
-
return true;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
318
|
export function memoryCleanupParentRef(scope, stashDir) {
|
|
288
319
|
if (scope.mode !== "ref" || !scope.value)
|
|
289
320
|
return undefined;
|
|
@@ -381,13 +412,13 @@ export function shouldDistillMemoryRef(ref, stashDir) {
|
|
|
381
412
|
* metadata events are ignored so a stray `akm feedback <ref>` invocation
|
|
382
413
|
* without a flag doesn't trigger downstream re-processing.
|
|
383
414
|
*/
|
|
384
|
-
export function buildLatestFeedbackTsMap(refs, sinceIso, itemRefByRef) {
|
|
415
|
+
export function buildLatestFeedbackTsMap(refs, sinceIso, itemRefByRef, eventsCtx) {
|
|
385
416
|
const out = new Map();
|
|
386
417
|
if (refs.length === 0)
|
|
387
418
|
return out;
|
|
388
419
|
// Correlate item_ref-keyed events and conceptId-keyed direct invocations.
|
|
389
420
|
const refByDurableKey = new Map(refs.flatMap((ref) => improveStateReadRefs(ref, itemRefByRef?.get(ref)).map((key) => [key, ref])));
|
|
390
|
-
const { events } = readEvents({ type: "feedback", since: sinceIso });
|
|
421
|
+
const { events } = readEvents({ type: "feedback", since: sinceIso }, eventsCtx);
|
|
391
422
|
for (const e of events) {
|
|
392
423
|
const ref = e.ref ? refByDurableKey.get(e.ref) : undefined;
|
|
393
424
|
if (!ref)
|
|
@@ -411,14 +442,14 @@ export function buildLatestFeedbackTsMap(refs, sinceIso, itemRefByRef) {
|
|
|
411
442
|
* lesson/knowledge ref, not the source memory — joining back through the
|
|
412
443
|
* payload would be fragile.
|
|
413
444
|
*/
|
|
414
|
-
export function buildLatestProposalTsMap(refs, source, itemRefByRef) {
|
|
445
|
+
export function buildLatestProposalTsMap(refs, source, itemRefByRef, eventsCtx) {
|
|
415
446
|
const out = new Map();
|
|
416
447
|
if (refs.length === 0)
|
|
417
448
|
return out;
|
|
418
449
|
// Correlate item_ref-keyed events and conceptId-keyed direct invocations.
|
|
419
450
|
const refByDurableKey = new Map(refs.flatMap((ref) => improveStateReadRefs(ref, itemRefByRef?.get(ref)).map((key) => [key, ref])));
|
|
420
451
|
const eventType = source === "reflect" ? "reflect_invoked" : "distill_invoked";
|
|
421
|
-
const { events } = readEvents({ type: eventType });
|
|
452
|
+
const { events } = readEvents({ type: eventType }, eventsCtx);
|
|
422
453
|
for (const e of events) {
|
|
423
454
|
const ref = e.ref ? refByDurableKey.get(e.ref) : undefined;
|
|
424
455
|
if (!ref)
|
|
@@ -489,14 +520,16 @@ export function shouldAnalyzeMemoryCleanup(scope, eligibleMemories, primaryStash
|
|
|
489
520
|
return false;
|
|
490
521
|
return parseRefInput(scope.value).type === "memory";
|
|
491
522
|
}
|
|
492
|
-
export function buildUtilityMap(refs) {
|
|
523
|
+
export function buildUtilityMap(refs, readOnly = false) {
|
|
493
524
|
const map = new Map();
|
|
494
525
|
if (refs.length === 0)
|
|
495
526
|
return map;
|
|
496
527
|
const refSet = new Set(refs.map((r) => r.ref));
|
|
497
528
|
let db;
|
|
498
529
|
try {
|
|
499
|
-
db = openExistingDatabase();
|
|
530
|
+
db = readOnly ? openReadonlyExistingDatabase(undefined, { isolatedSnapshot: true }) : openExistingDatabase();
|
|
531
|
+
if (!db)
|
|
532
|
+
return map;
|
|
500
533
|
const allDbEntries = getAllEntries(db);
|
|
501
534
|
const idToRef = new Map();
|
|
502
535
|
for (const indexed of allDbEntries) {
|
|
@@ -526,11 +559,11 @@ export function buildUtilityMap(refs) {
|
|
|
526
559
|
}
|
|
527
560
|
return map;
|
|
528
561
|
}
|
|
529
|
-
export async function findAssetFilePath(ref, stashDir,
|
|
562
|
+
export async function findAssetFilePath(ref, stashDir, writableBundleIds) {
|
|
530
563
|
return resolveAssetPath(ref, {
|
|
531
564
|
stashDir,
|
|
532
565
|
mode: "disk-only",
|
|
533
|
-
|
|
566
|
+
writableBundleIds,
|
|
534
567
|
directoryIndexNames: ["SKILL.md"],
|
|
535
568
|
preserveDirectNameFallback: true,
|
|
536
569
|
honorOrigin: true,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { deepMergeConfig } from "../../core/config/deep-merge.js";
|
|
5
|
+
import { ConfigError } from "../../core/errors.js";
|
|
6
|
+
import { lowerResolvedExecutionRequest } from "../../integrations/agent/execution-lowering.js";
|
|
7
|
+
import { prepareInlineExecution } from "../../integrations/agent/inline-execution.js";
|
|
8
|
+
function own(value, key) {
|
|
9
|
+
return value !== undefined && Object.hasOwn(value, key);
|
|
10
|
+
}
|
|
11
|
+
function cascadeDefaults(layer) {
|
|
12
|
+
if (!layer)
|
|
13
|
+
return {};
|
|
14
|
+
return {
|
|
15
|
+
...(own(layer, "engine") ? { engine: layer.engine } : {}),
|
|
16
|
+
...(own(layer, "model") ? { model: layer.model } : {}),
|
|
17
|
+
...(own(layer, "llm") ? { inference: layer.llm } : {}),
|
|
18
|
+
...(own(layer, "timeoutMs") ? { timeout: layer.timeoutMs ?? null } : {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function mergeDefaults(farther, nearer) {
|
|
22
|
+
return deepMergeConfig(farther, nearer);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve improve-owned model work through the canonical execution cascade.
|
|
26
|
+
* The legacy improve precedence is preserved exactly:
|
|
27
|
+
* defaults.llmEngine -> strategy -> process -> current invocation.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveImproveExecution(options) {
|
|
30
|
+
const defaultEngine = options.config.defaults?.llmEngine;
|
|
31
|
+
const profileDefaults = cascadeDefaults(options.profile);
|
|
32
|
+
const processDefaults = cascadeDefaults(options.process);
|
|
33
|
+
const currentDefaults = cascadeDefaults(options.current);
|
|
34
|
+
const selectedEngine = currentDefaults.engine ?? processDefaults.engine ?? profileDefaults.engine ?? defaultEngine;
|
|
35
|
+
if (selectedEngine === undefined || selectedEngine === null)
|
|
36
|
+
return null;
|
|
37
|
+
const invocationDefaults = mergeDefaults(defaultEngine ? { engine: defaultEngine } : {}, profileDefaults);
|
|
38
|
+
const current = mergeDefaults(processDefaults, currentDefaults);
|
|
39
|
+
const prepared = prepareInlineExecution({
|
|
40
|
+
content: `improve ${options.processName} execution selection`,
|
|
41
|
+
config: options.config,
|
|
42
|
+
invocationKind: "direct",
|
|
43
|
+
invocationDefaults,
|
|
44
|
+
current,
|
|
45
|
+
});
|
|
46
|
+
const lowered = lowerResolvedExecutionRequest(prepared.request, prepared.config);
|
|
47
|
+
return Object.freeze({ runner: lowered.runner, notices: lowered.notices });
|
|
48
|
+
}
|
|
49
|
+
export function resolveImproveLlmExecution(options) {
|
|
50
|
+
const resolved = resolveImproveExecution(options);
|
|
51
|
+
if (!resolved)
|
|
52
|
+
return null;
|
|
53
|
+
if (resolved.runner.kind !== "llm") {
|
|
54
|
+
throw new ConfigError(`Engine ${JSON.stringify(resolved.runner.engine ?? "unknown")} is not an LLM engine.`, "INVALID_CONFIG_FILE");
|
|
55
|
+
}
|
|
56
|
+
return { runner: resolved.runner, notices: resolved.notices };
|
|
57
|
+
}
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* see src/commands/proposal/proposal-cli.ts for its registration).
|
|
8
8
|
*
|
|
9
9
|
* Examples:
|
|
10
|
-
* akm proposal extract --type claude
|
|
11
|
-
* akm proposal extract --type claude
|
|
10
|
+
* akm proposal extract --type claude --session-id <id>
|
|
11
|
+
* akm proposal extract --type claude --since 24h
|
|
12
12
|
* akm proposal extract --type opencode --since 7d --dry-run
|
|
13
13
|
* akm proposal extract --auto # iterate all available harnesses
|
|
14
|
-
* akm proposal extract --type claude
|
|
14
|
+
* akm proposal extract --type claude --location /custom/path --session-id <id>
|
|
15
15
|
*
|
|
16
16
|
* Output is the AkmExtractResult JSON envelope (or an aggregated one when
|
|
17
17
|
* `--auto` runs multiple harnesses).
|
|
@@ -25,12 +25,12 @@ import { akmExtract, resolveStandaloneExtractPlan } from "./extract.js";
|
|
|
25
25
|
export const extractCommand = defineJsonCommand({
|
|
26
26
|
meta: {
|
|
27
27
|
name: "extract",
|
|
28
|
-
description: "Extract durable insights from native session files (claude
|
|
28
|
+
description: "Extract durable insights from native session files (claude, opencode) and queue them as proposals.",
|
|
29
29
|
},
|
|
30
30
|
args: {
|
|
31
31
|
type: {
|
|
32
32
|
type: "string",
|
|
33
|
-
description: "Harness name (claude
|
|
33
|
+
description: "Harness name (claude, opencode). Required unless --auto.",
|
|
34
34
|
},
|
|
35
35
|
"session-id": {
|
|
36
36
|
type: "string",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* the model emits get dropped before we parse.
|
|
16
16
|
*/
|
|
17
17
|
import promptTemplate from "../../assets/prompts/extract-session.md" with { type: "text" };
|
|
18
|
+
import { escapeJsonStringControls, stripCodeFences, stripThinkBlocks } from "../../core/parse.js";
|
|
18
19
|
const EXTRACT_CANDIDATE_NAME_PATTERN = "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)?$";
|
|
19
20
|
const EXTRACT_CANDIDATE_NAME_RE = new RegExp(EXTRACT_CANDIDATE_NAME_PATTERN);
|
|
20
21
|
/**
|
|
@@ -163,6 +164,57 @@ export function buildExtractPrompt(input) {
|
|
|
163
164
|
.replace("{{STANDARDS}}", standards)
|
|
164
165
|
.replace("{{TRANSCRIPT}}", `${TRANSCRIPT_FENCE_BEGIN}\n${formatTranscript(input.events)}\n${TRANSCRIPT_FENCE_END}`);
|
|
165
166
|
}
|
|
167
|
+
function failedExtractPayload(code, message) {
|
|
168
|
+
return { candidates: [], rationale_if_empty: message, parseFailure: { code, message } };
|
|
169
|
+
}
|
|
170
|
+
function parseFirstJsonObject(stdout) {
|
|
171
|
+
const text = escapeJsonStringControls(stripCodeFences(stripThinkBlocks(stdout)));
|
|
172
|
+
try {
|
|
173
|
+
return { objectFound: text.startsWith("{"), value: JSON.parse(text) };
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Continue with the first object embedded in prose. Once an opening brace
|
|
177
|
+
// is found, never descend into a nested object if that outer object is
|
|
178
|
+
// malformed or truncated: doing so can turn a broken candidate into a
|
|
179
|
+
// superficially valid top-level payload.
|
|
180
|
+
}
|
|
181
|
+
const start = text.indexOf("{");
|
|
182
|
+
if (start < 0)
|
|
183
|
+
return { objectFound: false };
|
|
184
|
+
let depth = 0;
|
|
185
|
+
let inString = false;
|
|
186
|
+
let escaped = false;
|
|
187
|
+
for (let index = start; index < text.length; index++) {
|
|
188
|
+
const char = text[index];
|
|
189
|
+
if (inString) {
|
|
190
|
+
if (escaped)
|
|
191
|
+
escaped = false;
|
|
192
|
+
else if (char === "\\")
|
|
193
|
+
escaped = true;
|
|
194
|
+
else if (char === '"')
|
|
195
|
+
inString = false;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (char === '"') {
|
|
199
|
+
inString = true;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (char === "{")
|
|
203
|
+
depth += 1;
|
|
204
|
+
else if (char === "}") {
|
|
205
|
+
depth -= 1;
|
|
206
|
+
if (depth !== 0)
|
|
207
|
+
continue;
|
|
208
|
+
try {
|
|
209
|
+
return { objectFound: true, value: JSON.parse(text.slice(start, index + 1)) };
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return { objectFound: true };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { objectFound: true };
|
|
217
|
+
}
|
|
166
218
|
/**
|
|
167
219
|
* Parse the LLM's JSON response into a structured {@link ExtractPayload}.
|
|
168
220
|
* Defensive — drops candidates that violate the shape rather than failing
|
|
@@ -170,32 +222,22 @@ export function buildExtractPrompt(input) {
|
|
|
170
222
|
*/
|
|
171
223
|
export function parseExtractPayload(stdout) {
|
|
172
224
|
if (!stdout || stdout.trim().length === 0) {
|
|
173
|
-
return
|
|
225
|
+
return failedExtractPayload("empty_response", "LLM returned an empty response");
|
|
174
226
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
parsed
|
|
227
|
+
const parsed = parseFirstJsonObject(stdout);
|
|
228
|
+
if (parsed.value === undefined) {
|
|
229
|
+
return parsed.objectFound
|
|
230
|
+
? failedExtractPayload("invalid_json_object", "JSON object was found but could not be parsed")
|
|
231
|
+
: failedExtractPayload("no_json_object", "LLM response: no JSON object found");
|
|
178
232
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
// top-level JSON object.
|
|
182
|
-
const start = stdout.indexOf("{");
|
|
183
|
-
const end = stdout.lastIndexOf("}");
|
|
184
|
-
if (start === -1 || end <= start) {
|
|
185
|
-
return { candidates: [], rationale_if_empty: `LLM response was not parseable JSON` };
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
parsed = JSON.parse(stdout.slice(start, end + 1));
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
return { candidates: [], rationale_if_empty: `LLM response was not parseable JSON` };
|
|
192
|
-
}
|
|
233
|
+
if (!parsed.value || typeof parsed.value !== "object" || Array.isArray(parsed.value)) {
|
|
234
|
+
return failedExtractPayload("invalid_payload", "LLM response JSON was not an object");
|
|
193
235
|
}
|
|
194
|
-
|
|
195
|
-
|
|
236
|
+
const obj = parsed.value;
|
|
237
|
+
if (!Array.isArray(obj.candidates)) {
|
|
238
|
+
return failedExtractPayload("invalid_payload", "LLM response JSON did not contain a candidates array");
|
|
196
239
|
}
|
|
197
|
-
const
|
|
198
|
-
const rawCandidates = Array.isArray(obj.candidates) ? obj.candidates : [];
|
|
240
|
+
const rawCandidates = obj.candidates;
|
|
199
241
|
const candidates = [];
|
|
200
242
|
for (const raw of rawCandidates) {
|
|
201
243
|
if (!raw || typeof raw !== "object")
|