akm-cli 0.8.3 → 0.9.0-beta.1
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 +209 -0
- package/dist/assets/help/help-proposals.md +1 -2
- package/dist/assets/hints/cli-hints-full.md +34 -19
- package/dist/assets/hints/cli-hints-short.md +1 -1
- package/dist/assets/profiles/catchup.json +13 -0
- package/dist/assets/profiles/consolidate.json +13 -0
- package/dist/assets/profiles/frequent.json +13 -0
- package/dist/assets/tasks/core/backup.yml +4 -0
- package/dist/assets/tasks/core/extract.yml +4 -0
- package/dist/assets/tasks/core/improve.yml +4 -0
- package/dist/assets/tasks/core/index-refresh.yml +4 -0
- package/dist/assets/tasks/core/sync.yml +4 -0
- package/dist/assets/tasks/core/update-stashes.yml +4 -0
- package/dist/assets/tasks/core/version-check.yml +4 -0
- package/dist/cli/config-migrate.js +6 -6
- package/dist/cli/config-validate.js +4 -4
- package/dist/cli/confirm.js +3 -3
- package/dist/cli/parse-args.js +1 -1
- package/dist/cli/shared.js +51 -14
- package/dist/cli-node.mjs +26 -0
- package/dist/cli.js +171 -3862
- package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
- package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
- package/dist/commands/agent/contribute-cli.js +200 -0
- package/dist/commands/completions.js +1 -1
- package/dist/commands/config-cli.js +240 -3
- package/dist/commands/config-edit.js +344 -0
- package/dist/commands/db-cli.js +2 -2
- package/dist/commands/env/env-cli.js +529 -0
- package/dist/commands/env/env.js +410 -0
- package/dist/commands/env/secret-cli.js +259 -0
- package/dist/commands/{secret.js → env/secret.js} +6 -47
- package/dist/commands/events.js +4 -4
- package/dist/commands/feedback-cli.js +18 -34
- package/dist/commands/graph/graph-cli.js +132 -0
- package/dist/commands/{graph.js → graph/graph.js} +22 -16
- package/dist/commands/health/checks.js +279 -0
- package/dist/commands/health.js +94 -262
- package/dist/commands/{consolidate.js → improve/consolidate.js} +48 -36
- package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
- package/dist/commands/{distill.js → improve/distill.js} +39 -18
- package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
- package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
- package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
- package/dist/commands/{extract.js → improve/extract.js} +185 -26
- package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +4 -4
- package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
- package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
- package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
- package/dist/commands/{improve.js → improve/improve.js} +517 -253
- package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
- package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
- package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
- package/dist/commands/{reflect.js → improve/reflect.js} +33 -28
- package/dist/commands/improve/session-asset.js +248 -0
- package/dist/commands/lint/agent-linter.js +1 -1
- package/dist/commands/lint/base-linter.js +55 -37
- package/dist/commands/lint/command-linter.js +1 -1
- package/dist/commands/lint/default-linter.js +1 -1
- package/dist/commands/lint/env-key-rules.js +1 -1
- package/dist/commands/lint/index.js +19 -25
- package/dist/commands/lint/knowledge-linter.js +1 -1
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +8 -8
- package/dist/commands/lint/skill-linter.js +1 -1
- package/dist/commands/lint/task-linter.js +1 -1
- package/dist/commands/lint/workflow-linter.js +1 -1
- package/dist/commands/lint.js +1 -1
- package/dist/commands/observability-cli.js +244 -0
- package/dist/commands/proposal/drain-policies.js +3 -3
- package/dist/commands/proposal/drain.js +15 -10
- package/dist/commands/proposal/proposal-cli.js +478 -0
- package/dist/commands/{proposal.js → proposal/proposal.js} +5 -5
- package/dist/commands/{propose.js → proposal/propose.js} +11 -11
- package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
- package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
- package/dist/{core → commands/proposal/validators}/proposals.js +13 -7
- package/dist/commands/{curate.js → read/curate.js} +7 -7
- package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
- package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
- package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
- package/dist/commands/read/search-cli.js +207 -0
- package/dist/commands/{search.js → read/search.js} +22 -27
- package/dist/commands/{show.js → read/show.js} +31 -45
- package/dist/commands/registry-cli.js +8 -8
- package/dist/commands/remember.js +8 -8
- package/dist/commands/sources/add-cli.js +293 -0
- package/dist/commands/{history.js → sources/history.js} +27 -25
- package/dist/commands/{info.js → sources/info.js} +6 -6
- package/dist/commands/{init.js → sources/init.js} +6 -6
- package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
- package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
- package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
- package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
- package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
- package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
- package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
- package/dist/commands/sources/sources-cli.js +305 -0
- package/dist/commands/sources/stash-cli.js +219 -0
- package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
- package/dist/commands/tasks/default-tasks.js +173 -0
- package/dist/commands/tasks/tasks-cli.js +210 -0
- package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
- package/dist/commands/wiki-cli.js +307 -0
- package/dist/commands/workflow-cli.js +329 -0
- package/dist/core/action-contributors.js +1 -1
- package/dist/core/assert.js +40 -0
- package/dist/core/asset/asset-create.js +54 -0
- package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
- package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
- package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
- package/dist/core/{markdown.js → asset/markdown.js} +1 -1
- package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
- package/dist/core/best-effort.js +64 -0
- package/dist/core/common.js +32 -18
- package/dist/core/{config-io.js → config/config-io.js} +29 -19
- package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
- package/dist/core/{config-schema.js → config/config-schema.js} +45 -1
- package/dist/core/config/config-types.js +16 -0
- package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
- package/dist/core/{config.js → config/config.js} +10 -8
- package/dist/core/env-secret-ref.js +90 -0
- package/dist/core/errors.js +13 -3
- package/dist/core/events.js +27 -4
- package/dist/core/file-lock.js +1 -1
- package/dist/core/improve-types.js +48 -0
- package/dist/core/lesson-lint.js +2 -2
- package/dist/core/paths.js +2 -2
- package/dist/core/ripgrep/install.js +2 -2
- package/dist/core/ripgrep/resolve.js +2 -2
- package/dist/core/state-db.js +88 -46
- package/dist/core/text-truncation.js +148 -0
- package/dist/core/time.js +1 -1
- package/dist/core/write-source.js +98 -85
- package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
- package/dist/indexer/{db.js → db/db.js} +126 -116
- package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
- package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
- package/dist/indexer/ensure-index.js +4 -4
- package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
- package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
- package/dist/indexer/indexer.js +37 -30
- package/dist/indexer/init.js +54 -0
- package/dist/indexer/manifest.js +10 -10
- package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +92 -23
- package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
- package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
- package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
- package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
- package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
- package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
- package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
- package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
- package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
- package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
- package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
- package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
- package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
- package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
- package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
- package/dist/indexer/{walker.js → walk/walker.js} +4 -3
- package/dist/integrations/agent/builder-shared.js +39 -0
- package/dist/integrations/agent/builders.js +14 -81
- package/dist/integrations/agent/config.js +6 -4
- package/dist/integrations/agent/detect.js +1 -1
- package/dist/integrations/agent/index.js +23 -8
- package/dist/integrations/agent/prompts.js +2 -3
- package/dist/integrations/agent/runner.js +22 -3
- package/dist/integrations/agent/spawn.js +9 -10
- package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
- package/dist/integrations/harnesses/claude/config-import.js +70 -0
- package/dist/integrations/harnesses/claude/index.js +64 -0
- package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +16 -1
- package/dist/integrations/harnesses/index.js +144 -0
- package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
- package/dist/integrations/harnesses/opencode/config-import.js +82 -0
- package/dist/integrations/harnesses/opencode/index.js +59 -0
- package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
- package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
- package/dist/integrations/harnesses/types.js +43 -0
- package/dist/integrations/lockfile.js +7 -16
- package/dist/integrations/session-logs/index.js +82 -9
- package/dist/llm/call-ai.js +4 -4
- package/dist/llm/client.js +131 -6
- package/dist/llm/embedder.js +6 -6
- package/dist/llm/embedders/local.js +9 -22
- package/dist/llm/embedders/remote.js +2 -2
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +31 -12
- package/dist/llm/index-passes.js +1 -1
- package/dist/llm/memory-infer.js +12 -5
- package/dist/llm/metadata-enhance.js +2 -2
- package/dist/output/context.js +6 -44
- package/dist/output/renderers.js +88 -58
- package/dist/output/shapes/curate.js +7 -3
- package/dist/output/shapes/distill.js +7 -3
- package/dist/output/shapes/env-list.js +18 -16
- package/dist/output/shapes/events.js +5 -4
- package/dist/output/shapes/helpers.js +2 -4
- package/dist/output/shapes/history.js +7 -3
- package/dist/output/shapes/passthrough.js +8 -11
- package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
- package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
- package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
- package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
- package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
- package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
- package/dist/output/shapes/registry-search.js +7 -3
- package/dist/output/shapes/registry.js +12 -0
- package/dist/output/shapes/search.js +7 -3
- package/dist/output/shapes/secret-list.js +18 -16
- package/dist/output/shapes/show.js +7 -3
- package/dist/output/shapes.js +55 -30
- package/dist/output/text/add.js +2 -3
- package/dist/output/text/clone.js +2 -3
- package/dist/output/text/config.js +2 -3
- package/dist/output/text/curate.js +4 -3
- package/dist/output/text/distill.js +2 -3
- package/dist/output/text/enable-disable.js +5 -4
- package/dist/output/text/env.js +13 -0
- package/dist/output/text/events.js +5 -4
- package/dist/output/text/feedback.js +4 -3
- package/dist/output/text/helpers.js +54 -39
- package/dist/output/text/history.js +2 -3
- package/dist/output/text/import.js +2 -3
- package/dist/output/text/index.js +2 -3
- package/dist/output/text/info.js +2 -3
- package/dist/output/text/init.js +2 -3
- package/dist/output/text/list.js +2 -3
- package/dist/output/text/proposal/producer.js +9 -0
- package/dist/output/text/proposal/proposal.js +13 -0
- package/dist/output/text/registry-commands.js +8 -7
- package/dist/output/text/registry.js +12 -0
- package/dist/output/text/remember.js +4 -3
- package/dist/output/text/remove.js +2 -3
- package/dist/output/text/save.js +2 -3
- package/dist/output/text/search.js +4 -3
- package/dist/output/text/show.js +4 -3
- package/dist/output/text/update.js +2 -3
- package/dist/output/text/upgrade.js +2 -3
- package/dist/output/text/wiki.js +12 -11
- package/dist/output/text/workflow.js +12 -10
- package/dist/output/text.js +66 -32
- package/dist/registry/build-index.js +11 -10
- package/dist/registry/factory.js +1 -1
- package/dist/registry/origin-resolve.js +1 -1
- package/dist/registry/providers/index.js +2 -2
- package/dist/registry/providers/skills-sh.js +91 -72
- package/dist/registry/providers/static-index.js +75 -52
- package/dist/registry/resolve.js +3 -3
- package/dist/runtime.js +242 -0
- package/dist/scripts/migrate-storage.js +1594 -673
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +240 -166
- package/dist/setup/detect.js +311 -9
- package/dist/setup/harness-config-import.js +6 -120
- package/dist/setup/setup.js +454 -43
- package/dist/sources/include.js +1 -1
- package/dist/sources/provider-factory.js +2 -2
- package/dist/sources/providers/filesystem.js +3 -3
- package/dist/sources/providers/git.js +9 -9
- package/dist/sources/providers/index.js +4 -4
- package/dist/sources/providers/npm.js +6 -6
- package/dist/sources/providers/provider-utils.js +13 -20
- package/dist/sources/providers/sync-from-ref.js +5 -5
- package/dist/sources/providers/tar-utils.js +2 -2
- package/dist/sources/providers/website.js +2 -2
- package/dist/sources/resolve.js +5 -5
- package/dist/sources/website-ingest.js +5 -5
- package/dist/storage/database.js +102 -0
- package/dist/storage/engines/sqlite-migrations.js +42 -0
- package/dist/storage/locations.js +25 -0
- package/dist/storage/repositories/index-db.js +43 -0
- package/dist/storage/repositories/workflow-runs-repository.js +141 -0
- package/dist/tasks/backends/cron.js +4 -4
- package/dist/tasks/backends/exec-utils.js +32 -0
- package/dist/tasks/backends/index.js +3 -3
- package/dist/tasks/backends/launchd.js +7 -14
- package/dist/tasks/backends/schtasks.js +7 -16
- package/dist/tasks/embedded.js +71 -0
- package/dist/tasks/parser.js +2 -2
- package/dist/tasks/resolveAkmBin.js +1 -1
- package/dist/tasks/runner.js +28 -15
- package/dist/tasks/schedule.js +1 -1
- package/dist/tasks/validator.js +7 -7
- package/dist/text-import-hook.mjs +51 -0
- package/dist/version.js +2 -1
- package/dist/wiki/wiki.js +7 -7
- package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
- package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
- package/dist/workflows/cli.js +1 -1
- package/dist/workflows/db.js +50 -32
- package/dist/workflows/parser.js +4 -4
- package/dist/workflows/renderer.js +5 -5
- package/dist/workflows/runtime/agent-identity.js +56 -0
- package/dist/workflows/runtime/checkin.js +57 -0
- package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
- package/dist/workflows/validate-summary.js +82 -0
- package/docs/README.md +1 -1
- package/docs/data-and-telemetry.md +6 -6
- package/package.json +16 -8
- package/dist/commands/add-cli.js +0 -279
- package/dist/commands/env.js +0 -213
- package/dist/integrations/agent/sdk-runner.js +0 -126
- package/dist/output/shapes/vault-list.js +0 -19
- package/dist/output/text/proposal-producer.js +0 -8
- package/dist/output/text/proposal.js +0 -12
- package/dist/output/text/vault.js +0 -16
- /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
- /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
- /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
- /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
- /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
- /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
- /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
- /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
|
@@ -5,28 +5,28 @@ import { createHash } from "node:crypto";
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import readline from "node:readline";
|
|
8
|
-
import { parse as yamlParse
|
|
9
|
-
import { parseAssetRef } from "
|
|
10
|
-
import { assembleAssetFromString } from "
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { hasHotCaptureMode, hasSupersededStatus, MERGE_ABSOLUTE_FLOOR_CHARS, MERGE_SHRINK_RATIO_MIN, validateProposalFrontmatter, } from "../
|
|
19
|
-
import { createProposal, isProposalSkipped, listProposals } from "../
|
|
20
|
-
import {
|
|
8
|
+
import { parse as yamlParse } from "yaml";
|
|
9
|
+
import { parseAssetRef } from "../../core/asset/asset-ref.js";
|
|
10
|
+
import { assembleAssetFromString, serializeFrontmatter } from "../../core/asset/asset-serialize.js";
|
|
11
|
+
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
12
|
+
import { resolveStashDir, timestampForFilename } from "../../core/common.js";
|
|
13
|
+
import { getDefaultLlmConfig, loadConfig } from "../../core/config/config.js";
|
|
14
|
+
import { ConfigError } from "../../core/errors.js";
|
|
15
|
+
import { appendEvent } from "../../core/events.js";
|
|
16
|
+
import { parseEmbeddedJsonResponse } from "../../core/parse.js";
|
|
17
|
+
import { detectTruncatedDescription } from "../../core/text-truncation.js";
|
|
18
|
+
import { hasHotCaptureMode, hasSupersededStatus, MERGE_ABSOLUTE_FLOOR_CHARS, MERGE_SHRINK_RATIO_MIN, validateProposalFrontmatter, } from "../proposal/validators/proposal-quality-validators.js";
|
|
19
|
+
import { createProposal, isProposalSkipped, listProposals } from "../proposal/validators/proposals.js";
|
|
20
|
+
import { writeContradictEdge } from "./memory/memory-belief.js";
|
|
21
21
|
// Re-export the moved helpers so existing test imports continue to resolve.
|
|
22
22
|
export { hasSupersededStatus, validateProposalFrontmatter };
|
|
23
|
-
import { warn } from "
|
|
24
|
-
import { deleteAssetFromSource, resolveWriteTarget, writeAssetToSource } from "
|
|
25
|
-
import { closeDatabase, findEntryIdByRef, getAllEntries, getEntryById, getNeighborsByEntryId, openExistingDatabase, } from "
|
|
26
|
-
import { resolveImproveProcessRunnerFromProfile } from "
|
|
27
|
-
import { chatCompletion } from "
|
|
28
|
-
import { cosineSimilarity, embedBatch } from "
|
|
29
|
-
import { isLlmFeatureEnabled, tryLlmFeature } from "
|
|
23
|
+
import { warn } from "../../core/warn.js";
|
|
24
|
+
import { commitWriteTargetBoundary, deleteAssetFromSource, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
|
|
25
|
+
import { closeDatabase, findEntryIdByRef, getAllEntries, getEntryById, getNeighborsByEntryId, openExistingDatabase, } from "../../indexer/db/db.js";
|
|
26
|
+
import { resolveImproveProcessRunnerFromProfile, runnerIsLlm } from "../../integrations/agent/runner.js";
|
|
27
|
+
import { chatCompletion } from "../../llm/client.js";
|
|
28
|
+
import { cosineSimilarity, embedBatch } from "../../llm/embedder.js";
|
|
29
|
+
import { isLlmFeatureEnabled, tryLlmFeature } from "../../llm/feature-gate.js";
|
|
30
30
|
// ── Prompts ─────────────────────────────────────────────────────────────────
|
|
31
31
|
const CONSOLIDATE_SYSTEM_PROMPT = `You are the akm consolidate assistant analyzing memory assets.
|
|
32
32
|
|
|
@@ -704,7 +704,7 @@ function archiveMemory(filePath, stashDir, ref, reason, opIndex, supersededBy, w
|
|
|
704
704
|
...(supersededBy ? { superseded_by: supersededBy } : {}),
|
|
705
705
|
superseded_reason: reason,
|
|
706
706
|
};
|
|
707
|
-
content = assembleAssetFromString(
|
|
707
|
+
content = assembleAssetFromString(serializeFrontmatter(newFm), parsed.content);
|
|
708
708
|
}
|
|
709
709
|
catch {
|
|
710
710
|
if (warnings)
|
|
@@ -745,7 +745,7 @@ function archiveMemory(filePath, stashDir, ref, reason, opIndex, supersededBy, w
|
|
|
745
745
|
function resolveConsolidateLlmConfig(config) {
|
|
746
746
|
const consolidateProcess = config.profiles?.improve?.default?.processes?.consolidate;
|
|
747
747
|
const runnerSpec = resolveImproveProcessRunnerFromProfile(consolidateProcess, config);
|
|
748
|
-
if (runnerSpec && runnerSpec
|
|
748
|
+
if (runnerSpec && runnerIsLlm(runnerSpec)) {
|
|
749
749
|
return runnerSpec.connection;
|
|
750
750
|
}
|
|
751
751
|
// Non-LLM runner modes (agent/sdk) don't apply to consolidate's HTTP path;
|
|
@@ -877,11 +877,12 @@ export async function akmConsolidate(opts = {}) {
|
|
|
877
877
|
// health rollup can aggregate without regex-parsing English warning
|
|
878
878
|
// strings. See `/tmp/akm-health-investigations/tuning-reasons-investigation.md` §Q2.
|
|
879
879
|
const skipReasons = [];
|
|
880
|
-
//
|
|
881
|
-
// accounting bucket
|
|
882
|
-
//
|
|
880
|
+
// Per-ref grouping of skipReasons entries. A ref occupies exactly one
|
|
881
|
+
// accounting bucket and therefore exactly one skipReasons array entry;
|
|
882
|
+
// subsequent skip ops for the same ref append to that entry's `skips[]`
|
|
883
|
+
// rather than pushing a second array entry (that would inflate
|
|
883
884
|
// Σ(skipReasons) and break the invariant by +1 per duplicate).
|
|
884
|
-
const
|
|
885
|
+
const skipReasonByRef = new Map();
|
|
885
886
|
const pushSkipReason = (op, ref, reason) => {
|
|
886
887
|
// 2026-05-27 cross-chunk double-count fix: if `ref` already contributed
|
|
887
888
|
// to judgedNoAction in its own chunk (a different chunk proposed an op
|
|
@@ -891,14 +892,17 @@ export async function akmConsolidate(opts = {}) {
|
|
|
891
892
|
// Σ(skipReasons) + failedChunkMemories.
|
|
892
893
|
if (judgedNoActionRefs.delete(ref))
|
|
893
894
|
judgedNoAction--;
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
//
|
|
897
|
-
|
|
895
|
+
const existing = skipReasonByRef.get(ref);
|
|
896
|
+
if (existing) {
|
|
897
|
+
// Already counted once for accounting. Append the extra skip to the
|
|
898
|
+
// ref's grouped entry for observability without adding a new array
|
|
899
|
+
// entry (which would break the accounting invariant).
|
|
900
|
+
existing.skips.push({ op, reason });
|
|
898
901
|
return;
|
|
899
902
|
}
|
|
900
|
-
|
|
901
|
-
|
|
903
|
+
const entry = { ref, skips: [{ op, reason }] };
|
|
904
|
+
skipReasonByRef.set(ref, entry);
|
|
905
|
+
skipReasons.push(entry);
|
|
902
906
|
};
|
|
903
907
|
// judgedNoAction tracks memories the LLM saw inside a chunk but proposed
|
|
904
908
|
// no op for. Computed per chunk as `chunk.length − unique(targetRefs in ops)`.
|
|
@@ -1560,7 +1564,7 @@ export async function akmConsolidate(opts = {}) {
|
|
|
1560
1564
|
...(parsedMemory.data ?? {}),
|
|
1561
1565
|
description,
|
|
1562
1566
|
};
|
|
1563
|
-
const serializedMergedFm =
|
|
1567
|
+
const serializedMergedFm = serializeFrontmatter(mergedBodyFm);
|
|
1564
1568
|
const proposalContent = assembleAssetFromString(serializedMergedFm, parsedMemory.content);
|
|
1565
1569
|
// Pre-emit dedup against pending consolidate proposals from the
|
|
1566
1570
|
// same improve run (slug-variant match). The cross-run content-hash
|
|
@@ -1634,6 +1638,14 @@ export async function akmConsolidate(opts = {}) {
|
|
|
1634
1638
|
}
|
|
1635
1639
|
}
|
|
1636
1640
|
}
|
|
1641
|
+
// 0.9.0 (issue #507): batch-at-boundary commit. The merge/delete loop above
|
|
1642
|
+
// wrote one merged primary and deleted N secondaries to the resolved target
|
|
1643
|
+
// with NO per-asset commit. If the target is a writable git source and any
|
|
1644
|
+
// asset was mutated, commit the whole batch ONCE here (stages .akm/ +
|
|
1645
|
+
// siblings together). No-op for filesystem/primary-stash targets.
|
|
1646
|
+
if (merged > 0 || deleted > 0) {
|
|
1647
|
+
commitWriteTargetBoundary(target, `Consolidate: ${merged} merged, ${deleted} removed`);
|
|
1648
|
+
}
|
|
1637
1649
|
cleanupJournal(stashDir, timestamp);
|
|
1638
1650
|
// TTL cleanup: remove archive entries older than archiveRetentionDays (default 90).
|
|
1639
1651
|
// C-5 / #391: emit an `archive_cleanup` event before each deletion so the
|
|
@@ -1828,7 +1840,7 @@ export function sanitizeMergedContent(raw) {
|
|
|
1828
1840
|
// Recovery: if the strict yaml library fails, fall back to the lenient
|
|
1829
1841
|
// hand-rolled parseFrontmatter parser, which tolerates common LLM YAML
|
|
1830
1842
|
// quirks (unescaped special chars, bare scalars, etc.). If it recovers
|
|
1831
|
-
// at least one key, proceed —
|
|
1843
|
+
// at least one key, proceed — serializeFrontmatter below will re-serialize
|
|
1832
1844
|
// cleanly. Only reject if both parsers fail to extract any data.
|
|
1833
1845
|
let parsedFm;
|
|
1834
1846
|
try {
|
|
@@ -1855,7 +1867,7 @@ export function sanitizeMergedContent(raw) {
|
|
|
1855
1867
|
// Re-serialise via yaml.stringify to fix any quoting quirks.
|
|
1856
1868
|
let serialized;
|
|
1857
1869
|
try {
|
|
1858
|
-
serialized =
|
|
1870
|
+
serialized = serializeFrontmatter(fm);
|
|
1859
1871
|
}
|
|
1860
1872
|
catch (e) {
|
|
1861
1873
|
return { ok: false, reason: `YAML_STRINGIFY_FAILED: ${e instanceof Error ? e.message : String(e)}` };
|
|
@@ -2205,7 +2217,7 @@ async function generateMergedContent(config, primaryRef, primaryBody, secondaryR
|
|
|
2205
2217
|
: secFm.data[key];
|
|
2206
2218
|
}
|
|
2207
2219
|
normalizeUpdatedField(repairedFmData);
|
|
2208
|
-
const repairedYaml =
|
|
2220
|
+
const repairedYaml = serializeFrontmatter(repairedFmData);
|
|
2209
2221
|
const bodyPart = mergedFm.content ?? "";
|
|
2210
2222
|
return { content: `---\n${repairedYaml}\n---\n${bodyPart}` };
|
|
2211
2223
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
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
|
-
import { parseAssetRef } from "
|
|
5
|
-
import { assembleAsset } from "
|
|
6
|
-
import { parseFrontmatter } from "
|
|
4
|
+
import { parseAssetRef } from "../../core/asset/asset-ref.js";
|
|
5
|
+
import { assembleAsset } from "../../core/asset/asset-serialize.js";
|
|
6
|
+
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
7
7
|
function hasNonEmptyList(value) {
|
|
8
8
|
return Array.isArray(value) && value.some((item) => typeof item === "string" && item.trim().length > 0);
|
|
9
9
|
}
|
|
@@ -52,22 +52,22 @@
|
|
|
52
52
|
*/
|
|
53
53
|
import fs from "node:fs";
|
|
54
54
|
import path from "node:path";
|
|
55
|
-
import { parseAssetRef } from "
|
|
56
|
-
import { assembleAssetFromString } from "
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
55
|
+
import { parseAssetRef } from "../../core/asset/asset-ref.js";
|
|
56
|
+
import { assembleAssetFromString } from "../../core/asset/asset-serialize.js";
|
|
57
|
+
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
58
|
+
import { stripMarkdownFences } from "../../core/asset/markdown.js";
|
|
59
|
+
import { resolveStashDir, timestampForFilename } from "../../core/common.js";
|
|
60
|
+
import { getDefaultLlmConfig, loadConfig } from "../../core/config/config.js";
|
|
61
|
+
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
62
|
+
import { appendEvent, readEvents } from "../../core/events.js";
|
|
63
|
+
import { lintLessonContent } from "../../core/lesson-lint.js";
|
|
64
|
+
import { warnVerbose } from "../../core/warn.js";
|
|
65
|
+
import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
|
|
66
|
+
import { chatCompletion, parseEmbeddedJsonResponse } from "../../llm/client.js";
|
|
67
|
+
import { isLlmFeatureEnabled, tryLlmFeature } from "../../llm/feature-gate.js";
|
|
68
|
+
import { createProposal, isProposalSkipped, listProposals, } from "../proposal/validators/proposals.js";
|
|
69
|
+
import { akmSearch } from "../read/search.js";
|
|
70
|
+
import { assessMemoryKnowledgePromotionCandidate, deriveKnowledgeRef } from "./distill-promotion-policy.js";
|
|
71
71
|
/**
|
|
72
72
|
* Asset-ref types that `akm distill` structurally refuses as inputs.
|
|
73
73
|
*
|
|
@@ -112,13 +112,14 @@ export function deriveLessonRef(inputRef) {
|
|
|
112
112
|
.replace(/^-|-$/g, "");
|
|
113
113
|
return `lesson:${safe}-lesson`;
|
|
114
114
|
}
|
|
115
|
+
import { repairTruncatedDescription } from "../../core/text-truncation.js";
|
|
115
116
|
// ── Content quality validators ──────────────────────────────────────────────
|
|
116
117
|
//
|
|
117
118
|
// The actual implementations now live in `core/proposal-quality-validators.ts`
|
|
118
119
|
// so the same checks run inside `runProposalValidators` on `proposal accept`.
|
|
119
120
|
// We re-export the public-facing helpers here so existing imports
|
|
120
|
-
// (`from "
|
|
121
|
-
import { detectDoubleFrontmatter, isValidDescription, isValidWhenToUse } from "../
|
|
121
|
+
// (`from "../../src/commands/distill"`) continue to resolve.
|
|
122
|
+
import { detectDoubleFrontmatter, isValidDescription, isValidWhenToUse, } from "../proposal/validators/proposal-quality-validators.js";
|
|
122
123
|
export { detectDoubleFrontmatter, isValidDescription, isValidWhenToUse };
|
|
123
124
|
// ── Prompt assembly ─────────────────────────────────────────────────────────
|
|
124
125
|
const LESSON_SYSTEM_PROMPT = [
|
|
@@ -1121,6 +1122,26 @@ export async function akmDistill(options) {
|
|
|
1121
1122
|
}
|
|
1122
1123
|
}
|
|
1123
1124
|
}
|
|
1125
|
+
// Post-generation truncation repair (#556): if the LLM sliced the
|
|
1126
|
+
// description mid-sentence, deterministically complete it from its own text
|
|
1127
|
+
// / the lesson body BEFORE the lint + quality validators run. No-op
|
|
1128
|
+
// (byte-identical) for already-complete descriptions, so this never alters
|
|
1129
|
+
// a valid proposal. Runs on the lesson path only (knowledge has no
|
|
1130
|
+
// description field gate here).
|
|
1131
|
+
if (effectiveProposalKind !== "knowledge") {
|
|
1132
|
+
const parsedRepair = parseFrontmatter(content);
|
|
1133
|
+
const fmRepair = (parsedRepair.data ?? {});
|
|
1134
|
+
const descRepairRaw = typeof fmRepair.description === "string" ? fmRepair.description : "";
|
|
1135
|
+
if (descRepairRaw) {
|
|
1136
|
+
const repaired = repairTruncatedDescription(descRepairRaw, parsedRepair.content);
|
|
1137
|
+
if (repaired !== descRepairRaw) {
|
|
1138
|
+
const repairedFmLines = Object.entries({ ...fmRepair, description: repaired })
|
|
1139
|
+
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
|
|
1140
|
+
.join("\n");
|
|
1141
|
+
content = assembleAssetFromString(repairedFmLines, parsedRepair.content);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1124
1145
|
// Parse + lint the lesson before creating the proposal. The lint is the
|
|
1125
1146
|
// canonical gate for required frontmatter (v1 spec §13). On failure we
|
|
1126
1147
|
// surface a structured error and exit non-zero — but still emit
|
|
@@ -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 { writeFileAtomic } from "
|
|
6
|
+
import { writeFileAtomic } from "../../core/common.js";
|
|
7
7
|
export function writeEvalCase(stashDir, evalCase) {
|
|
8
8
|
const evalDir = path.join(stashDir, ".akm", "eval-cases");
|
|
9
9
|
fs.mkdirSync(evalDir, { recursive: true });
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
* `--auto` runs multiple harnesses).
|
|
16
16
|
*/
|
|
17
17
|
import { defineCommand } from "citty";
|
|
18
|
-
import { output, runWithJsonErrors } from "
|
|
19
|
-
import { UsageError } from "
|
|
20
|
-
import { getAvailableHarnesses } from "
|
|
21
|
-
import { akmExtract } from "./extract";
|
|
18
|
+
import { output, runWithJsonErrors } from "../../cli/shared.js";
|
|
19
|
+
import { UsageError } from "../../core/errors.js";
|
|
20
|
+
import { getAvailableHarnesses } from "../../integrations/session-logs/index.js";
|
|
21
|
+
import { akmExtract } from "./extract.js";
|
|
22
22
|
export const extractCommand = defineCommand({
|
|
23
23
|
meta: {
|
|
24
24
|
name: "extract",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* happy path. `additionalProperties: false` means any hallucinated keys
|
|
15
15
|
* the model emits get dropped before we parse.
|
|
16
16
|
*/
|
|
17
|
-
import promptTemplate from "
|
|
17
|
+
import promptTemplate from "../../assets/prompts/extract-session.md" with { type: "text" };
|
|
18
18
|
/**
|
|
19
19
|
* JSON Schema for the structured extract output. Passed to `chatCompletion`
|
|
20
20
|
* when the configured LLM connection has `supportsJsonSchema: true`.
|
|
@@ -55,7 +55,7 @@ export const EXTRACT_JSON_SCHEMA = {
|
|
|
55
55
|
type: "string",
|
|
56
56
|
minLength: 20,
|
|
57
57
|
maxLength: 400,
|
|
58
|
-
description: "One-sentence summary of the candidate.",
|
|
58
|
+
description: "One-sentence summary of the candidate. Must be a complete sentence; do not end mid-clause.",
|
|
59
59
|
},
|
|
60
60
|
when_to_use: {
|
|
61
61
|
type: "string",
|
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
4
|
+
/**
|
|
5
|
+
* `akm extract` — session-insight extractor.
|
|
6
|
+
*
|
|
7
|
+
* Replaces the akm-plugin session-checkpoint hook with an on-demand extractor
|
|
8
|
+
* that reads native session files (claude-code JSONL, opencode storage tree)
|
|
9
|
+
* through the {@link SessionLogHarness} registry, pre-filters noise, and asks
|
|
10
|
+
* a bounded in-tree LLM to produce candidate memory/lesson/knowledge proposals
|
|
11
|
+
* for content the agent did NOT preserve via inline `akm remember`/`akm feedback`.
|
|
12
|
+
*
|
|
13
|
+
* Architectural notes:
|
|
14
|
+
* - Stateless. All file/LLM access goes through injectable seams so tests
|
|
15
|
+
* never touch a real platform.
|
|
16
|
+
* - Bounded LLM call wrapped by {@link tryLlmFeature} under the
|
|
17
|
+
* `session_extraction` gate (default-on; opt out via
|
|
18
|
+
* `profiles.improve.default.processes.extract.enabled: false`).
|
|
19
|
+
* - Proposals routed via `createProposal({ source: "extract", ... })` — the
|
|
20
|
+
* same review queue as reflect / distill / consolidate. Never direct-write.
|
|
21
|
+
* - Per-candidate body assembly merges description (+ when_to_use for lessons)
|
|
22
|
+
* into the body's YAML frontmatter so the accept-time
|
|
23
|
+
* descriptionQualityValidator passes — same pattern as the
|
|
24
|
+
* consolidate-writer fix.
|
|
25
|
+
*/
|
|
26
|
+
import { assembleAsset } from "../../core/asset/asset-serialize.js";
|
|
27
|
+
import { resolveStashDir, timestampForFilename } from "../../core/common.js";
|
|
28
|
+
import { getDefaultLlmConfig, loadConfig } from "../../core/config/config.js";
|
|
29
|
+
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
30
|
+
import { appendEvent } from "../../core/events.js";
|
|
31
|
+
import { getExtractedSessionsMap, openStateDatabase, shouldSkipAlreadyExtractedSession, upsertExtractedSession, } from "../../core/state-db.js";
|
|
32
|
+
import { repairTruncatedDescription } from "../../core/text-truncation.js";
|
|
33
|
+
import { warn } from "../../core/warn.js";
|
|
34
|
+
import { resolveImproveProcessRunnerFromProfile, runnerIsLlm } from "../../integrations/agent/runner.js";
|
|
35
|
+
import { normalizeHarnessId } from "../../integrations/harnesses/index.js";
|
|
36
|
+
import { getAvailableHarnesses } from "../../integrations/session-logs/index.js";
|
|
37
|
+
import { preFilterSession } from "../../integrations/session-logs/pre-filter.js";
|
|
38
|
+
import { chatCompletion } from "../../llm/client.js";
|
|
39
|
+
import { isLlmFeatureEnabled, tryLlmFeature } from "../../llm/feature-gate.js";
|
|
40
|
+
import { createProposal, isProposalSkipped } from "../proposal/validators/proposals.js";
|
|
41
|
+
import { buildExtractPrompt, EXTRACT_JSON_SCHEMA, parseExtractPayload } from "./extract-prompt.js";
|
|
42
|
+
import { buildSessionSummaryPrompt, parseSessionSummary, SESSION_SUMMARY_JSON_SCHEMA, sessionMeetsDurationGate, writeSessionAsset, } from "./session-asset.js";
|
|
43
|
+
/** Default minimum session duration (minutes) for session indexing (#561). */
|
|
44
|
+
const DEFAULT_MIN_SESSION_DURATION_MINUTES = 5;
|
|
19
45
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
20
46
|
/**
|
|
21
47
|
* Parse a since-string into an absolute ms-epoch cutoff. Accepts:
|
|
@@ -49,7 +75,15 @@ export function parseSinceArg(value, now = Date.now()) {
|
|
|
49
75
|
*/
|
|
50
76
|
function resolveHarness(type, harnesses) {
|
|
51
77
|
const pool = harnesses ?? getAvailableHarnesses();
|
|
52
|
-
|
|
78
|
+
// #563 id-normalization bridge: a provider's `name` is its runtime id (e.g.
|
|
79
|
+
// the Claude provider is "claude-code"), but the canonical harness id is
|
|
80
|
+
// "claude". Normalize BOTH the requested `--type` and each provider name to
|
|
81
|
+
// canonical before comparing, so `--type claude` and `--type claude-code`
|
|
82
|
+
// both resolve to the Claude provider. Behaviour fix: previously only the
|
|
83
|
+
// exact runtime string ("claude-code") matched; the canonical "claude" used
|
|
84
|
+
// everywhere else (agent profiles, config schema) silently found nothing.
|
|
85
|
+
const wanted = normalizeHarnessId(type);
|
|
86
|
+
return pool.find((h) => normalizeHarnessId(h.name) === wanted);
|
|
53
87
|
}
|
|
54
88
|
/**
|
|
55
89
|
* Build the ref + content for a candidate. The body must contain a
|
|
@@ -59,16 +93,19 @@ function resolveHarness(type, harnesses) {
|
|
|
59
93
|
*/
|
|
60
94
|
function buildCandidateProposal(candidate, sourceRef) {
|
|
61
95
|
const ref = `${candidate.type}:${candidate.name}`;
|
|
96
|
+
// Post-generation repair pass (#556): deterministically complete a
|
|
97
|
+
// description the LLM sliced mid-sentence before it reaches the
|
|
98
|
+
// auto-accept validators. No-op (byte-identical) for valid descriptions.
|
|
99
|
+
const description = repairTruncatedDescription(candidate.description, candidate.body);
|
|
62
100
|
const fm = {
|
|
63
|
-
description
|
|
101
|
+
description,
|
|
64
102
|
sources: [`session:${sourceRef.harness}:${sourceRef.sessionId}`],
|
|
65
103
|
};
|
|
66
104
|
if (candidate.type === "lesson" && candidate.when_to_use) {
|
|
67
105
|
fm.when_to_use = candidate.when_to_use;
|
|
68
106
|
}
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
return { ref, content };
|
|
107
|
+
const content = assembleAsset(fm, candidate.body);
|
|
108
|
+
return { ref, content, description };
|
|
72
109
|
}
|
|
73
110
|
/**
|
|
74
111
|
* Process one session through the full pipeline: read → pre-filter → LLM →
|
|
@@ -78,7 +115,7 @@ function buildCandidateProposal(candidate, sourceRef) {
|
|
|
78
115
|
* proposal validation failure) the session result records a warning and
|
|
79
116
|
* keeps going — one session's bad luck never aborts a multi-session run.
|
|
80
117
|
*/
|
|
81
|
-
async function processSession(harness, sessionRef, stashDir, config, llmConfig, chat, ctx, sourceRun, dryRun, timeoutMs, maxTotalChars) {
|
|
118
|
+
async function processSession(harness, sessionRef, stashDir, config, llmConfig, chat, ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, sessionIndexing) {
|
|
82
119
|
const warnings = [];
|
|
83
120
|
let data;
|
|
84
121
|
try {
|
|
@@ -100,6 +137,30 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
100
137
|
...(typeof maxTotalChars === "number" ? { maxTotalChars } : {}),
|
|
101
138
|
});
|
|
102
139
|
const prompt = buildExtractPrompt({ data, events: filtered.events, inlineRefs: data.inlineRefs });
|
|
140
|
+
// #561 — ADDITIVE session indexing. Generate + write the session asset
|
|
141
|
+
// (`sessions/<harness>/<id>.md`). FAIL-OPEN: any failure only records a
|
|
142
|
+
// warning; it NEVER changes the proposal/skip outcome of extract. Returns the
|
|
143
|
+
// frontmatter fields to merge into the per-session result for state-db
|
|
144
|
+
// correlation. When disabled this closure makes NO LLM call and writes NOTHING.
|
|
145
|
+
const maybeWriteSessionAsset = async () => {
|
|
146
|
+
if (!sessionIndexing.enabled || dryRun)
|
|
147
|
+
return {};
|
|
148
|
+
if (!sessionMeetsDurationGate(data, sessionIndexing.minDurationMinutes))
|
|
149
|
+
return {};
|
|
150
|
+
try {
|
|
151
|
+
const result = await writeSessionAsset(data, stashDir, sessionIndexing.generate);
|
|
152
|
+
if (result.written) {
|
|
153
|
+
return {
|
|
154
|
+
...(result.ref ? { sessionAssetRef: result.ref } : {}),
|
|
155
|
+
...(result.logPath ? { sessionLogPath: result.logPath } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
warnings.push(`session asset write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
161
|
+
}
|
|
162
|
+
return {};
|
|
163
|
+
};
|
|
103
164
|
let llmRaw = "";
|
|
104
165
|
const llmResult = await tryLlmFeature("session_extraction", config, async () => {
|
|
105
166
|
llmRaw = await chat(llmConfig, [{ role: "user", content: prompt }], {
|
|
@@ -141,6 +202,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
141
202
|
preFilterOutput: filtered.stats.outputCount,
|
|
142
203
|
},
|
|
143
204
|
}, ctx);
|
|
205
|
+
const sessionAsset = await maybeWriteSessionAsset();
|
|
144
206
|
return {
|
|
145
207
|
sessionId: sessionRef.sessionId,
|
|
146
208
|
harness: harness.name,
|
|
@@ -153,6 +215,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
153
215
|
truncatedCount: filtered.stats.truncatedCount,
|
|
154
216
|
},
|
|
155
217
|
warnings,
|
|
218
|
+
...sessionAsset,
|
|
156
219
|
};
|
|
157
220
|
}
|
|
158
221
|
for (const candidate of payload.candidates) {
|
|
@@ -161,7 +224,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
161
224
|
continue;
|
|
162
225
|
}
|
|
163
226
|
try {
|
|
164
|
-
const { ref, content } = buildCandidateProposal(candidate, sessionRef);
|
|
227
|
+
const { ref, content, description } = buildCandidateProposal(candidate, sessionRef);
|
|
165
228
|
const result = createProposal(stashDir, {
|
|
166
229
|
ref,
|
|
167
230
|
source: "extract",
|
|
@@ -169,7 +232,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
169
232
|
payload: {
|
|
170
233
|
content,
|
|
171
234
|
frontmatter: {
|
|
172
|
-
description
|
|
235
|
+
description,
|
|
173
236
|
...(candidate.when_to_use ? { when_to_use: candidate.when_to_use } : {}),
|
|
174
237
|
...(typeof candidate.confidence === "number" ? { confidence: candidate.confidence } : {}),
|
|
175
238
|
sources: [`session:${sessionRef.harness}:${sessionRef.sessionId}`],
|
|
@@ -202,6 +265,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
202
265
|
preFilterOutput: filtered.stats.outputCount,
|
|
203
266
|
},
|
|
204
267
|
}, ctx);
|
|
268
|
+
const sessionAsset = await maybeWriteSessionAsset();
|
|
205
269
|
return {
|
|
206
270
|
sessionId: sessionRef.sessionId,
|
|
207
271
|
harness: harness.name,
|
|
@@ -213,6 +277,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
213
277
|
truncatedCount: filtered.stats.truncatedCount,
|
|
214
278
|
},
|
|
215
279
|
warnings,
|
|
280
|
+
...sessionAsset,
|
|
216
281
|
};
|
|
217
282
|
}
|
|
218
283
|
// ── Public entrypoint ────────────────────────────────────────────────────────
|
|
@@ -257,7 +322,7 @@ export async function akmExtract(options) {
|
|
|
257
322
|
let llmConfig;
|
|
258
323
|
const runnerSpec = resolveImproveProcessRunnerFromProfile(extractProcess, config);
|
|
259
324
|
if (runnerSpec) {
|
|
260
|
-
if (runnerSpec
|
|
325
|
+
if (!runnerIsLlm(runnerSpec)) {
|
|
261
326
|
throw new ConfigError(`Extract only supports mode: "llm" (in-tree LLM call). Got mode: "${runnerSpec.kind}" from profiles.improve.default.processes.extract — change it to "llm" or remove the override.`, "INVALID_CONFIG_FILE");
|
|
262
327
|
}
|
|
263
328
|
llmConfig = runnerSpec.connection;
|
|
@@ -276,6 +341,35 @@ export async function akmExtract(options) {
|
|
|
276
341
|
const maxTotalChars = typeof extractProcess?.maxTotalChars === "number" ? extractProcess.maxTotalChars : undefined;
|
|
277
342
|
// Default discovery window — process config can override the built-in 24h.
|
|
278
343
|
const effectiveSince = options.since ?? extractProcess?.defaultSince;
|
|
344
|
+
// #561 — resolve session-indexing config. Default ON: we only reach this code
|
|
345
|
+
// when `session_extraction` is enabled AND an LLM is configured (both checked
|
|
346
|
+
// above), so defaulting on costs nothing offline (the summary call fails open)
|
|
347
|
+
// while making sessions searchable in the common LLM-configured case. Set
|
|
348
|
+
// `processes.extract.indexSessions: false` for byte-identical legacy behaviour.
|
|
349
|
+
const sessionIndexingEnabled = extractProcess?.indexSessions ?? true;
|
|
350
|
+
const minSessionDuration = typeof extractProcess?.minSessionDuration === "number"
|
|
351
|
+
? extractProcess.minSessionDuration
|
|
352
|
+
: DEFAULT_MIN_SESSION_DURATION_MINUTES;
|
|
353
|
+
// Production summary generator: a bounded in-tree LLM call wrapped in the same
|
|
354
|
+
// fail-open `tryLlmFeature` seam as the rest of extract. Returns `undefined`
|
|
355
|
+
// on disablement / timeout / error so no asset is written. Tests inject a fake.
|
|
356
|
+
const chatForSummary = options.chat ?? chatCompletion;
|
|
357
|
+
const defaultSessionSummaryGenerator = async (data) => {
|
|
358
|
+
let raw = "";
|
|
359
|
+
await tryLlmFeature("session_extraction", config, async () => {
|
|
360
|
+
raw = await chatForSummary(llmConfig, [{ role: "user", content: buildSessionSummaryPrompt(data) }], {
|
|
361
|
+
timeoutMs,
|
|
362
|
+
responseSchema: SESSION_SUMMARY_JSON_SCHEMA,
|
|
363
|
+
});
|
|
364
|
+
return raw;
|
|
365
|
+
}, "", { timeoutMs });
|
|
366
|
+
return parseSessionSummary(raw);
|
|
367
|
+
};
|
|
368
|
+
const sessionIndexing = {
|
|
369
|
+
enabled: sessionIndexingEnabled,
|
|
370
|
+
minDurationMinutes: minSessionDuration,
|
|
371
|
+
generate: options.generateSessionSummary ?? defaultSessionSummaryGenerator,
|
|
372
|
+
};
|
|
279
373
|
const harness = resolveHarness(options.type, options.harnesses);
|
|
280
374
|
if (!harness) {
|
|
281
375
|
return {
|
|
@@ -355,7 +449,7 @@ export async function akmExtract(options) {
|
|
|
355
449
|
let seenMap = new Map();
|
|
356
450
|
if (trackingEnabled && candidates.length > 0) {
|
|
357
451
|
try {
|
|
358
|
-
stateDb = options.stateDb ?? openStateDatabase();
|
|
452
|
+
stateDb = options.stateDb ?? openStateDatabase(options.stateDbPath);
|
|
359
453
|
seenMap = getExtractedSessionsMap(stateDb, harness.name, candidates.map((c) => c.sessionId));
|
|
360
454
|
}
|
|
361
455
|
catch (err) {
|
|
@@ -389,7 +483,7 @@ export async function akmExtract(options) {
|
|
|
389
483
|
continue;
|
|
390
484
|
}
|
|
391
485
|
try {
|
|
392
|
-
const result = await processSession(harness, summary, stashDir, config, llmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars);
|
|
486
|
+
const result = await processSession(harness, summary, stashDir, config, llmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, sessionIndexing);
|
|
393
487
|
sessions.push(result);
|
|
394
488
|
if (result.skipped)
|
|
395
489
|
skippedCount += 1;
|
|
@@ -423,6 +517,11 @@ export async function akmExtract(options) {
|
|
|
423
517
|
preFilterOutputCount: result.preFilter.outputCount,
|
|
424
518
|
preFilterTruncatedCount: result.preFilter.truncatedCount,
|
|
425
519
|
...(result.skipReason ? { skipReason: result.skipReason } : {}),
|
|
520
|
+
// #561 — record the session's log_path for correlation across
|
|
521
|
+
// index rebuilds (the session asset frontmatter is the primary
|
|
522
|
+
// durable key; this is the state-db mirror of it).
|
|
523
|
+
...(result.sessionLogPath ? { logPath: result.sessionLogPath } : {}),
|
|
524
|
+
...(result.sessionAssetRef ? { sessionAssetRef: result.sessionAssetRef } : {}),
|
|
426
525
|
},
|
|
427
526
|
});
|
|
428
527
|
}
|
|
@@ -475,3 +574,63 @@ export async function akmExtract(options) {
|
|
|
475
574
|
durationMs: Date.now() - startMs,
|
|
476
575
|
};
|
|
477
576
|
}
|
|
577
|
+
/**
|
|
578
|
+
* Count NEW (unseen, in-window) extract candidate sessions across all available
|
|
579
|
+
* harnesses WITHOUT making any LLM calls. Mirrors the discovery + seen-filter
|
|
580
|
+
* logic in {@link akmExtract} so the `#554 minNewSessions` gate in `improve`
|
|
581
|
+
* can decide whether the extract pass is worth running before any work begins.
|
|
582
|
+
*
|
|
583
|
+
* A session is a "new candidate" when it is in the `since` window AND it would
|
|
584
|
+
* not be skipped by {@link shouldSkipAlreadyExtractedSession} (i.e. it has never
|
|
585
|
+
* been extracted, or new events have arrived since it was last extracted).
|
|
586
|
+
*/
|
|
587
|
+
export function countNewExtractCandidates(config, options = {}) {
|
|
588
|
+
const extractProcess = config.profiles?.improve?.default?.processes?.extract;
|
|
589
|
+
const effectiveSince = options.since ?? extractProcess?.defaultSince;
|
|
590
|
+
const sinceMs = parseSinceArg(effectiveSince);
|
|
591
|
+
const harnesses = (options.harnesses ?? getAvailableHarnesses()).filter((h) => h.isAvailable());
|
|
592
|
+
let stateDb = options.stateDb;
|
|
593
|
+
let openedStateDb = false;
|
|
594
|
+
let total = 0;
|
|
595
|
+
try {
|
|
596
|
+
for (const harness of harnesses) {
|
|
597
|
+
const candidates = harness.listSessions({ sinceMs });
|
|
598
|
+
if (candidates.length === 0)
|
|
599
|
+
continue;
|
|
600
|
+
let seenMap = new Map();
|
|
601
|
+
try {
|
|
602
|
+
if (!stateDb) {
|
|
603
|
+
stateDb = openStateDatabase(options.stateDbPath);
|
|
604
|
+
openedStateDb = true;
|
|
605
|
+
}
|
|
606
|
+
seenMap = getExtractedSessionsMap(stateDb, harness.name, candidates.map((c) => c.sessionId));
|
|
607
|
+
}
|
|
608
|
+
catch (err) {
|
|
609
|
+
// state.db unavailable — treat every in-window session as a new
|
|
610
|
+
// candidate (fail-open: never let a transient sqlite error wrongly
|
|
611
|
+
// trip the gate and skip a pass that should have run).
|
|
612
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
613
|
+
warn(`[extract] state.db unavailable while counting candidates, treating all as new: ${msg}`);
|
|
614
|
+
total += candidates.length;
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
for (const summary of candidates) {
|
|
618
|
+
const prior = seenMap.get(summary.sessionId);
|
|
619
|
+
if (shouldSkipAlreadyExtractedSession(prior, summary.endedAt))
|
|
620
|
+
continue;
|
|
621
|
+
total += 1;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
finally {
|
|
626
|
+
if (stateDb && openedStateDb) {
|
|
627
|
+
try {
|
|
628
|
+
stateDb.close();
|
|
629
|
+
}
|
|
630
|
+
catch {
|
|
631
|
+
// best-effort close
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return total;
|
|
636
|
+
}
|