akm-cli 0.9.0-beta.9 → 0.9.0-rc.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 +715 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/help/help-improve.md +9 -6
- package/dist/assets/hints/cli-hints-full.md +6 -5
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/proactive-maintenance.json +25 -0
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/recombine-only.json +21 -0
- package/dist/assets/profiles/reflect-distill.json +30 -0
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +11 -3
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +45 -16
- package/dist/assets/wiki/schema-template.md +4 -4
- package/dist/cli/clack.js +56 -0
- package/dist/cli/config-migrate.js +7 -1
- package/dist/cli/confirm.js +1 -1
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +25 -21
- package/dist/commands/agent/agent-dispatch.js +3 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +26 -7
- package/dist/commands/config-cli.js +26 -13
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-binding.js +95 -0
- package/dist/commands/env/env-cli.js +228 -292
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/env/secret-cli.js +140 -138
- package/dist/commands/feedback-cli.js +156 -155
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +3 -3
- package/dist/commands/health/advisories.js +151 -0
- package/dist/commands/health/checks.js +103 -16
- package/dist/commands/health/html-report.js +447 -81
- package/dist/commands/health/improve-metrics.js +771 -0
- package/dist/commands/health/llm-usage.js +65 -0
- package/dist/commands/health/md-report.js +103 -0
- package/dist/commands/health/metrics.js +278 -0
- package/dist/commands/health/stash-exposure.js +46 -0
- package/dist/commands/health/surfaces.js +216 -0
- package/dist/commands/health/task-runs.js +135 -0
- package/dist/commands/health/types.js +26 -0
- package/dist/commands/health/windows.js +195 -0
- package/dist/commands/health.js +91 -1091
- package/dist/commands/improve/anti-collapse.js +170 -0
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/collapse-detector.js +421 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +64 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +1295 -1277
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill/content-repair.js +202 -0
- package/dist/commands/improve/distill/promote-memory.js +229 -0
- package/dist/commands/improve/distill/quality-gate.js +236 -0
- package/dist/commands/improve/distill-guards.js +127 -0
- package/dist/commands/improve/distill-promotion-policy.js +826 -167
- package/dist/commands/improve/distill.js +228 -605
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +179 -59
- package/dist/commands/improve/extract-prompt.js +54 -3
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +409 -43
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/hot-probation.js +45 -0
- package/dist/commands/improve/improve-auto-accept.js +157 -10
- package/dist/commands/improve/improve-cli.js +115 -73
- package/dist/commands/improve/improve-profiles.js +28 -8
- package/dist/commands/improve/improve-result-file.js +15 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +485 -2764
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1100 -0
- package/dist/commands/improve/memory/memory-belief.js +14 -15
- package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
- package/dist/commands/improve/memory/memory-improve.js +27 -27
- package/dist/commands/improve/outcome-loop.js +270 -0
- package/dist/commands/improve/preparation.js +2002 -0
- package/dist/commands/improve/proactive-maintenance.js +37 -35
- package/dist/commands/improve/procedural.js +398 -0
- package/dist/commands/improve/recombine.js +818 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +206 -45
- package/dist/commands/improve/salience.js +455 -0
- package/dist/commands/improve/schema-similarity-gate.js +168 -0
- package/dist/commands/improve/shared.js +51 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/observability-cli.js +4 -4
- package/dist/commands/proposal/drain-policies.js +13 -4
- package/dist/commands/proposal/drain.js +45 -51
- package/dist/commands/proposal/legacy-import.js +115 -0
- package/dist/commands/proposal/proposal-cli.js +24 -34
- package/dist/commands/proposal/proposal.js +2 -1
- package/dist/commands/proposal/propose.js +8 -3
- package/dist/commands/proposal/repository.js +829 -0
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +93 -895
- package/dist/commands/read/curate.js +410 -111
- package/dist/commands/read/knowledge.js +10 -3
- package/dist/commands/read/remember-cli.js +133 -138
- package/dist/commands/read/search-cli.js +15 -8
- package/dist/commands/read/search.js +22 -11
- package/dist/commands/read/show.js +106 -14
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/remember.js +11 -12
- package/dist/commands/sources/add-cli.js +91 -95
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/init.js +66 -18
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/migration-help.js +7 -4
- package/dist/commands/sources/schema-repair.js +44 -46
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/sources-cli.js +3 -3
- package/dist/commands/sources/stash-cli.js +19 -39
- package/dist/commands/sources/stash-skeleton.js +57 -8
- package/dist/commands/tasks/default-tasks.js +15 -2
- package/dist/commands/tasks/tasks-cli.js +20 -29
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +23 -38
- package/dist/commands/workflow-cli.js +291 -13
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +79 -5
- package/dist/core/asset/frontmatter.js +188 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +4 -23
- package/dist/core/concurrent.js +10 -1
- package/dist/core/config/config-io.js +10 -1
- package/dist/core/config/config-migration.js +18 -40
- package/dist/core/config/config-schema.js +403 -62
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +67 -22
- package/dist/core/deep-merge.js +38 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/eval/rank-metrics.js +113 -0
- package/dist/core/events.js +4 -7
- package/dist/core/improve-types.js +47 -8
- package/dist/core/json-schema.js +142 -0
- package/dist/core/logs-db.js +14 -75
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +18 -18
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +770 -0
- package/dist/core/state-db.js +132 -1126
- package/dist/core/structured.js +69 -0
- package/dist/core/time.js +53 -0
- package/dist/core/warn.js +21 -0
- package/dist/core/write-source.js +37 -0
- package/dist/indexer/db/db.js +261 -770
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/ensure-index.js +36 -92
- package/dist/indexer/feedback/utility-policy.js +75 -0
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +207 -4
- package/dist/indexer/index-writer-lock.js +18 -11
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/indexer.js +182 -204
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/metadata.js +20 -0
- package/dist/indexer/read-preflight.js +23 -0
- package/dist/indexer/search/db-search.js +89 -13
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/indexer/search/ranking-contributors.js +95 -9
- package/dist/indexer/search/ranking.js +79 -3
- package/dist/indexer/search/search-fields.js +6 -0
- package/dist/indexer/search/search-source.js +32 -21
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +48 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/builders.js +41 -13
- package/dist/integrations/agent/config.js +20 -59
- package/dist/integrations/agent/detect.js +9 -0
- package/dist/integrations/agent/index.js +3 -19
- package/dist/integrations/agent/model-aliases.js +16 -2
- package/dist/integrations/agent/profiles.js +79 -6
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +83 -0
- package/dist/integrations/agent/runner.js +13 -9
- package/dist/integrations/agent/spawn.js +206 -81
- package/dist/integrations/harnesses/aider/agent-builder.js +113 -0
- package/dist/integrations/harnesses/aider/index.js +58 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +153 -0
- package/dist/integrations/harnesses/amazonq/index.js +59 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -7
- package/dist/integrations/harnesses/claude/index.js +27 -23
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/claude/session-log.js +10 -0
- package/dist/integrations/harnesses/codex/agent-builder.js +137 -0
- package/dist/integrations/harnesses/codex/index.js +63 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
- package/dist/integrations/harnesses/copilot/index.js +60 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +121 -0
- package/dist/integrations/harnesses/gemini/index.js +60 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/index.js +28 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
- package/dist/integrations/harnesses/opencode/index.js +17 -16
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/harness.js +65 -0
- package/dist/integrations/harnesses/opencode-sdk/index.js +10 -34
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +642 -71
- package/dist/integrations/harnesses/openhands/agent-builder.js +126 -0
- package/dist/integrations/harnesses/openhands/index.js +58 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +104 -0
- package/dist/integrations/harnesses/pi/index.js +58 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/types.js +8 -0
- package/dist/integrations/session-logs/index.js +40 -11
- package/dist/llm/call-ai.js +2 -2
- package/dist/llm/client.js +34 -11
- package/dist/llm/embedder.js +67 -4
- package/dist/llm/embedders/cache.js +3 -1
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +73 -3
- package/dist/llm/feature-gate.js +16 -15
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +87 -15
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +6 -1
- package/dist/output/text/helpers.js +241 -2
- package/dist/output/text/workflow.js +4 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +27 -9
- package/dist/runtime.js +25 -1
- package/dist/schemas/akm-config.json +14225 -0
- package/dist/schemas/akm-workflow.json +328 -0
- package/dist/scripts/migrate-storage.js +2743 -8390
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +1652 -607
- package/dist/setup/detect.js +9 -0
- package/dist/setup/legacy-config.js +106 -0
- package/dist/setup/prompt.js +57 -0
- package/dist/setup/providers.js +14 -0
- package/dist/setup/registry-stash-loader.js +12 -0
- package/dist/setup/semantic-assets.js +124 -0
- package/dist/setup/setup.js +52 -1614
- package/dist/setup/steps/connection.js +734 -0
- package/dist/setup/steps/output.js +31 -0
- package/dist/setup/steps/platforms.js +124 -0
- package/dist/setup/steps/semantic.js +27 -0
- package/dist/setup/steps/sources.js +222 -0
- package/dist/setup/steps/stashdir.js +42 -0
- package/dist/setup/steps/tasks.js +152 -0
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/canaries-repository.js +107 -0
- package/dist/storage/repositories/consolidation-repository.js +38 -0
- package/dist/storage/repositories/embeddings-repository.js +72 -0
- package/dist/storage/repositories/events-repository.js +187 -0
- package/dist/storage/repositories/extract-sessions-repository.js +96 -0
- package/dist/storage/repositories/improve-runs-repository.js +146 -0
- package/dist/storage/repositories/index-db.js +14 -8
- package/dist/storage/repositories/proposals-repository.js +220 -0
- package/dist/storage/repositories/recombine-repository.js +213 -0
- package/dist/storage/repositories/registry-cache.js +93 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
- package/dist/storage/repositories/task-history-repository.js +93 -0
- package/dist/storage/repositories/workflow-runs-repository.js +189 -1
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/index.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +15 -13
- package/dist/text-import-hook.mjs +1 -1
- package/dist/wiki/wiki.js +52 -11
- package/dist/workflows/authoring/authoring.js +123 -10
- package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
- package/dist/workflows/cli.js +5 -0
- package/dist/workflows/db.js +138 -4
- package/dist/workflows/exec/brief.js +484 -0
- package/dist/workflows/exec/native-executor.js +975 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1295 -0
- package/dist/workflows/exec/run-workflow.js +596 -0
- package/dist/workflows/exec/scheduler.js +100 -0
- package/dist/workflows/exec/step-work.js +1156 -0
- package/dist/workflows/exec/unit-writer.js +23 -0
- package/dist/workflows/exec/watch.js +116 -0
- package/dist/workflows/exec/worktree.js +171 -0
- package/dist/workflows/ir/compile.js +388 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +33 -0
- package/dist/workflows/ir/schema.js +4 -0
- package/dist/workflows/parser.js +3 -1
- package/dist/workflows/program/expressions.js +369 -0
- package/dist/workflows/program/parser.js +760 -0
- package/dist/workflows/program/project.js +105 -0
- package/dist/workflows/program/schema.js +54 -0
- package/dist/workflows/renderer.js +82 -5
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/runs.js +248 -153
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +188 -0
- package/dist/workflows/validate-summary.js +26 -10
- package/dist/workflows/validator.js +1 -1
- package/docs/README.md +69 -18
- package/docs/data-and-telemetry.md +7 -5
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +10 -10
- package/dist/assets/tasks/core/update-stashes.yml +0 -4
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
- package/dist/indexer/passes/staleness-detect.js +0 -488
|
@@ -23,23 +23,35 @@
|
|
|
23
23
|
* descriptionQualityValidator passes — same pattern as the
|
|
24
24
|
* consolidate-writer fix.
|
|
25
25
|
*/
|
|
26
|
+
import fs from "node:fs";
|
|
27
|
+
import path from "node:path";
|
|
26
28
|
import { assembleAsset } from "../../core/asset/asset-serialize.js";
|
|
27
29
|
import { resolveStashDir, timestampForFilename } from "../../core/common.js";
|
|
28
|
-
import { getDefaultLlmConfig, loadConfig } from "../../core/config/config.js";
|
|
30
|
+
import { getDefaultLlmConfig, getImproveProcessConfig, loadConfig } from "../../core/config/config.js";
|
|
29
31
|
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
30
32
|
import { appendEvent } from "../../core/events.js";
|
|
31
|
-
import {
|
|
33
|
+
import { probeLock, releaseLock, tryAcquireLockSync } from "../../core/file-lock.js";
|
|
34
|
+
import { resolveStashStandards } from "../../core/standards/resolve-stash-standards.js";
|
|
35
|
+
import { getStateDbPath, openStateDatabase, withStateDb } from "../../core/state-db.js";
|
|
32
36
|
import { repairTruncatedDescription } from "../../core/text-truncation.js";
|
|
33
37
|
import { warn } from "../../core/warn.js";
|
|
38
|
+
import { indexWrittenAssets } from "../../indexer/index-written-assets.js";
|
|
34
39
|
import { resolveImproveProcessRunnerFromProfile, runnerIsLlm } from "../../integrations/agent/runner.js";
|
|
35
40
|
import { normalizeHarnessId } from "../../integrations/harnesses/index.js";
|
|
36
41
|
import { getAvailableHarnesses } from "../../integrations/session-logs/index.js";
|
|
37
42
|
import { preFilterSession } from "../../integrations/session-logs/pre-filter.js";
|
|
38
43
|
import { chatCompletion } from "../../llm/client.js";
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
44
|
+
import { embed } from "../../llm/embedder.js";
|
|
45
|
+
import { tryLlmFeature } from "../../llm/feature-gate.js";
|
|
46
|
+
import { sha256Hex } from "../../runtime.js";
|
|
47
|
+
import { getExtractedSessionsMap, getLastExtractRunAt, shouldSkipAlreadyExtractedSession, upsertExtractedSession, } from "../../storage/repositories/extract-sessions-repository.js";
|
|
48
|
+
import { createProposal, isProposalSkipped } from "../proposal/repository.js";
|
|
41
49
|
import { buildExtractPrompt, EXTRACT_JSON_SCHEMA, parseExtractPayload } from "./extract-prompt.js";
|
|
50
|
+
import { buildHotProbationFrontmatter } from "./hot-probation.js";
|
|
51
|
+
import { resolveProcessEnabled } from "./improve-profiles.js";
|
|
52
|
+
import { applySchemaSimilarityPenalty, loadDerivedLayerEmbeddings, } from "./schema-similarity-gate.js";
|
|
42
53
|
import { buildSessionSummaryPrompt, parseSessionSummary, SESSION_SUMMARY_JSON_SCHEMA, sessionMeetsDurationGate, writeSessionAsset, } from "./session-asset.js";
|
|
54
|
+
import { resolveTriageConfig, scoreSessionTriage } from "./triage.js";
|
|
43
55
|
/** Default minimum session duration (minutes) for session indexing (#561). */
|
|
44
56
|
const DEFAULT_MIN_SESSION_DURATION_MINUTES = 5;
|
|
45
57
|
/**
|
|
@@ -50,6 +62,82 @@ const DEFAULT_MIN_SESSION_DURATION_MINUTES = 5;
|
|
|
50
62
|
* (0 chars, journal files) are safe to skip.
|
|
51
63
|
*/
|
|
52
64
|
const DEFAULT_MIN_CONTENT_CHARS = 10;
|
|
65
|
+
/**
|
|
66
|
+
* Default cap on NEW sessions the extract pass will LLM-process in a single run
|
|
67
|
+
* (`processes.extract.maxSessionsPerRun` overrides; `0` disables). Bounds per-run
|
|
68
|
+
* wall time + token spend so a backlog of accumulated sessions can't run a single
|
|
69
|
+
* pass past its scheduled-task timeout. Overflow sessions stay unseen and are
|
|
70
|
+
* processed by subsequent runs, so coverage is preserved — just spread out.
|
|
71
|
+
*/
|
|
72
|
+
const DEFAULT_MAX_SESSIONS_PER_RUN = 25;
|
|
73
|
+
/**
|
|
74
|
+
* Floor for the default discovery window (48h). When no explicit `--since` /
|
|
75
|
+
* `defaultSince` is configured, discovery looks back to the LAST recorded
|
|
76
|
+
* extract run for the harness (so an intermittently-online host that was off for
|
|
77
|
+
* days still rediscovers sessions that ended during the gap), but never LESS
|
|
78
|
+
* than this — looking back less than the prior window could drop a session that
|
|
79
|
+
* a previous run deferred via `maxSessionsPerRun`. Widening is free of redundant
|
|
80
|
+
* LLM cost: the content-hash ledger skips unchanged sessions with zero LLM calls.
|
|
81
|
+
*/
|
|
82
|
+
const DEFAULT_SINCE_FLOOR_MS = 48 * 60 * 60 * 1000;
|
|
83
|
+
/**
|
|
84
|
+
* Staleness window for the per-session extract lock. A single session's
|
|
85
|
+
* processing is bounded by the per-session LLM timeout (default 60s) plus the
|
|
86
|
+
* session-summary call, so a lock older than this must belong to a crashed
|
|
87
|
+
* holder and is safe to reclaim.
|
|
88
|
+
*/
|
|
89
|
+
const EXTRACT_SESSION_LOCK_STALE_MS = 5 * 60 * 1000;
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the discovery `sinceMs` cutoff when no explicit `since`/`defaultSince`
|
|
92
|
+
* is set: the later of (last recorded extract run for this harness) and
|
|
93
|
+
* (now − 48h). See {@link DEFAULT_SINCE_FLOOR_MS}. Best-effort — any state.db
|
|
94
|
+
* error falls back to the 48h floor.
|
|
95
|
+
*/
|
|
96
|
+
function resolveDefaultSinceMs(harnessName, now, opts) {
|
|
97
|
+
const floor = now - DEFAULT_SINCE_FLOOR_MS;
|
|
98
|
+
if (opts.skipTracking)
|
|
99
|
+
return floor;
|
|
100
|
+
try {
|
|
101
|
+
return withStateDb((db) => {
|
|
102
|
+
const lastRun = getLastExtractRunAt(db, harnessName);
|
|
103
|
+
return lastRun != null ? Math.min(lastRun, floor) : floor;
|
|
104
|
+
}, { path: opts.stateDbPath, borrowed: opts.stateDb });
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return floor;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Filesystem-safe per-session lock path, co-located with the state.db. */
|
|
111
|
+
function getExtractSessionLockPath(harness, sessionId, stateDbPath) {
|
|
112
|
+
const safe = `${harness}-${sessionId}`.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
113
|
+
return path.join(path.dirname(stateDbPath), "extract-locks", `extract-${safe}.lock`);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Try to claim the per-session extract lock so a concurrent extract (e.g. a
|
|
117
|
+
* session-end hook firing `--session-id` while the hourly improve pass runs
|
|
118
|
+
* discovery) cannot double-process the SAME session — duplicate LLM spend and
|
|
119
|
+
* near-duplicate proposals. Reclaims a stale lock (dead holder PID or age past
|
|
120
|
+
* {@link EXTRACT_SESSION_LOCK_STALE_MS}). Returns false when another LIVE run
|
|
121
|
+
* holds it — the caller then skips the session without any LLM call. Best-effort:
|
|
122
|
+
* any filesystem error resolves to `true` (proceed) so locking never blocks
|
|
123
|
+
* extraction outright.
|
|
124
|
+
*/
|
|
125
|
+
function acquireExtractSessionLock(lockPath) {
|
|
126
|
+
try {
|
|
127
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
128
|
+
if (tryAcquireLockSync(lockPath, String(process.pid)))
|
|
129
|
+
return true;
|
|
130
|
+
const probe = probeLock(lockPath, { staleAfterMs: EXTRACT_SESSION_LOCK_STALE_MS });
|
|
131
|
+
if (probe.state === "held")
|
|
132
|
+
return false;
|
|
133
|
+
// absent (released between attempt + probe) or stale → reclaim and retry once.
|
|
134
|
+
releaseLock(lockPath);
|
|
135
|
+
return tryAcquireLockSync(lockPath, String(process.pid));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
53
141
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
54
142
|
/**
|
|
55
143
|
* Parse a since-string into an absolute ms-epoch cutoff. Accepts:
|
|
@@ -112,9 +200,43 @@ function buildCandidateProposal(candidate, sourceRef) {
|
|
|
112
200
|
if (candidate.type === "lesson" && candidate.when_to_use) {
|
|
113
201
|
fm.when_to_use = candidate.when_to_use;
|
|
114
202
|
}
|
|
203
|
+
// #615 WS-0: preserve ordered-action + outcome data in frontmatter so the data
|
|
204
|
+
// survives even if source transcripts are not re-extractable later. The
|
|
205
|
+
// procedural-compilation feature (detection/compilation) is deferred to 0.10+.
|
|
206
|
+
if (candidate.orderedActions && candidate.orderedActions.length > 0) {
|
|
207
|
+
fm.orderedActions = candidate.orderedActions;
|
|
208
|
+
if (candidate.outcomeData) {
|
|
209
|
+
fm.outcomeData = candidate.outcomeData;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
115
212
|
const content = assembleAsset(fm, candidate.body);
|
|
116
213
|
return { ref, content, description };
|
|
117
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Canonicalize a session's content into a single deterministic string for
|
|
217
|
+
* hashing (#602). Each event is rendered `<role>\n<text>` and events are joined
|
|
218
|
+
* with a NUL-delimited separator (`\n\0\n`) so event boundaries cannot be forged
|
|
219
|
+
* by text that itself contains newlines.
|
|
220
|
+
*
|
|
221
|
+
* The input is the RAW `data.events` stream — NOT the pre-filtered / truncated
|
|
222
|
+
* set — so the hash is stable across `maxTotalChars` (and any other pre-filter)
|
|
223
|
+
* config changes: changing config must NEVER change the hash (idempotency AC).
|
|
224
|
+
* `inlineRefs` and ref metadata (title, startedAt/endedAt timestamps) are
|
|
225
|
+
* deliberately EXCLUDED so clock/title churn (and an agent adding an inline
|
|
226
|
+
* `akm remember` mid-session) does not change the hash.
|
|
227
|
+
*/
|
|
228
|
+
function canonicalizeSessionContent(data) {
|
|
229
|
+
return data.events.map((e) => `${e.role ?? "unknown"}\n${e.text}`).join("\n\0\n");
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* sha256 (hex) of the normalized session content (#602). This is the byte-exact,
|
|
233
|
+
* clock-independent skip authority that replaced the old `session_ended_at`
|
|
234
|
+
* timestamp comparison. See {@link canonicalizeSessionContent} for exactly what
|
|
235
|
+
* is (and is not) hashed.
|
|
236
|
+
*/
|
|
237
|
+
export function hashSessionContent(data) {
|
|
238
|
+
return sha256Hex(canonicalizeSessionContent(data));
|
|
239
|
+
}
|
|
118
240
|
/**
|
|
119
241
|
* Process one session through the full pipeline: read → pre-filter → LLM →
|
|
120
242
|
* parse → createProposal-per-candidate. Returns the per-session result.
|
|
@@ -123,7 +245,21 @@ function buildCandidateProposal(candidate, sourceRef) {
|
|
|
123
245
|
* proposal validation failure) the session result records a warning and
|
|
124
246
|
* keeps going — one session's bad luck never aborts a multi-session run.
|
|
125
247
|
*/
|
|
126
|
-
async function processSession(harness, sessionRef, stashDir, config, llmConfig, chat, ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars,
|
|
248
|
+
async function processSession(harness, sessionRef, stashDir, config, llmConfig, chat, ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars,
|
|
249
|
+
// #626 — pre-LLM heuristic triage gate. Default-off (enabled:false) takes the
|
|
250
|
+
// exact pre-change path (no scorer call, no new skipReason).
|
|
251
|
+
// #641 — proceduralAwareFloor is opt-in, DEFAULT OFF.
|
|
252
|
+
triage, sessionIndexing, schemaSimilarityCtx,
|
|
253
|
+
// #602 — already-extracted skip moved INSIDE processSession: the content hash
|
|
254
|
+
// can only be computed after readSession, so the skip decision lives here. The
|
|
255
|
+
// prior row + bypass flags are threaded in from the caller. Skipping here still
|
|
256
|
+
// costs ZERO LLM calls (the expensive resource #602 protects); only the cheap
|
|
257
|
+
// file read is incurred.
|
|
258
|
+
prior, force,
|
|
259
|
+
// Stash authoring standards (convention/meta fact bodies) for non-wiki
|
|
260
|
+
// output. Resolved ONCE per run by the caller and threaded in so facts are
|
|
261
|
+
// not re-read per session. Empty string when none exist.
|
|
262
|
+
standardsContext) {
|
|
127
263
|
const warnings = [];
|
|
128
264
|
let data;
|
|
129
265
|
try {
|
|
@@ -141,6 +277,27 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
141
277
|
skipReason: "read_failed",
|
|
142
278
|
};
|
|
143
279
|
}
|
|
280
|
+
// #602 — content-hash skip. Computed on the RAW event stream immediately after
|
|
281
|
+
// a successful read, BEFORE the pre-filter / minContentChars / triage gates, so
|
|
282
|
+
// an unchanged session never reaches the LLM. Hash-based ⇒ clock-independent
|
|
283
|
+
// (immune to the Jun 11-12 timestamp double-extract/over-throttle bug). The skip
|
|
284
|
+
// applies UNIFORMLY — including explicit `--session-id` targeting (so a
|
|
285
|
+
// session-end hook firing `extract --session-id <id>` is idempotent). ONLY
|
|
286
|
+
// `--force` overrides it to re-extract a previously-extracted session.
|
|
287
|
+
const contentHash = hashSessionContent(data);
|
|
288
|
+
if (!force && shouldSkipAlreadyExtractedSession(prior, contentHash)) {
|
|
289
|
+
return {
|
|
290
|
+
sessionId: sessionRef.sessionId,
|
|
291
|
+
harness: harness.name,
|
|
292
|
+
candidateCount: 0,
|
|
293
|
+
proposalIds: [],
|
|
294
|
+
preFilter: { inputCount: 0, outputCount: 0, truncatedCount: 0 },
|
|
295
|
+
warnings: [`already extracted (content unchanged) at ${prior?.processed_at}; pass --force to re-process`],
|
|
296
|
+
skipped: true,
|
|
297
|
+
skipReason: "already_extracted",
|
|
298
|
+
contentHash,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
144
301
|
const filtered = preFilterSession(data, {
|
|
145
302
|
...(typeof maxTotalChars === "number" ? { maxTotalChars } : {}),
|
|
146
303
|
});
|
|
@@ -167,9 +324,42 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
167
324
|
warnings: [],
|
|
168
325
|
skipped: true,
|
|
169
326
|
skipReason: "too_short",
|
|
327
|
+
contentHash,
|
|
170
328
|
};
|
|
171
329
|
}
|
|
172
|
-
|
|
330
|
+
// #626 — pre-LLM heuristic triage gate. Runs AFTER minContentChars + the
|
|
331
|
+
// already-extracted skip check (both in the caller / above), BEFORE the
|
|
332
|
+
// extraction prompt and the session-asset write. When the session scores below
|
|
333
|
+
// the configured threshold we triage it out: no chat() call, no session asset,
|
|
334
|
+
// no proposals. Pure-heuristic — zero added LLM cost. Default-off → skipped.
|
|
335
|
+
if (triage.enabled) {
|
|
336
|
+
const t = scoreSessionTriage(data, triage.minScore, {
|
|
337
|
+
proceduralAwareFloor: triage.proceduralAwareFloor,
|
|
338
|
+
});
|
|
339
|
+
if (!t.pass) {
|
|
340
|
+
return {
|
|
341
|
+
sessionId: sessionRef.sessionId,
|
|
342
|
+
harness: harness.name,
|
|
343
|
+
candidateCount: 0,
|
|
344
|
+
proposalIds: [],
|
|
345
|
+
preFilter: {
|
|
346
|
+
inputCount: filtered.stats.inputCount,
|
|
347
|
+
outputCount: filtered.stats.outputCount,
|
|
348
|
+
truncatedCount: filtered.stats.truncatedCount,
|
|
349
|
+
},
|
|
350
|
+
warnings: [],
|
|
351
|
+
skipped: true,
|
|
352
|
+
skipReason: "triaged_out",
|
|
353
|
+
contentHash,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const prompt = buildExtractPrompt({
|
|
358
|
+
data,
|
|
359
|
+
events: filtered.events,
|
|
360
|
+
inlineRefs: data.inlineRefs,
|
|
361
|
+
...(standardsContext.trim() ? { standardsContext } : {}),
|
|
362
|
+
});
|
|
173
363
|
// #561 — ADDITIVE session indexing. Generate + write the session asset
|
|
174
364
|
// (`sessions/<harness>/<id>.md`). FAIL-OPEN: any failure only records a
|
|
175
365
|
// warning; it NEVER changes the proposal/skip outcome of extract. Returns the
|
|
@@ -183,6 +373,10 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
183
373
|
try {
|
|
184
374
|
const result = await writeSessionAsset(data, stashDir, sessionIndexing.generate);
|
|
185
375
|
if (result.written) {
|
|
376
|
+
// Write-path indexing (itself fail-open): standalone `akm extract`
|
|
377
|
+
// (session-end hook) has no post-loop reindex to pick this file up.
|
|
378
|
+
if (result.filePath)
|
|
379
|
+
await indexWrittenAssets(stashDir, [result.filePath]);
|
|
186
380
|
return {
|
|
187
381
|
...(result.ref ? { sessionAssetRef: result.ref } : {}),
|
|
188
382
|
...(result.logPath ? { sessionLogPath: result.logPath } : {}),
|
|
@@ -217,6 +411,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
217
411
|
warnings: ["session_extraction feature returned empty (disabled / timeout / error)"],
|
|
218
412
|
skipped: true,
|
|
219
413
|
skipReason: "llm_unavailable",
|
|
414
|
+
contentHash,
|
|
220
415
|
};
|
|
221
416
|
}
|
|
222
417
|
const payload = parseExtractPayload(llmRaw);
|
|
@@ -248,15 +443,38 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
248
443
|
truncatedCount: filtered.stats.truncatedCount,
|
|
249
444
|
},
|
|
250
445
|
warnings,
|
|
446
|
+
contentHash,
|
|
251
447
|
...sessionAsset,
|
|
252
448
|
};
|
|
253
449
|
}
|
|
450
|
+
// WS-3b step 0c: hot-probation intake buffer (#604).
|
|
451
|
+
// When enabled, system-generated extractions enter captureMode: hot-probation
|
|
452
|
+
// so they spend ONE consolidation cycle in probation before the deterministic
|
|
453
|
+
// dedup+quality pass promotes them. Default OFF.
|
|
454
|
+
// Reads the `default` profile deliberately: this per-session hot-probation
|
|
455
|
+
// flag lives inside the 18-arg `processSession`, which has no handle to the
|
|
456
|
+
// active profile, and threading a 19th positional arg for a DEFAULT-OFF flag
|
|
457
|
+
// isn't worth the param bloat. The extract STAGE toggle (in `akmExtract`,
|
|
458
|
+
// below) already honors `--profile`.
|
|
459
|
+
const hotProbationEnabled = getImproveProcessConfig(config, "extract")?.hotProbation?.enabled === true;
|
|
254
460
|
for (const candidate of payload.candidates) {
|
|
255
461
|
if (dryRun) {
|
|
256
462
|
proposalIds.push(`dry-run:${candidate.type}:${candidate.name}`);
|
|
257
463
|
continue;
|
|
258
464
|
}
|
|
259
465
|
try {
|
|
466
|
+
// WS-3b Step-0b: schema-similarity intake gate. When enabled and the
|
|
467
|
+
// candidate is a lesson/knowledge whose body embedding is within ε of an
|
|
468
|
+
// existing derived-layer node, down-prioritize by multiplying confidence by
|
|
469
|
+
// the penalty. PARITY: schemaSimilarityCtx is null when the flag is off →
|
|
470
|
+
// applySchemaSimilarityPenalty returns the original confidence untouched and
|
|
471
|
+
// never embeds. (Logic lives in schema-similarity-gate.ts so it is unit-testable.)
|
|
472
|
+
const gateResult = await applySchemaSimilarityPenalty(candidate, schemaSimilarityCtx, (text) => schemaSimilarityCtx?.embedFn
|
|
473
|
+
? schemaSimilarityCtx.embedFn(text)
|
|
474
|
+
: embed(text, schemaSimilarityCtx?.embeddingConfig));
|
|
475
|
+
const effectiveConfidence = gateResult.effectiveConfidence;
|
|
476
|
+
if (gateResult.warning)
|
|
477
|
+
warn(gateResult.warning);
|
|
260
478
|
const { ref, content, description } = buildCandidateProposal(candidate, sessionRef);
|
|
261
479
|
const result = createProposal(stashDir, {
|
|
262
480
|
ref,
|
|
@@ -267,9 +485,22 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
267
485
|
frontmatter: {
|
|
268
486
|
description,
|
|
269
487
|
...(candidate.when_to_use ? { when_to_use: candidate.when_to_use } : {}),
|
|
270
|
-
...(
|
|
488
|
+
...(effectiveConfidence !== undefined ? { confidence: effectiveConfidence } : {}),
|
|
271
489
|
sources: [`session:${sessionRef.harness}:${sessionRef.sessionId}`],
|
|
272
490
|
evidence: candidate.evidence,
|
|
491
|
+
// #615 WS-0: mirror ordered-action + outcome data in the proposal
|
|
492
|
+
// frontmatter record so downstream tooling can read it without
|
|
493
|
+
// re-parsing the content body. Omitted when not present.
|
|
494
|
+
...(candidate.orderedActions && candidate.orderedActions.length > 0
|
|
495
|
+
? { orderedActions: candidate.orderedActions }
|
|
496
|
+
: {}),
|
|
497
|
+
...(candidate.outcomeData ? { outcomeData: candidate.outcomeData } : {}),
|
|
498
|
+
// WS-3b step 0c: tag system-generated extractions as hot-probation
|
|
499
|
+
// when the feature is enabled. The consolidation pass will exclude
|
|
500
|
+
// them from the LLM merge pool until the intake dedup+quality pass
|
|
501
|
+
// runs against them. User-explicit `akm remember` (captureMode: hot)
|
|
502
|
+
// is unaffected — this only applies to extract-generated proposals.
|
|
503
|
+
...(hotProbationEnabled ? buildHotProbationFrontmatter() : {}),
|
|
273
504
|
},
|
|
274
505
|
},
|
|
275
506
|
}, ctx);
|
|
@@ -310,6 +541,7 @@ async function processSession(harness, sessionRef, stashDir, config, llmConfig,
|
|
|
310
541
|
truncatedCount: filtered.stats.truncatedCount,
|
|
311
542
|
},
|
|
312
543
|
warnings,
|
|
544
|
+
contentHash,
|
|
313
545
|
...sessionAsset,
|
|
314
546
|
};
|
|
315
547
|
}
|
|
@@ -323,13 +555,22 @@ export async function akmExtract(options) {
|
|
|
323
555
|
const stashDir = options.stashDir ?? resolveStashDir();
|
|
324
556
|
const dryRun = options.dryRun ?? false;
|
|
325
557
|
const sourceRun = options.sourceRun ?? `extract-${timestampForFilename()}`;
|
|
326
|
-
// Read the per-process extract config from the
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
|
|
558
|
+
// Read the per-process extract config + enabled-state from the ACTIVE improve
|
|
559
|
+
// profile when `akmImprove` threaded it; otherwise fall back to the `default`
|
|
560
|
+
// profile (the explicit `akm extract` path, which has no active profile). This
|
|
561
|
+
// is what stops a non-default profile's `extract.enabled` from being silently
|
|
562
|
+
// overridden by the default profile and vice-versa.
|
|
563
|
+
const activeProfile = options.improveProfile;
|
|
564
|
+
const extractProcess = activeProfile ? activeProfile.processes?.extract : getImproveProcessConfig(config, "extract");
|
|
565
|
+
// The `extract.enabled` process toggle gates extract as a STAGE of `akm improve`
|
|
566
|
+
// (the activeProfile path) — consistent with #593/#594 where the active profile,
|
|
567
|
+
// not `default`, is the source of truth. An EXPLICIT `akm extract` invocation
|
|
568
|
+
// (no activeProfile) is a direct user/cron action and always runs; gating it on
|
|
569
|
+
// the default improve profile's stage toggle was a footgun — dropping extract
|
|
570
|
+
// from the daily improve profile would silently disable the standalone command.
|
|
571
|
+
const extractEnabled = activeProfile ? resolveProcessEnabled("extract", activeProfile) : true;
|
|
331
572
|
// Feature-gate early so we get a clean "skipped because disabled" envelope.
|
|
332
|
-
if (!
|
|
573
|
+
if (!extractEnabled) {
|
|
333
574
|
return {
|
|
334
575
|
schemaVersion: 1,
|
|
335
576
|
ok: true,
|
|
@@ -375,8 +616,17 @@ export async function akmExtract(options) {
|
|
|
375
616
|
// #595/#596 — minimum raw session size; sessions below it skip the LLM call
|
|
376
617
|
// entirely. Set `processes.extract.minContentChars: 0` to disable the gate.
|
|
377
618
|
const minContentChars = typeof extractProcess?.minContentChars === "number" ? extractProcess.minContentChars : DEFAULT_MIN_CONTENT_CHARS;
|
|
619
|
+
// Cap on NEW sessions LLM-processed per run; 0 disables. Absent = default.
|
|
620
|
+
// Bounds per-run wall time / LLM cost so a backlog can't push a run past its
|
|
621
|
+
// task timeout — the overflow stays unseen and is picked up by later runs.
|
|
622
|
+
const maxSessionsPerRun = typeof extractProcess?.maxSessionsPerRun === "number"
|
|
623
|
+
? extractProcess.maxSessionsPerRun
|
|
624
|
+
: DEFAULT_MAX_SESSIONS_PER_RUN;
|
|
378
625
|
// Default discovery window — process config can override the built-in 24h.
|
|
379
626
|
const effectiveSince = options.since ?? extractProcess?.defaultSince;
|
|
627
|
+
// #626 — resolve the triage gate config once per run. Default-off → the
|
|
628
|
+
// per-session path never calls the scorer and emits no telemetry.
|
|
629
|
+
const triage = resolveTriageConfig(extractProcess);
|
|
380
630
|
// #561 — resolve session-indexing config. Default ON: we only reach this code
|
|
381
631
|
// when `session_extraction` is enabled AND an LLM is configured (both checked
|
|
382
632
|
// above), so defaulting on costs nothing offline (the summary call fails open)
|
|
@@ -465,7 +715,16 @@ export async function akmExtract(options) {
|
|
|
465
715
|
candidates = [target];
|
|
466
716
|
}
|
|
467
717
|
else {
|
|
468
|
-
|
|
718
|
+
// No explicit `--since`/`defaultSince` → default to "since the last run"
|
|
719
|
+
// (floored at 48h) so an intermittently-online host doesn't lose sessions
|
|
720
|
+
// that ended while it was off. See {@link resolveDefaultSinceMs}.
|
|
721
|
+
const sinceMs = effectiveSince
|
|
722
|
+
? parseSinceArg(effectiveSince)
|
|
723
|
+
: resolveDefaultSinceMs(harness.name, startMs, {
|
|
724
|
+
...(options.stateDb ? { stateDb: options.stateDb } : {}),
|
|
725
|
+
...(options.stateDbPath ? { stateDbPath: options.stateDbPath } : {}),
|
|
726
|
+
...(options.skipTracking ? { skipTracking: options.skipTracking } : {}),
|
|
727
|
+
});
|
|
469
728
|
candidates = harness.listSessions({
|
|
470
729
|
sinceMs,
|
|
471
730
|
...(options.location ? { location: options.location } : {}),
|
|
@@ -474,6 +733,10 @@ export async function akmExtract(options) {
|
|
|
474
733
|
const sessions = [];
|
|
475
734
|
let processedCount = 0;
|
|
476
735
|
let skippedCount = 0;
|
|
736
|
+
// #626 — per-run triage aggregation counters (counts-only telemetry, AC4).
|
|
737
|
+
let triageEvaluated = 0;
|
|
738
|
+
let triagePassed = 0;
|
|
739
|
+
let triagedOut = 0;
|
|
477
740
|
const allProposalIds = [];
|
|
478
741
|
const topLevelWarnings = [];
|
|
479
742
|
const chat = options.chat ?? chatCompletion;
|
|
@@ -497,39 +760,97 @@ export async function akmExtract(options) {
|
|
|
497
760
|
stateDb = undefined;
|
|
498
761
|
}
|
|
499
762
|
}
|
|
763
|
+
// WS-3b Step-0b: schema-similarity intake gate.
|
|
764
|
+
// DEFAULT ON since R3 (docs/design/improve-self-learning-analysis.md G5):
|
|
765
|
+
// extract is the highest-volume acquisition path with no LLM judge, so the
|
|
766
|
+
// cheap embedding-dedup check (one embed per lesson/knowledge candidate,
|
|
767
|
+
// fail-open) is the intake quality gate. Opt out via
|
|
768
|
+
// processes.extract.schemaSimilarity.enabled: false. The gate is inert in
|
|
769
|
+
// practice when no derived-layer embeddings exist (empty ctx → no penalty).
|
|
770
|
+
const schemaSimilarityCfg = extractProcess?.schemaSimilarity;
|
|
771
|
+
let schemaSimilarityCtx = null;
|
|
772
|
+
if (schemaSimilarityCfg?.enabled !== false) {
|
|
773
|
+
const derivedEmbeddings = options.schemaSimilarityEmbeddings ?? loadDerivedLayerEmbeddings();
|
|
774
|
+
schemaSimilarityCtx = {
|
|
775
|
+
config: { ...schemaSimilarityCfg, enabled: true },
|
|
776
|
+
derivedEmbeddings,
|
|
777
|
+
embeddingConfig: config.embedding,
|
|
778
|
+
embedFn: options.schemaSimilarityEmbedFn,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
// Stash authoring standards (convention/meta fact bodies) for non-wiki
|
|
782
|
+
// extract output. Resolved ONCE per run and threaded into each session's
|
|
783
|
+
// prompt so facts are not re-read per session.
|
|
784
|
+
const extractStandardsContext = resolveStashStandards(stashDir);
|
|
500
785
|
for (const summary of candidates) {
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
786
|
+
// #602 — the already-extracted skip moved INTO processSession (the content
|
|
787
|
+
// hash needs the session body, only available after readSession). The prior
|
|
788
|
+
// row + bypass flags are threaded through; an unchanged session returns
|
|
789
|
+
// skipReason 'already_extracted' WITHOUT any LLM call.
|
|
504
790
|
const prior = seenMap.get(summary.sessionId);
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
791
|
+
// Per-run cap on LLM-processed sessions (skip-tracked seen sessions above
|
|
792
|
+
// don't count). Single-session / --force modes bypass the cap (explicit
|
|
793
|
+
// intent). Overflow sessions are left unseen for the next run.
|
|
794
|
+
if (!options.sessionId && !options.force && maxSessionsPerRun > 0 && processedCount >= maxSessionsPerRun) {
|
|
795
|
+
topLevelWarnings.push(`Reached maxSessionsPerRun=${maxSessionsPerRun}; ${candidates.length - processedCount - skippedCount} session(s) deferred to a later run.`);
|
|
796
|
+
break;
|
|
797
|
+
}
|
|
798
|
+
// Q5 — per-session lock so two concurrent extracts (e.g. a session-end hook
|
|
799
|
+
// firing `--session-id` while the hourly improve discovery pass runs) can't
|
|
800
|
+
// both LLM-process the SAME session. The holder records the outcome; a
|
|
801
|
+
// second run skips without any LLM call. Engaged only for real cross-process
|
|
802
|
+
// runs (those that open their own state.db): dry-run is read-only, an
|
|
803
|
+
// injected `stateDb` handle is an in-process/test scenario with no cross-
|
|
804
|
+
// process race, and skip-tracking-off opts out entirely.
|
|
805
|
+
let sessionLockPath;
|
|
806
|
+
if (trackingEnabled && !dryRun && !options.stateDb) {
|
|
807
|
+
sessionLockPath = getExtractSessionLockPath(harness.name, summary.sessionId, options.stateDbPath ?? getStateDbPath());
|
|
808
|
+
if (!acquireExtractSessionLock(sessionLockPath)) {
|
|
809
|
+
sessions.push({
|
|
810
|
+
sessionId: summary.sessionId,
|
|
811
|
+
harness: harness.name,
|
|
812
|
+
candidateCount: 0,
|
|
813
|
+
proposalIds: [],
|
|
814
|
+
preFilter: { inputCount: 0, outputCount: 0, truncatedCount: 0 },
|
|
815
|
+
warnings: ["concurrent extract holds this session's lock — skipped (handled by the other run)"],
|
|
816
|
+
skipped: true,
|
|
817
|
+
skipReason: "locked_concurrent",
|
|
818
|
+
});
|
|
819
|
+
skippedCount += 1;
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
520
822
|
}
|
|
521
823
|
try {
|
|
522
|
-
const result = await processSession(harness, summary, stashDir, config, llmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars, sessionIndexing);
|
|
824
|
+
const result = await processSession(harness, summary, stashDir, config, llmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars, triage, sessionIndexing, schemaSimilarityCtx, prior, options.force === true, extractStandardsContext);
|
|
523
825
|
sessions.push(result);
|
|
826
|
+
// #626 — triage aggregation. A session reached the triage gate only when it
|
|
827
|
+
// was NOT already preempted by an earlier skip (read_failed / too_short /
|
|
828
|
+
// already_extracted handled above the processSession call). When triage is
|
|
829
|
+
// enabled, processSession either triages-out (skipReason 'triaged_out') or
|
|
830
|
+
// proceeds past the gate — both count as "evaluated".
|
|
831
|
+
if (triage.enabled) {
|
|
832
|
+
const preemptedBeforeTriage = result.skipReason === "read_failed" ||
|
|
833
|
+
result.skipReason === "too_short" ||
|
|
834
|
+
result.skipReason === "already_extracted";
|
|
835
|
+
if (!preemptedBeforeTriage) {
|
|
836
|
+
triageEvaluated += 1;
|
|
837
|
+
if (result.skipReason === "triaged_out")
|
|
838
|
+
triagedOut += 1;
|
|
839
|
+
else
|
|
840
|
+
triagePassed += 1;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
524
843
|
if (result.skipped)
|
|
525
844
|
skippedCount += 1;
|
|
526
845
|
else
|
|
527
846
|
processedCount += 1;
|
|
528
847
|
allProposalIds.push(...result.proposalIds);
|
|
529
|
-
// Persist outcome so the next run skips this session unless
|
|
530
|
-
//
|
|
531
|
-
// and should never poison the seen-table.
|
|
532
|
-
|
|
848
|
+
// Persist outcome so the next run skips this session unless its content
|
|
849
|
+
// changes. We only track non-dry-run paths — dry-run is for inspection
|
|
850
|
+
// and should never poison the seen-table. #602: an `already_extracted`
|
|
851
|
+
// skip is a no-op (the row already carries the matching hash), so don't
|
|
852
|
+
// re-write it — that keeps `processed_at` stable across unchanged runs.
|
|
853
|
+
if (trackingEnabled && stateDb && !dryRun && result.skipReason !== "already_extracted") {
|
|
533
854
|
try {
|
|
534
855
|
const outcome = result.skipped
|
|
535
856
|
? result.skipReason === "read_failed" || result.skipReason === "exception"
|
|
@@ -548,6 +869,16 @@ export async function akmExtract(options) {
|
|
|
548
869
|
proposalCount: result.proposalIds.length,
|
|
549
870
|
rationale: result.rationaleIfEmpty ?? null,
|
|
550
871
|
sourceRun,
|
|
872
|
+
// #602 — persist the freshly computed content hash so the NEXT run
|
|
873
|
+
// can compare byte-for-byte. read_failed (before hash) → null, which
|
|
874
|
+
// keeps the row eligible for retry (matches failed-row semantics).
|
|
875
|
+
// R4 — llm_unavailable (LLM was down) and triaged_out (deferred by the
|
|
876
|
+
// triage gate) are transient outcomes: persist null so the null-hash
|
|
877
|
+
// retry re-processes them on a later run instead of pinning them as
|
|
878
|
+
// "seen" forever against the current byte content.
|
|
879
|
+
contentHash: result.skipReason === "llm_unavailable" || result.skipReason === "triaged_out"
|
|
880
|
+
? null
|
|
881
|
+
: (result.contentHash ?? null),
|
|
551
882
|
metadata: {
|
|
552
883
|
preFilterInputCount: result.preFilter.inputCount,
|
|
553
884
|
preFilterOutputCount: result.preFilter.outputCount,
|
|
@@ -584,6 +915,10 @@ export async function akmExtract(options) {
|
|
|
584
915
|
});
|
|
585
916
|
skippedCount += 1;
|
|
586
917
|
}
|
|
918
|
+
finally {
|
|
919
|
+
if (sessionLockPath)
|
|
920
|
+
releaseLock(sessionLockPath);
|
|
921
|
+
}
|
|
587
922
|
}
|
|
588
923
|
// Close the state.db connection we opened. Callers that injected stateDb
|
|
589
924
|
// via the test seam own its lifecycle.
|
|
@@ -595,6 +930,20 @@ export async function akmExtract(options) {
|
|
|
595
930
|
// best-effort close
|
|
596
931
|
}
|
|
597
932
|
}
|
|
933
|
+
// #626 — counts-only triage telemetry (AC4). Exactly ONE aggregated event per
|
|
934
|
+
// run, emitted only when the gate was enabled and actually evaluated at least
|
|
935
|
+
// one session. No per-session events (avoids the log-spam the issue warns of).
|
|
936
|
+
if (triage.enabled && triageEvaluated > 0) {
|
|
937
|
+
appendEvent({
|
|
938
|
+
eventType: "extract_triaged",
|
|
939
|
+
metadata: {
|
|
940
|
+
evaluated: triageEvaluated,
|
|
941
|
+
passed: triagePassed,
|
|
942
|
+
triagedOut,
|
|
943
|
+
sourceRun,
|
|
944
|
+
},
|
|
945
|
+
}, options.ctx);
|
|
946
|
+
}
|
|
598
947
|
return {
|
|
599
948
|
schemaVersion: 1,
|
|
600
949
|
ok: true,
|
|
@@ -616,20 +965,34 @@ export async function akmExtract(options) {
|
|
|
616
965
|
* logic in {@link akmExtract} so the `#554 minNewSessions` gate in `improve`
|
|
617
966
|
* can decide whether the extract pass is worth running before any work begins.
|
|
618
967
|
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
968
|
+
* #602 — this gate is intentionally CHEAP: it does NOT read session bodies, so
|
|
969
|
+
* it cannot compute the content hash that {@link shouldSkipAlreadyExtractedSession}
|
|
970
|
+
* now uses. It therefore uses a CONSERVATIVE row-presence approximation: a
|
|
971
|
+
* session counts as "new" when there is NO prior row OR the prior row's
|
|
972
|
+
* `content_hash` is null (never-seen or backfill-eligible). A prior row WITH a
|
|
973
|
+
* non-null content_hash counts as NOT new — it MIGHT have changed, but the
|
|
974
|
+
* precise per-session hash check happens downstream in processSession, so an
|
|
975
|
+
* over-/under-count here only affects whether the pass RUNS, never whether a
|
|
976
|
+
* changed session is actually re-processed.
|
|
622
977
|
*/
|
|
623
978
|
export function countNewExtractCandidates(config, options = {}) {
|
|
624
|
-
const extractProcess = config.
|
|
979
|
+
const extractProcess = getImproveProcessConfig(config, "extract", options.improveProfile);
|
|
625
980
|
const effectiveSince = options.since ?? extractProcess?.defaultSince;
|
|
626
|
-
|
|
981
|
+
// Mirror akmExtract: when no explicit window is set, default per-harness to
|
|
982
|
+
// "since the last run" (floored at 48h) instead of a fixed 24h. Keeps this
|
|
983
|
+
// gate's discovery window identical to what akmExtract will actually scan.
|
|
984
|
+
const explicitSinceMs = effectiveSince ? parseSinceArg(effectiveSince) : undefined;
|
|
627
985
|
const harnesses = (options.harnesses ?? getAvailableHarnesses()).filter((h) => h.isAvailable());
|
|
628
986
|
let stateDb = options.stateDb;
|
|
629
987
|
let openedStateDb = false;
|
|
630
988
|
let total = 0;
|
|
631
989
|
try {
|
|
632
990
|
for (const harness of harnesses) {
|
|
991
|
+
const sinceMs = explicitSinceMs ??
|
|
992
|
+
resolveDefaultSinceMs(harness.name, Date.now(), {
|
|
993
|
+
...(options.stateDb ? { stateDb: options.stateDb } : {}),
|
|
994
|
+
...(options.stateDbPath ? { stateDbPath: options.stateDbPath } : {}),
|
|
995
|
+
});
|
|
633
996
|
const candidates = harness.listSessions({ sinceMs });
|
|
634
997
|
if (candidates.length === 0)
|
|
635
998
|
continue;
|
|
@@ -652,7 +1015,10 @@ export function countNewExtractCandidates(config, options = {}) {
|
|
|
652
1015
|
}
|
|
653
1016
|
for (const summary of candidates) {
|
|
654
1017
|
const prior = seenMap.get(summary.sessionId);
|
|
655
|
-
|
|
1018
|
+
// #602 row-presence approximation (see fn doc): a prior row WITH a
|
|
1019
|
+
// non-null content_hash is treated as not-new here; everything else
|
|
1020
|
+
// (never-seen, or null-hash backfill-eligible) counts as new.
|
|
1021
|
+
if (prior && prior.content_hash != null)
|
|
656
1022
|
continue;
|
|
657
1023
|
total += 1;
|
|
658
1024
|
}
|