akm-cli 0.9.0-beta.6 → 0.9.0-rc.0
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 +663 -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/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 -14
- 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-cli.js +220 -227
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/env/secret-cli.js +140 -138
- package/dist/commands/feedback-cli.js +153 -147
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- 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 -1083
- 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 +1313 -1278
- 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 +243 -599
- 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 +55 -4
- 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 +160 -7
- package/dist/commands/improve/improve-cli.js +115 -73
- package/dist/commands/improve/improve-profiles.js +32 -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 +510 -2537
- 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 +115 -0
- 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 +212 -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 +7 -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 -882
- package/dist/commands/read/curate.js +419 -103
- 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/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 +29 -41
- 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 +15 -1
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +21 -4
- 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 +389 -58
- 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/logs-db.js +14 -75
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +21 -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 +142 -1091
- 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 +356 -780
- 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 +103 -24
- 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 +106 -0
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/indexer.js +291 -310
- 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 +9 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/builders.js +39 -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 +7 -2
- package/dist/integrations/agent/profiles.js +7 -1
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/agent/runner.js +13 -9
- package/dist/integrations/agent/spawn.js +69 -67
- package/dist/integrations/harnesses/claude/agent-builder.js +1 -1
- package/dist/integrations/harnesses/claude/index.js +2 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
- package/dist/integrations/harnesses/opencode/index.js +2 -0
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +98 -17
- package/dist/integrations/harnesses/types.js +1 -0
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/call-ai.js +2 -2
- package/dist/llm/client.js +57 -15
- 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 +85 -14
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +2 -1
- package/dist/output/text/helpers.js +29 -1
- package/dist/output/text/workflow.js +1 -0
- 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/scripts/migrate-storage.js +2718 -2354
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +891 -597
- 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/tar-utils.js +16 -8
- 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/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 +0 -0
- package/dist/wiki/wiki.js +52 -11
- package/dist/workflows/cli.js +1 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +43 -118
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/README.md +69 -18
- package/docs/data-and-telemetry.md +5 -4
- package/docs/migration/release-notes/0.7.0.md +1 -1
- 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
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Symmetric valence weighting for the improve eligibility sort (#614).
|
|
6
|
+
*
|
|
7
|
+
* BACKGROUND. The improve attention/eligibility ranking historically combined
|
|
8
|
+
* utility with a NEGATIVE-ONLY feedback term: `negative / (positive + negative)`.
|
|
9
|
+
* Under that formula a strong-positive asset contributes a feedback ratio of
|
|
10
|
+
* `0` — i.e. positive feedback never drives attention. Only complaints could
|
|
11
|
+
* lift an asset up the ranking, so a heavily-praised, heavily-used asset that
|
|
12
|
+
* deserves REINFORCEMENT (distill / promote the win) is treated identically to
|
|
13
|
+
* a never-rated one.
|
|
14
|
+
*
|
|
15
|
+
* FIX (gated, default-off). When symmetric valence is enabled we replace the
|
|
16
|
+
* negative-only ratio with a |valence| MAGNITUDE term so that BOTH strong
|
|
17
|
+
* positive and strong negative feedback drive attention. Utility remains the
|
|
18
|
+
* dominant ordering factor — valence is a secondary attention nudge with a
|
|
19
|
+
* small fixed weight, never a utility override.
|
|
20
|
+
*
|
|
21
|
+
* This module is intentionally pure and storage-free: it takes pre-aggregated
|
|
22
|
+
* positive/negative counts plus a utility lookup and returns a deterministic
|
|
23
|
+
* score and lane. All DB access stays in the caller.
|
|
24
|
+
*/
|
|
25
|
+
/** Weight on utility in the combined eligibility score. Utility is dominant. */
|
|
26
|
+
export const UTILITY_WEIGHT = 0.7;
|
|
27
|
+
/** Weight on the feedback attention term in the combined eligibility score. */
|
|
28
|
+
export const FEEDBACK_WEIGHT = 0.3;
|
|
29
|
+
/**
|
|
30
|
+
* Minimum |valence| magnitude for an item to be ROUTED to the fix / reinforce
|
|
31
|
+
* lane. Below this the feedback is too weak/mixed to be a confident signal and
|
|
32
|
+
* the item carries no attention lane (`null`). Pure magnitude in [0, 1].
|
|
33
|
+
*/
|
|
34
|
+
export const STRONG_VALENCE_THRESHOLD = 0.5;
|
|
35
|
+
/**
|
|
36
|
+
* Compute the symmetric-valence attention score for one asset's feedback.
|
|
37
|
+
*
|
|
38
|
+
* Deterministic: depends only on the integer counts. No clock, no randomness.
|
|
39
|
+
*/
|
|
40
|
+
export function computeValenceScore(counts) {
|
|
41
|
+
const positive = Math.max(0, counts.positive);
|
|
42
|
+
const negative = Math.max(0, counts.negative);
|
|
43
|
+
const total = positive + negative;
|
|
44
|
+
if (total === 0) {
|
|
45
|
+
return { valence: 0, magnitude: 0, attention: 0, lane: null };
|
|
46
|
+
}
|
|
47
|
+
const valence = (positive - negative) / total;
|
|
48
|
+
const magnitude = Math.abs(valence);
|
|
49
|
+
let lane = null;
|
|
50
|
+
if (magnitude >= STRONG_VALENCE_THRESHOLD) {
|
|
51
|
+
lane = valence < 0 ? "fix" : "reinforce";
|
|
52
|
+
}
|
|
53
|
+
return { valence, magnitude, attention: magnitude, lane };
|
|
54
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* WS-3b Step 0c — Hot-probation intake buffer (#604).
|
|
6
|
+
*
|
|
7
|
+
* New system-generated extractions enter `captureMode: hot-probation` and spend
|
|
8
|
+
* ONE consolidation cycle in probation before promotion to the main stash;
|
|
9
|
+
* dedup + quality second-pass runs against them. Stops noisy extractions from
|
|
10
|
+
* polluting the stash at the source. Reuses shared dedupHash + body_embeddings.
|
|
11
|
+
* Default OFF.
|
|
12
|
+
*
|
|
13
|
+
* @module hot-probation
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* captureMode value for system-generated extractions in probation.
|
|
17
|
+
* Automatic counterpart to the user-explicit `captureMode: hot`.
|
|
18
|
+
*/
|
|
19
|
+
export const CAPTURE_MODE_HOT_PROBATION = "hot-probation";
|
|
20
|
+
/**
|
|
21
|
+
* Returns true when an asset is in hot-probation (system-generated, not yet
|
|
22
|
+
* graduated from the intake dedup+quality pass).
|
|
23
|
+
*/
|
|
24
|
+
export function isHotProbation(captureModeValue) {
|
|
25
|
+
return captureModeValue === CAPTURE_MODE_HOT_PROBATION;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Returns true when an asset should be skipped by the consolidation LLM
|
|
29
|
+
* because it's still in hot-probation (hasn't completed the intake pass yet).
|
|
30
|
+
*
|
|
31
|
+
* Hot-probation assets are processed by the consolidation dedup pre-pass
|
|
32
|
+
* (runDeterministicDedup) but excluded from the LLM merge clustering, so
|
|
33
|
+
* noisy extractions can't pollute the LLM context.
|
|
34
|
+
*/
|
|
35
|
+
export function shouldSkipHotProbationInLlm(frontmatterData) {
|
|
36
|
+
return isHotProbation(frontmatterData.captureMode);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build frontmatter fields to inject when creating a hot-probation proposal.
|
|
40
|
+
* The proposal will carry `captureMode: hot-probation` so downstream logic
|
|
41
|
+
* knows to run the intake dedup pass before graduating it.
|
|
42
|
+
*/
|
|
43
|
+
export function buildHotProbationFrontmatter() {
|
|
44
|
+
return { captureMode: CAPTURE_MODE_HOT_PROBATION };
|
|
45
|
+
}
|
|
@@ -2,9 +2,34 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { loadConfig } from "../../core/config/config.js";
|
|
5
|
+
import { UsageError } from "../../core/errors.js";
|
|
5
6
|
import { appendEvent } from "../../core/events.js";
|
|
7
|
+
import { withStateDb } from "../../core/state-db.js";
|
|
6
8
|
import { info, warn } from "../../core/warn.js";
|
|
7
|
-
import {
|
|
9
|
+
import { getPhaseThreshold } from "../../storage/repositories/improve-runs-repository.js";
|
|
10
|
+
import { archiveProposal, getProposal, promoteProposal, recordGateDecision } from "../proposal/repository.js";
|
|
11
|
+
async function sha256Hex(input) {
|
|
12
|
+
const data = new TextEncoder().encode(input);
|
|
13
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
14
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Derive a stable, low-cardinality reason bucket from an auto-accept promotion
|
|
18
|
+
* error. `promoteProposal` throws a `validateProposal` report formatted as
|
|
19
|
+
* `[kind] message` lines; we extract the first finding kind. Non-validation
|
|
20
|
+
* throws collapse to `promote-error`.
|
|
21
|
+
*/
|
|
22
|
+
function classifyPromoteFailure(err) {
|
|
23
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
24
|
+
const finding = /\[([a-z][a-z0-9-]*)\]/i.exec(message);
|
|
25
|
+
if (finding)
|
|
26
|
+
return `validation:${finding[1]}`;
|
|
27
|
+
if (/not pending/i.test(message))
|
|
28
|
+
return "not-pending";
|
|
29
|
+
if (/unknown asset type/i.test(message))
|
|
30
|
+
return "unknown-type";
|
|
31
|
+
return "promote-error";
|
|
32
|
+
}
|
|
8
33
|
// ---------------------------------------------------------------------------
|
|
9
34
|
// Gate implementation
|
|
10
35
|
// ---------------------------------------------------------------------------
|
|
@@ -18,13 +43,32 @@ import { promoteProposal, recordGateDecision } from "../proposal/validators/prop
|
|
|
18
43
|
* @param promoteFn Injectable override for `promoteProposal` (test seam).
|
|
19
44
|
*/
|
|
20
45
|
export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProposal) {
|
|
21
|
-
const result = {
|
|
46
|
+
const result = {
|
|
47
|
+
promoted: [],
|
|
48
|
+
skipped: [],
|
|
49
|
+
failed: [],
|
|
50
|
+
suppressed: [],
|
|
51
|
+
failedByReason: {},
|
|
52
|
+
failedBySource: {},
|
|
53
|
+
};
|
|
22
54
|
// --- Guard: gate is disabled or context is incomplete ---
|
|
23
55
|
if (cfg.dryRun || cfg.globalThreshold === undefined || !cfg.stashDir) {
|
|
24
56
|
result.skipped = candidates.map((c) => c.proposalId);
|
|
25
57
|
return result;
|
|
26
58
|
}
|
|
27
|
-
|
|
59
|
+
// WS-4: per-phase threshold from state.db overrides the global threshold.
|
|
60
|
+
// The per-phase value is populated by makeGateConfig when a stateDbPath is
|
|
61
|
+
// available; callers that don't pass it get the global threshold unchanged.
|
|
62
|
+
const resolvedThreshold = cfg.phaseThreshold ?? cfg.globalThreshold;
|
|
63
|
+
const effectiveThreshold = Math.max(resolvedThreshold, cfg.minimumThreshold ?? 0) / 100;
|
|
64
|
+
// WS-4: Exploration budget — promote at most N candidates regardless of
|
|
65
|
+
// confidence to prevent the gate converging to pure exploitation.
|
|
66
|
+
// Exploration candidates are chosen from the LOWEST-confidence eligible set
|
|
67
|
+
// (i.e. those that would be deferred) so the budget truly samples the low-
|
|
68
|
+
// confidence tail and is meaningfully distinct from normal auto-accept.
|
|
69
|
+
// Promoted exploration proposals are logged with eligibilitySource="exploration".
|
|
70
|
+
const explorationBudget = cfg.explorationBudgetCount ?? 0;
|
|
71
|
+
let explorationRemaining = explorationBudget;
|
|
28
72
|
const resolvedConfig = typeof cfg.config === "function" ? cfg.config() : cfg.config;
|
|
29
73
|
const gateLabel = `improve:${cfg.phase}`;
|
|
30
74
|
// #577: stamp the gate's verdict onto each proposal so `akm proposal show`
|
|
@@ -40,7 +84,20 @@ export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProp
|
|
|
40
84
|
};
|
|
41
85
|
for (const candidate of candidates) {
|
|
42
86
|
const { proposalId, confidence } = candidate;
|
|
43
|
-
|
|
87
|
+
let currentProposal;
|
|
88
|
+
try {
|
|
89
|
+
currentProposal = cfg.stashDir ? getProposal(cfg.stashDir, proposalId) : undefined;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
currentProposal = undefined;
|
|
93
|
+
}
|
|
94
|
+
const currentContentHash = currentProposal ? await sha256Hex(currentProposal.payload.content) : undefined;
|
|
95
|
+
// Determine if this candidate is exploration-eligible: below-threshold
|
|
96
|
+
// (would normally be deferred) but with a valid confidence score and budget
|
|
97
|
+
// remaining. No-confidence candidates are never exploration-promoted.
|
|
98
|
+
const belowThreshold = confidence === undefined || confidence < effectiveThreshold;
|
|
99
|
+
const isExploration = belowThreshold && confidence !== undefined && explorationRemaining > 0;
|
|
100
|
+
if (belowThreshold && !isExploration) {
|
|
44
101
|
stamp(proposalId, {
|
|
45
102
|
outcome: "deferred",
|
|
46
103
|
reason: confidence === undefined ? "no-confidence" : "below-threshold",
|
|
@@ -51,15 +108,32 @@ export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProp
|
|
|
51
108
|
result.skipped.push(proposalId);
|
|
52
109
|
continue;
|
|
53
110
|
}
|
|
111
|
+
// Either above-threshold (normal auto-accept) or exploration-budget promoted.
|
|
112
|
+
if (isExploration)
|
|
113
|
+
explorationRemaining -= 1;
|
|
114
|
+
const promoteReason = isExploration ? "exploration-budget" : "above-threshold";
|
|
115
|
+
if (currentProposal?.gateDecision?.outcome === "auto-rejected" &&
|
|
116
|
+
currentProposal.gateDecision.contentHash !== undefined &&
|
|
117
|
+
currentProposal.gateDecision.contentHash === currentContentHash) {
|
|
118
|
+
result.suppressed.push(proposalId);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
54
121
|
try {
|
|
55
122
|
const promotion = await promoteFn(cfg.stashDir, resolvedConfig, proposalId, {}, undefined);
|
|
56
123
|
stamp(promotion.proposal.id, {
|
|
57
124
|
outcome: "auto-accepted",
|
|
58
|
-
reason:
|
|
125
|
+
reason: promoteReason,
|
|
59
126
|
confidence,
|
|
60
127
|
thresholds: { autoAccept: effectiveThreshold },
|
|
128
|
+
...(currentContentHash !== undefined ? { contentHash: currentContentHash } : {}),
|
|
61
129
|
gate: gateLabel,
|
|
62
130
|
});
|
|
131
|
+
// Resolve the eligibilitySource: exploration-promoted proposals get
|
|
132
|
+
// eligibilitySource="exploration" (WS-4); normal auto-accepts carry
|
|
133
|
+
// whatever the proposal was tagged with at selection time.
|
|
134
|
+
const resolvedEligibilitySource = isExploration
|
|
135
|
+
? "exploration"
|
|
136
|
+
: promotion.proposal.eligibilitySource;
|
|
63
137
|
appendEvent({
|
|
64
138
|
eventType: "promoted",
|
|
65
139
|
ref: promotion.ref,
|
|
@@ -72,14 +146,62 @@ export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProp
|
|
|
72
146
|
confidence,
|
|
73
147
|
threshold: effectiveThreshold,
|
|
74
148
|
phase: cfg.phase,
|
|
149
|
+
// Attribution tagging: carry the eligibility lane from the proposal
|
|
150
|
+
// record onto the auto-accept promoted event so the lane survives to
|
|
151
|
+
// accept time even when promotion happens in a later run.
|
|
152
|
+
...(resolvedEligibilitySource !== undefined ? { eligibilitySource: resolvedEligibilitySource } : {}),
|
|
153
|
+
// WS-4: mark exploration promotions so health/telemetry can
|
|
154
|
+
// distinguish them from calibration-signal promotions.
|
|
155
|
+
...(isExploration ? { explorationBudget: true } : {}),
|
|
75
156
|
},
|
|
76
157
|
}, cfg.eventsCtx ?? {});
|
|
77
|
-
|
|
158
|
+
if (isExploration) {
|
|
159
|
+
info(`[improve] exploration-accepted ${promotion.ref} (${cfg.phase}; confidence=${confidence.toFixed(2)}; budgetRemaining=${explorationRemaining})`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
info(`[improve] auto-accepted ${promotion.ref} (${cfg.phase}; confidence=${confidence.toFixed(2)} >= threshold=${effectiveThreshold.toFixed(2)})`);
|
|
163
|
+
}
|
|
78
164
|
result.promoted.push(proposalId);
|
|
79
165
|
}
|
|
80
166
|
catch (err) {
|
|
81
|
-
|
|
167
|
+
const reason = classifyPromoteFailure(err);
|
|
168
|
+
warn(`[improve] ${cfg.phase} auto-accept failed for ${proposalId} (${reason}): ${err instanceof Error ? err.message : String(err)}`);
|
|
82
169
|
result.failed.push(proposalId);
|
|
170
|
+
result.failedByReason[reason] = (result.failedByReason[reason] ?? 0) + 1;
|
|
171
|
+
// Record WHY on the proposal so `akm proposal show` explains the rejection
|
|
172
|
+
// and the leak is no longer blind. Best-effort.
|
|
173
|
+
stamp(proposalId, {
|
|
174
|
+
outcome: "auto-rejected",
|
|
175
|
+
reason,
|
|
176
|
+
confidence,
|
|
177
|
+
thresholds: { autoAccept: effectiveThreshold },
|
|
178
|
+
...(currentContentHash !== undefined ? { contentHash: currentContentHash } : {}),
|
|
179
|
+
gate: gateLabel,
|
|
180
|
+
});
|
|
181
|
+
// A validation failure is permanent — the minted content can never satisfy
|
|
182
|
+
// the asset schema, so archive it as rejected instead of leaving it pending
|
|
183
|
+
// to be retried on every future run (the 90-day TTL is otherwise the first
|
|
184
|
+
// thing ever to touch these zombies). Best-effort; ctx=undefined mirrors the
|
|
185
|
+
// promoteFn call above so the archive hits the same stashDir-derived DB.
|
|
186
|
+
//
|
|
187
|
+
// Discriminate on the STRUCTURED error, not the `reason` string: promoteProposal
|
|
188
|
+
// throws UsageError("MISSING_REQUIRED_ARGUMENT") only for the validateProposal
|
|
189
|
+
// failure branch. Message-sniffing (`reason` = "validation:<kind>") would also
|
|
190
|
+
// match a transient git-push rejection ("[rejected] ... non-fast-forward") from
|
|
191
|
+
// a git-backed write target and permanently archive a valid, retryable proposal.
|
|
192
|
+
const isPermanentValidationFailure = err instanceof UsageError && err.code === "MISSING_REQUIRED_ARGUMENT";
|
|
193
|
+
if (isPermanentValidationFailure) {
|
|
194
|
+
try {
|
|
195
|
+
archiveProposal(cfg.stashDir, proposalId, "rejected", `auto-accept ${reason}`, undefined);
|
|
196
|
+
}
|
|
197
|
+
catch (archiveErr) {
|
|
198
|
+
warn(`[improve] ${cfg.phase} failed to archive validation-failed proposal ${proposalId}: ${archiveErr instanceof Error ? archiveErr.message : String(archiveErr)}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// If exploration budget was consumed but promotion failed, restore the slot
|
|
202
|
+
// so the budget isn't exhausted on errors.
|
|
203
|
+
if (isExploration)
|
|
204
|
+
explorationRemaining += 1;
|
|
83
205
|
}
|
|
84
206
|
}
|
|
85
207
|
return result;
|
|
@@ -108,15 +230,46 @@ export function resolveExtractConfidence(proposal) {
|
|
|
108
230
|
/**
|
|
109
231
|
* Build a gate config for a phase, inheriting global settings from the
|
|
110
232
|
* improve options. Callers supply only the phase-specific overrides.
|
|
233
|
+
*
|
|
234
|
+
* WS-4 additions:
|
|
235
|
+
* - When `shared.stateDbPath` is provided, reads the persisted per-phase
|
|
236
|
+
* threshold from `improve_gate_thresholds` (Migration 012). The phase
|
|
237
|
+
* value overrides `globalThreshold` but is still floored by
|
|
238
|
+
* `minimumThreshold`. Falls back to `globalThreshold` when no row exists.
|
|
239
|
+
* - Computes `explorationBudgetCount` from
|
|
240
|
+
* `config.improve.exploration.budgetFraction × candidateCount` when the
|
|
241
|
+
* exploration budget is enabled. Defaults to 0 (no exploration).
|
|
111
242
|
*/
|
|
112
243
|
export function makeGateConfig(phase, shared, overrides = {}) {
|
|
244
|
+
// WS-4: read per-phase threshold from state.db when available.
|
|
245
|
+
let phaseThreshold;
|
|
246
|
+
if (shared.stateDbPath && shared.globalThreshold !== undefined) {
|
|
247
|
+
try {
|
|
248
|
+
phaseThreshold = withStateDb((db) => getPhaseThreshold(db, phase) ?? undefined, {
|
|
249
|
+
path: shared.stateDbPath,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// DB unavailable — fall back to globalThreshold silently.
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// WS-4: compute exploration budget count from config fraction × candidateCount.
|
|
257
|
+
let explorationBudgetCount;
|
|
258
|
+
const resolvedConfig = typeof shared.config === "function" ? shared.config() : shared.config;
|
|
259
|
+
const explorationCfg = resolvedConfig.improve?.exploration;
|
|
260
|
+
if (explorationCfg?.enabled && shared.candidateCount !== undefined && shared.candidateCount > 0) {
|
|
261
|
+
const fraction = Math.min(1, Math.max(0, explorationCfg.budgetFraction ?? 0.05));
|
|
262
|
+
explorationBudgetCount = Math.max(0, Math.floor(fraction * shared.candidateCount));
|
|
263
|
+
}
|
|
113
264
|
return {
|
|
114
265
|
phase,
|
|
115
266
|
globalThreshold: shared.globalThreshold,
|
|
267
|
+
...(phaseThreshold !== undefined ? { phaseThreshold } : {}),
|
|
116
268
|
dryRun: shared.dryRun,
|
|
117
269
|
stashDir: shared.stashDir,
|
|
118
270
|
config: shared.config,
|
|
119
271
|
eventsCtx: shared.eventsCtx,
|
|
272
|
+
...(explorationBudgetCount !== undefined && explorationBudgetCount > 0 ? { explorationBudgetCount } : {}),
|
|
120
273
|
...overrides,
|
|
121
274
|
};
|
|
122
275
|
}
|
|
@@ -8,15 +8,71 @@ import { output, runWithJsonErrors } from "../../cli/shared.js";
|
|
|
8
8
|
import { loadConfig } from "../../core/config/config.js";
|
|
9
9
|
import { UsageError } from "../../core/errors.js";
|
|
10
10
|
import { getCacheDir } from "../../core/paths.js";
|
|
11
|
+
import { withStateDb } from "../../core/state-db.js";
|
|
11
12
|
import { clearLogFile, setLogFile } from "../../core/warn.js";
|
|
13
|
+
import { closeDatabase, openExistingDatabase } from "../../indexer/db/db.js";
|
|
12
14
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
13
|
-
import {
|
|
15
|
+
import { parseFlagValue } from "../../output/context.js";
|
|
16
|
+
import { getActiveCanaries, queryRecentCycleMetrics } from "../../storage/repositories/canaries-repository.js";
|
|
17
|
+
import { refreshCanarySet } from "./collapse-detector.js";
|
|
14
18
|
import { akmImprove } from "./improve.js";
|
|
15
19
|
import { buildImproveRunId, recordTerminatedImproveRun, relativeImproveResultPath, writeImproveResultFile, } from "./improve-result-file.js";
|
|
20
|
+
import { runImproveSession } from "./improve-session.js";
|
|
21
|
+
// R5 — collapse-detector canary set inspection / explicit refresh. The
|
|
22
|
+
// detector NEVER auto-refreshes the canary set (silent re-baselining is how a
|
|
23
|
+
// slow collapse hides); this verb is the only refresh path.
|
|
24
|
+
//
|
|
25
|
+
// Dispatched from the parent improve run() on `scope === "canary"` — NOT a
|
|
26
|
+
// citty subCommand: registering subCommands makes citty treat EVERY first
|
|
27
|
+
// positional as a subcommand name, breaking `akm improve <type|ref>` outright
|
|
28
|
+
// (citty throws "Unknown command memory"), and citty also re-runs the parent
|
|
29
|
+
// run() after a matched subcommand.
|
|
30
|
+
async function runCanaryInspection(refresh) {
|
|
31
|
+
const config = loadConfig();
|
|
32
|
+
const cfg = config.improve?.collapseDetector ?? {};
|
|
33
|
+
const result = withStateDb((stateDb) => {
|
|
34
|
+
let refreshOutcome;
|
|
35
|
+
if (refresh) {
|
|
36
|
+
const indexDb = openExistingDatabase();
|
|
37
|
+
try {
|
|
38
|
+
// Mint-first, deactivate-after (refreshCanarySet): an empty/unreadable
|
|
39
|
+
// index keeps the old baseline instead of destroying it.
|
|
40
|
+
refreshOutcome = refreshCanarySet(stateDb, indexDb, cfg) === null ? "kept-old-set" : "refreshed";
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
closeDatabase(indexDb);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const canaries = getActiveCanaries(stateDb);
|
|
47
|
+
const canarySetId = canaries[0]?.canary_set_id;
|
|
48
|
+
const recentCycles = canarySetId ? queryRecentCycleMetrics(stateDb, canarySetId, cfg.windowCycles ?? 5) : [];
|
|
49
|
+
return {
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
ok: true,
|
|
52
|
+
refreshed: refreshOutcome === "refreshed",
|
|
53
|
+
...(refreshOutcome === "kept-old-set"
|
|
54
|
+
? { warning: "refresh skipped: no mintable learning entries in the index — existing canary set kept" }
|
|
55
|
+
: {}),
|
|
56
|
+
canarySetId: canarySetId ?? null,
|
|
57
|
+
canaries: canaries.map((c) => ({ id: c.id, anchorRef: c.anchor_ref, query: c.query })),
|
|
58
|
+
recentCycles: recentCycles.map((r) => ({
|
|
59
|
+
ts: r.ts,
|
|
60
|
+
pass: r.pass,
|
|
61
|
+
meanRecall: r.mean_recall,
|
|
62
|
+
meanNdcg: r.mean_ndcg,
|
|
63
|
+
distinctContentRatio: r.distinct_content_ratio,
|
|
64
|
+
acceptedActions: r.accepted_actions,
|
|
65
|
+
mergeFloorViolations: r.merge_floor_violations,
|
|
66
|
+
alerts: JSON.parse(r.alerts_json),
|
|
67
|
+
})),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
output("improve-canary", result);
|
|
71
|
+
}
|
|
16
72
|
export const improveCommand = defineCommand({
|
|
17
73
|
meta: {
|
|
18
74
|
name: "improve",
|
|
19
|
-
description: "Analyze existing AKM assets and generate improvement proposals; also consolidates memories when profiles.improve.default.processes.consolidate.enabled is true",
|
|
75
|
+
description: "Analyze existing AKM assets and generate improvement proposals; also consolidates memories when profiles.improve.default.processes.consolidate.enabled is true. `akm improve canary [--refresh]` inspects the collapse-detector canary set.",
|
|
20
76
|
},
|
|
21
77
|
args: {
|
|
22
78
|
scope: {
|
|
@@ -25,6 +81,11 @@ export const improveCommand = defineCommand({
|
|
|
25
81
|
required: false,
|
|
26
82
|
},
|
|
27
83
|
task: { type: "string", description: "Add extra guidance for this improvement pass" },
|
|
84
|
+
refresh: {
|
|
85
|
+
type: "boolean",
|
|
86
|
+
description: "(canary scope only) Mint a new collapse-detector canary set and deactivate the old one; old rows and their cycle history are retained",
|
|
87
|
+
default: false,
|
|
88
|
+
},
|
|
28
89
|
"dry-run": { type: "boolean", description: "Show planned actions without writing", default: false },
|
|
29
90
|
target: { type: "string", description: "Override the write target for accepted proposals" },
|
|
30
91
|
"auto-accept": {
|
|
@@ -73,37 +134,44 @@ export const improveCommand = defineCommand({
|
|
|
73
134
|
},
|
|
74
135
|
},
|
|
75
136
|
async run({ args }) {
|
|
137
|
+
// "canary" is a reserved scope word (never a valid asset type, and refs
|
|
138
|
+
// contain ":"): dispatch to the detector inspection verb instead of an
|
|
139
|
+
// improve run.
|
|
140
|
+
if (args.scope === "canary") {
|
|
141
|
+
await runWithJsonErrors(() => runCanaryInspection(args.refresh));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
76
144
|
await runWithJsonErrors(async () => {
|
|
77
145
|
const formatFlagValue = parseFlagValue(process.argv, "--format");
|
|
78
146
|
if (formatFlagValue !== undefined) {
|
|
79
147
|
throw new UsageError(`akm improve does not accept --format. That flag controls output formatting for other commands (search, show, etc.).\n` +
|
|
80
148
|
`Did you mean: akm improve (no --format flag)?`, "INVALID_FLAG_VALUE");
|
|
81
149
|
}
|
|
82
|
-
const jsonToStdout =
|
|
83
|
-
const autoAcceptRaw =
|
|
150
|
+
const jsonToStdout = args["json-to-stdout"];
|
|
151
|
+
const autoAcceptRaw = args["auto-accept"];
|
|
84
152
|
const autoAccept = parseAutoAcceptFlag(autoAcceptRaw);
|
|
85
153
|
const targetArg = getStringArg(args, "target");
|
|
86
154
|
const taskArg = getStringArg(args, "task");
|
|
87
|
-
const dryRun =
|
|
155
|
+
const dryRun = args["dry-run"];
|
|
88
156
|
const limitRaw = parsePositiveIntFlag(args.limit ?? undefined);
|
|
89
|
-
const timeoutMs = parsePositiveIntFlag(
|
|
90
|
-
const consolidateRecoveryRaw =
|
|
157
|
+
const timeoutMs = parsePositiveIntFlag(args["timeout-ms"], "--timeout-ms");
|
|
158
|
+
const consolidateRecoveryRaw = args["consolidate-recovery"];
|
|
91
159
|
const consolidateRecovery = consolidateRecoveryRaw === undefined
|
|
92
160
|
? undefined
|
|
93
161
|
: consolidateRecoveryRaw.trim().toLowerCase();
|
|
94
162
|
if (consolidateRecovery !== undefined && consolidateRecovery !== "abort" && consolidateRecovery !== "clean") {
|
|
95
163
|
throw new UsageError(`Invalid --consolidate-recovery value: "${consolidateRecoveryRaw}". Must be one of: abort, clean.`, "INVALID_FLAG_VALUE");
|
|
96
164
|
}
|
|
97
|
-
const minRetrievalCountRaw =
|
|
165
|
+
const minRetrievalCountRaw = args["min-retrieval-count"];
|
|
98
166
|
const minRetrievalCount = parseNonNegativeIntFlag(minRetrievalCountRaw, "--min-retrieval-count");
|
|
99
|
-
const requireFeedbackSignal =
|
|
100
|
-
const skipIfLocked =
|
|
167
|
+
const requireFeedbackSignal = args["require-feedback-signal"];
|
|
168
|
+
const skipIfLocked = args["skip-if-locked"];
|
|
101
169
|
const profileArg = getStringArg(args, "profile");
|
|
102
170
|
// Only set the keys the user actually passed (citty leaves the flag
|
|
103
171
|
// undefined unless `--sync`/`--no-sync` / `--push`/`--no-push` appears),
|
|
104
172
|
// so the resolved profile `sync` block wins by default.
|
|
105
|
-
const syncFlag =
|
|
106
|
-
const pushFlag =
|
|
173
|
+
const syncFlag = args.sync;
|
|
174
|
+
const pushFlag = args.push;
|
|
107
175
|
const syncOverride = {};
|
|
108
176
|
if (syncFlag !== undefined)
|
|
109
177
|
syncOverride.enabled = syncFlag;
|
|
@@ -144,67 +212,44 @@ export const improveCommand = defineCommand({
|
|
|
144
212
|
process.stderr.write(`warning: failed to persist terminated improve run ${runId}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
145
213
|
}
|
|
146
214
|
};
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// a SIGTERM'd run
|
|
151
|
-
// improve_runs
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
SIGTERM: { code: 143, reason: "SIGTERM", ack: true },
|
|
156
|
-
SIGINT: { code: 130, reason: "SIGINT", ack: true },
|
|
157
|
-
SIGHUP: { code: 129, reason: "SIGHUP", ack: false },
|
|
158
|
-
};
|
|
159
|
-
const makeSignalHandler = (sig) => () => {
|
|
160
|
-
const { code, reason, ack } = SIGNAL_TABLE[sig];
|
|
161
|
-
// Hard-exit fallback: if the synchronous persist ever hangs (e.g. a
|
|
162
|
-
// stuck sqlite lock under contention), the watchdog still exits with
|
|
163
|
-
// the correct code instead of leaving a zombie process. .unref() keeps
|
|
164
|
-
// the timer from holding the loop open on the normal (fast) path.
|
|
165
|
-
const watchdog = setTimeout(() => process.exit(code), 2000);
|
|
166
|
-
if (typeof watchdog.unref === "function")
|
|
167
|
-
watchdog.unref();
|
|
168
|
-
try {
|
|
169
|
-
persistTerminated(reason);
|
|
170
|
-
}
|
|
171
|
-
finally {
|
|
172
|
-
clearTimeout(watchdog);
|
|
173
|
-
}
|
|
174
|
-
if (ack) {
|
|
175
|
-
process.stderr.write(`[improve] received ${sig}; recorded terminated run ${runId}\n`);
|
|
176
|
-
}
|
|
177
|
-
process.exit(code);
|
|
178
|
-
};
|
|
179
|
-
const sigtermHandler = makeSignalHandler("SIGTERM");
|
|
180
|
-
const sigintHandler = makeSignalHandler("SIGINT");
|
|
181
|
-
const sighupHandler = makeSignalHandler("SIGHUP");
|
|
182
|
-
process.once("SIGTERM", sigtermHandler);
|
|
183
|
-
process.once("SIGINT", sigintHandler);
|
|
184
|
-
process.once("SIGHUP", sighupHandler);
|
|
215
|
+
// R8: the signal table / handlers / watchdog / persist-before-exit
|
|
216
|
+
// choreography lives in `runImproveSession`. It registers the
|
|
217
|
+
// SIGTERM/SIGINT/SIGHUP handlers (each persists the terminated-run row
|
|
218
|
+
// BEFORE process.exit so a SIGTERM'd run — e.g. cron timeout — always
|
|
219
|
+
// leaves a row in improve_runs), awaits the work, then removes the
|
|
220
|
+
// handlers on the way out. `onTerminate` persists synchronously
|
|
221
|
+
// (recordTerminatedImproveRun -> bun:sqlite writes are sync), and the
|
|
222
|
+
// 2000ms watchdog inside the session force-exits if that ever hangs.
|
|
185
223
|
let improveResult;
|
|
186
224
|
try {
|
|
187
|
-
improveResult = await
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
target: targetArg,
|
|
192
|
-
autoAccept,
|
|
193
|
-
...(runId !== undefined ? { runId } : {}),
|
|
194
|
-
...(limitRaw !== undefined ? { limit: limitRaw } : {}),
|
|
195
|
-
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
196
|
-
...(minRetrievalCount !== undefined ? { minRetrievalCount } : {}),
|
|
197
|
-
...(requireFeedbackSignal ? { requireFeedbackSignal } : {}),
|
|
198
|
-
...(skipIfLocked ? { skipIfLocked } : {}),
|
|
199
|
-
...(profileArg !== undefined ? { profile: profileArg } : {}),
|
|
200
|
-
...(Object.keys(syncOverride).length > 0 ? { sync: syncOverride } : {}),
|
|
201
|
-
consolidateOptions: {
|
|
202
|
-
target: targetArg,
|
|
225
|
+
improveResult = await runImproveSession({
|
|
226
|
+
runWork: () => akmImprove({
|
|
227
|
+
scope: scopeArg,
|
|
228
|
+
task: taskArg,
|
|
203
229
|
dryRun,
|
|
230
|
+
target: targetArg,
|
|
204
231
|
autoAccept,
|
|
205
|
-
|
|
206
|
-
...(
|
|
207
|
-
|
|
232
|
+
...(runId !== undefined ? { runId } : {}),
|
|
233
|
+
...(limitRaw !== undefined ? { limit: limitRaw } : {}),
|
|
234
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
235
|
+
...(minRetrievalCount !== undefined ? { minRetrievalCount } : {}),
|
|
236
|
+
...(requireFeedbackSignal ? { requireFeedbackSignal } : {}),
|
|
237
|
+
...(skipIfLocked ? { skipIfLocked } : {}),
|
|
238
|
+
...(profileArg !== undefined ? { profile: profileArg } : {}),
|
|
239
|
+
...(Object.keys(syncOverride).length > 0 ? { sync: syncOverride } : {}),
|
|
240
|
+
consolidateOptions: {
|
|
241
|
+
target: targetArg,
|
|
242
|
+
dryRun,
|
|
243
|
+
autoAccept,
|
|
244
|
+
task: taskArg,
|
|
245
|
+
...(consolidateRecovery !== undefined ? { recoveryMode: consolidateRecovery } : {}),
|
|
246
|
+
},
|
|
247
|
+
}),
|
|
248
|
+
}, {
|
|
249
|
+
signalSource: process,
|
|
250
|
+
exit: process.exit,
|
|
251
|
+
onTerminate: (reason) => persistTerminated(reason),
|
|
252
|
+
ack: (message) => process.stderr.write(`[improve] ${message}; recorded terminated run ${runId}\n`),
|
|
208
253
|
});
|
|
209
254
|
}
|
|
210
255
|
catch (err) {
|
|
@@ -216,9 +261,6 @@ export const improveCommand = defineCommand({
|
|
|
216
261
|
throw err;
|
|
217
262
|
}
|
|
218
263
|
finally {
|
|
219
|
-
process.removeListener("SIGTERM", sigtermHandler);
|
|
220
|
-
process.removeListener("SIGINT", sigintHandler);
|
|
221
|
-
process.removeListener("SIGHUP", sighupHandler);
|
|
222
264
|
clearLogFile();
|
|
223
265
|
}
|
|
224
266
|
const durationMs = Date.now() - startedAtMs;
|
|
@@ -246,7 +288,7 @@ export const improveCommand = defineCommand({
|
|
|
246
288
|
runRecorded = true; // Suppress any late signal-handler write — the success path owns the row now.
|
|
247
289
|
if (primaryStashDir) {
|
|
248
290
|
try {
|
|
249
|
-
writeImproveResultFile(primaryStashDir, runId, improveResult, startedAtIso);
|
|
291
|
+
writeImproveResultFile(primaryStashDir, runId, improveResult, startedAtIso, profileArg ?? null);
|
|
250
292
|
}
|
|
251
293
|
catch (err) {
|
|
252
294
|
// Stderr warning on the failure path is preferable to crashing
|