akm-cli 0.8.7 → 0.8.14
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 +428 -0
- package/dist/assets/help/help-proposals.md +1 -2
- package/dist/assets/hints/cli-hints-full.md +34 -19
- package/dist/assets/hints/cli-hints-short.md +1 -1
- package/dist/assets/profiles/catchup.json +13 -0
- package/dist/assets/profiles/consolidate.json +13 -0
- package/dist/assets/profiles/frequent.json +13 -0
- package/dist/assets/tasks/core/backup.yml +4 -0
- package/dist/assets/tasks/core/extract.yml +4 -0
- package/dist/assets/tasks/core/improve.yml +4 -0
- package/dist/assets/tasks/core/index-refresh.yml +4 -0
- package/dist/assets/tasks/core/sync.yml +4 -0
- package/dist/assets/tasks/core/update-stashes.yml +4 -0
- package/dist/assets/tasks/core/version-check.yml +4 -0
- package/dist/assets/templates/html/default.html +78 -0
- package/dist/assets/templates/html/health.html +560 -0
- package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
- package/dist/cli/config-migrate.js +6 -6
- package/dist/cli/config-validate.js +4 -4
- package/dist/cli/confirm.js +3 -3
- package/dist/cli/parse-args.js +1 -1
- package/dist/cli/shared.js +72 -19
- package/dist/cli-node.mjs +26 -0
- package/dist/cli.js +206 -3866
- package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
- package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
- package/dist/commands/agent/contribute-cli.js +200 -0
- package/dist/commands/completions.js +1 -1
- package/dist/commands/config-cli.js +230 -3
- package/dist/commands/db-cli.js +2 -2
- package/dist/commands/env/env-cli.js +529 -0
- package/dist/commands/env/env.js +410 -0
- package/dist/commands/env/secret-cli.js +259 -0
- package/dist/commands/{secret.js → env/secret.js} +6 -47
- package/dist/commands/events.js +4 -4
- package/dist/commands/feedback-cli.js +18 -34
- package/dist/commands/graph/graph-cli.js +132 -0
- package/dist/commands/{graph.js → graph/graph.js} +22 -16
- package/dist/commands/health/checks.js +279 -0
- package/dist/commands/health/html-report.js +448 -0
- package/dist/commands/health.js +189 -266
- package/dist/commands/{consolidate.js → improve/consolidate.js} +48 -36
- package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
- package/dist/commands/{distill.js → improve/distill.js} +39 -18
- package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
- package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
- package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
- package/dist/commands/{extract.js → improve/extract.js} +221 -26
- package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +30 -4
- package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
- package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
- package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
- package/dist/commands/{improve.js → improve/improve.js} +672 -292
- package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
- package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
- package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/{reflect.js → improve/reflect.js} +58 -28
- package/dist/commands/improve/session-asset.js +248 -0
- package/dist/commands/lint/agent-linter.js +1 -1
- package/dist/commands/lint/base-linter.js +55 -37
- package/dist/commands/lint/command-linter.js +1 -1
- package/dist/commands/lint/default-linter.js +1 -1
- package/dist/commands/lint/env-key-rules.js +1 -1
- package/dist/commands/lint/index.js +19 -25
- package/dist/commands/lint/knowledge-linter.js +1 -1
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +8 -8
- package/dist/commands/lint/skill-linter.js +1 -1
- package/dist/commands/lint/task-linter.js +1 -1
- package/dist/commands/lint/workflow-linter.js +1 -1
- package/dist/commands/lint.js +1 -1
- package/dist/commands/observability-cli.js +244 -0
- package/dist/commands/proposal/drain-policies.js +3 -3
- package/dist/commands/proposal/drain.js +87 -15
- package/dist/commands/proposal/proposal-cli.js +490 -0
- package/dist/commands/{proposal.js → proposal/proposal.js} +17 -6
- package/dist/commands/{propose.js → proposal/propose.js} +11 -11
- package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
- package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
- package/dist/{core → commands/proposal/validators}/proposals.js +374 -345
- package/dist/commands/{curate.js → read/curate.js} +7 -7
- package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
- package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
- package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
- package/dist/commands/read/search-cli.js +207 -0
- package/dist/commands/{search.js → read/search.js} +22 -27
- package/dist/commands/{show.js → read/show.js} +31 -45
- package/dist/commands/registry-cli.js +8 -8
- package/dist/commands/remember.js +14 -10
- package/dist/commands/sources/add-cli.js +293 -0
- package/dist/commands/{history.js → sources/history.js} +27 -25
- package/dist/commands/{info.js → sources/info.js} +6 -6
- package/dist/commands/{init.js → sources/init.js} +6 -6
- package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
- package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
- package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
- package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
- package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
- package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
- package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
- package/dist/commands/sources/sources-cli.js +305 -0
- package/dist/commands/sources/stash-cli.js +219 -0
- package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
- package/dist/commands/tasks/default-tasks.js +173 -0
- package/dist/commands/tasks/tasks-cli.js +210 -0
- package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
- package/dist/commands/wiki-cli.js +307 -0
- package/dist/commands/workflow-cli.js +329 -0
- package/dist/core/action-contributors.js +1 -1
- package/dist/core/assert.js +40 -0
- package/dist/core/asset/asset-create.js +54 -0
- package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
- package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
- package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
- package/dist/core/{markdown.js → asset/markdown.js} +1 -1
- package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
- package/dist/core/best-effort.js +64 -0
- package/dist/core/common.js +32 -18
- package/dist/core/{config-io.js → config/config-io.js} +29 -19
- package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
- package/dist/core/{config-schema.js → config/config-schema.js} +50 -7
- package/dist/core/config/config-types.js +16 -0
- package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
- package/dist/core/{config.js → config/config.js} +10 -8
- package/dist/core/env-secret-ref.js +90 -0
- package/dist/core/errors.js +13 -3
- package/dist/core/events.js +27 -4
- package/dist/core/file-lock.js +1 -1
- package/dist/core/improve-types.js +48 -0
- package/dist/core/lesson-lint.js +2 -2
- package/dist/core/logs-db.js +304 -0
- package/dist/core/paths.js +2 -2
- package/dist/core/ripgrep/install.js +2 -2
- package/dist/core/ripgrep/resolve.js +2 -2
- package/dist/core/state-db.js +195 -60
- package/dist/core/text-truncation.js +148 -0
- package/dist/core/time.js +1 -1
- package/dist/core/write-source.js +98 -85
- package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
- package/dist/indexer/{db.js → db/db.js} +128 -118
- package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
- package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
- package/dist/indexer/ensure-index.js +4 -4
- package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
- package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
- package/dist/indexer/indexer.js +37 -30
- package/dist/indexer/init.js +54 -0
- package/dist/indexer/manifest.js +10 -10
- package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +141 -33
- package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
- package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
- package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
- package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
- package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
- package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
- package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
- package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
- package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
- package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
- package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
- package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
- package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
- package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
- package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
- package/dist/indexer/{walker.js → walk/walker.js} +4 -3
- package/dist/integrations/agent/builder-shared.js +39 -0
- package/dist/integrations/agent/builders.js +14 -81
- package/dist/integrations/agent/config.js +6 -4
- package/dist/integrations/agent/detect.js +1 -1
- package/dist/integrations/agent/index.js +23 -8
- package/dist/integrations/agent/prompts.js +2 -3
- package/dist/integrations/agent/runner.js +22 -3
- package/dist/integrations/agent/spawn.js +9 -10
- package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
- package/dist/integrations/harnesses/claude/config-import.js +70 -0
- package/dist/integrations/harnesses/claude/index.js +64 -0
- package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +32 -5
- package/dist/integrations/harnesses/index.js +144 -0
- package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
- package/dist/integrations/harnesses/opencode/config-import.js +82 -0
- package/dist/integrations/harnesses/opencode/index.js +59 -0
- package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
- package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
- package/dist/integrations/harnesses/types.js +43 -0
- package/dist/integrations/lockfile.js +7 -16
- package/dist/integrations/session-logs/index.js +82 -9
- package/dist/llm/call-ai.js +4 -4
- package/dist/llm/client.js +146 -6
- package/dist/llm/embedder.js +6 -6
- package/dist/llm/embedders/local.js +9 -22
- package/dist/llm/embedders/remote.js +2 -2
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +31 -12
- package/dist/llm/index-passes.js +1 -1
- package/dist/llm/memory-infer.js +12 -5
- package/dist/llm/metadata-enhance.js +2 -2
- package/dist/llm/usage-persist.js +77 -0
- package/dist/llm/usage-telemetry.js +103 -0
- package/dist/output/context.js +9 -46
- package/dist/output/html-render.js +73 -0
- package/dist/output/renderers.js +88 -58
- package/dist/output/shapes/curate.js +7 -3
- package/dist/output/shapes/distill.js +7 -3
- package/dist/output/shapes/env-list.js +18 -16
- package/dist/output/shapes/events.js +5 -4
- package/dist/output/shapes/helpers.js +19 -5
- package/dist/output/shapes/history.js +7 -3
- package/dist/output/shapes/passthrough.js +8 -11
- package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
- package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
- package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
- package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
- package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
- package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
- package/dist/output/shapes/registry-search.js +7 -3
- package/dist/output/shapes/registry.js +12 -0
- package/dist/output/shapes/search.js +7 -3
- package/dist/output/shapes/secret-list.js +18 -16
- package/dist/output/shapes/show.js +7 -3
- package/dist/output/shapes.js +55 -30
- package/dist/output/text/add.js +2 -3
- package/dist/output/text/clone.js +2 -3
- package/dist/output/text/config.js +2 -3
- package/dist/output/text/curate.js +4 -3
- package/dist/output/text/distill.js +2 -3
- package/dist/output/text/enable-disable.js +5 -4
- package/dist/output/text/env.js +13 -0
- package/dist/output/text/events.js +5 -4
- package/dist/output/text/feedback.js +4 -3
- package/dist/output/text/helpers.js +123 -40
- package/dist/output/text/history.js +2 -3
- package/dist/output/text/import.js +2 -3
- package/dist/output/text/index.js +2 -3
- package/dist/output/text/info.js +2 -3
- package/dist/output/text/init.js +2 -3
- package/dist/output/text/list.js +2 -3
- package/dist/output/text/proposal/producer.js +9 -0
- package/dist/output/text/proposal/proposal.js +13 -0
- package/dist/output/text/registry-commands.js +8 -7
- package/dist/output/text/registry.js +12 -0
- package/dist/output/text/remember.js +4 -3
- package/dist/output/text/remove.js +2 -3
- package/dist/output/text/save.js +2 -3
- package/dist/output/text/search.js +4 -3
- package/dist/output/text/show.js +4 -3
- package/dist/output/text/update.js +2 -3
- package/dist/output/text/upgrade.js +2 -3
- package/dist/output/text/wiki.js +12 -11
- package/dist/output/text/workflow.js +12 -10
- package/dist/output/text.js +66 -32
- package/dist/registry/build-index.js +11 -10
- package/dist/registry/factory.js +1 -1
- package/dist/registry/origin-resolve.js +1 -1
- package/dist/registry/providers/index.js +2 -2
- package/dist/registry/providers/skills-sh.js +91 -72
- package/dist/registry/providers/static-index.js +75 -52
- package/dist/registry/resolve.js +3 -3
- package/dist/runtime.js +242 -0
- package/dist/scripts/migrate-storage.js +1654 -683
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +254 -168
- package/dist/setup/detect.js +311 -9
- package/dist/setup/harness-config-import.js +6 -120
- package/dist/setup/setup.js +454 -43
- package/dist/sources/include.js +1 -1
- package/dist/sources/provider-factory.js +2 -2
- package/dist/sources/providers/filesystem.js +3 -3
- package/dist/sources/providers/git.js +9 -9
- package/dist/sources/providers/index.js +4 -4
- package/dist/sources/providers/npm.js +6 -6
- package/dist/sources/providers/provider-utils.js +13 -20
- package/dist/sources/providers/sync-from-ref.js +5 -5
- package/dist/sources/providers/tar-utils.js +2 -2
- package/dist/sources/providers/website.js +2 -2
- package/dist/sources/resolve.js +5 -5
- package/dist/sources/website-ingest.js +5 -5
- package/dist/storage/database.js +102 -0
- package/dist/storage/engines/sqlite-migrations.js +42 -0
- package/dist/storage/locations.js +25 -0
- package/dist/storage/repositories/index-db.js +43 -0
- package/dist/storage/repositories/workflow-runs-repository.js +141 -0
- package/dist/tasks/backends/cron.js +4 -4
- package/dist/tasks/backends/exec-utils.js +32 -0
- package/dist/tasks/backends/index.js +3 -3
- package/dist/tasks/backends/launchd.js +7 -14
- package/dist/tasks/backends/schtasks.js +7 -16
- package/dist/tasks/embedded.js +71 -0
- package/dist/tasks/parser.js +2 -2
- package/dist/tasks/resolveAkmBin.js +1 -1
- package/dist/tasks/runner.js +127 -31
- package/dist/tasks/schedule.js +1 -1
- package/dist/tasks/validator.js +7 -7
- package/dist/text-import-hook.mjs +51 -0
- package/dist/version.js +2 -1
- package/dist/wiki/wiki.js +7 -7
- package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
- package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
- package/dist/workflows/cli.js +1 -1
- package/dist/workflows/db.js +54 -32
- package/dist/workflows/parser.js +4 -4
- package/dist/workflows/renderer.js +5 -5
- package/dist/workflows/runtime/agent-identity.js +56 -0
- package/dist/workflows/runtime/checkin.js +57 -0
- package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
- package/dist/workflows/validate-summary.js +82 -0
- package/docs/README.md +1 -1
- package/docs/data-and-telemetry.md +6 -6
- package/package.json +17 -8
- package/dist/commands/add-cli.js +0 -279
- package/dist/commands/env.js +0 -213
- package/dist/integrations/agent/sdk-runner.js +0 -126
- package/dist/output/shapes/vault-list.js +0 -19
- package/dist/output/text/proposal-producer.js +0 -8
- package/dist/output/text/proposal.js +0 -12
- package/dist/output/text/vault.js +0 -16
- /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
- /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
- /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
- /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
- /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
- /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
- /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
- /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
package/dist/commands/health.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
4
|
import fs from "node:fs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { getStateDbPathInDataDir } from "../core/paths";
|
|
10
|
-
import { openStateDatabase, queryTaskHistory } from "../core/state-db";
|
|
11
|
-
import { parseSinceToIso } from "../core/time";
|
|
12
|
-
import { readSemanticStatus } from "../indexer/semantic-status";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
5
|
+
import { ConfigError, UsageError } from "../core/errors.js";
|
|
6
|
+
import { appendEvent, readEvents } from "../core/events.js";
|
|
7
|
+
import { buildTaskRunId, getLoggedRunIds, openLogsDatabase } from "../core/logs-db.js";
|
|
8
|
+
import { getStateDbPathInDataDir } from "../core/paths.js";
|
|
9
|
+
import { listExistingTableNames, openStateDatabase, queryCompletedTaskIntervals, queryImproveRuns, queryTaskHistory, } from "../core/state-db.js";
|
|
10
|
+
import { parseSinceToIso } from "../core/time.js";
|
|
11
|
+
import { readSemanticStatus } from "../indexer/search/semantic-status.js";
|
|
12
|
+
import { getExecutionLogCandidates } from "../integrations/session-logs/index.js";
|
|
13
|
+
import { LLM_USAGE_EVENT } from "../llm/usage-persist.js";
|
|
14
|
+
import { HEALTH_CHECKS } from "./health/checks.js";
|
|
15
15
|
const DEFAULT_SINCE_MS = 24 * 60 * 60 * 1000;
|
|
16
16
|
const IMPROVE_COMPLETED_EVENT = "improve_completed";
|
|
17
17
|
const HEALTH_PROBE_EVENT = "health_probe";
|
|
@@ -104,6 +104,7 @@ function createUnknownImproveMetrics() {
|
|
|
104
104
|
ran: false,
|
|
105
105
|
considered: 0,
|
|
106
106
|
cacheHits: 0,
|
|
107
|
+
retryAttempts: 0,
|
|
107
108
|
freshAttempts: 0,
|
|
108
109
|
splitParents: 0,
|
|
109
110
|
written: 0,
|
|
@@ -111,6 +112,7 @@ function createUnknownImproveMetrics() {
|
|
|
111
112
|
skippedChildExists: 0,
|
|
112
113
|
skippedAborted: 0,
|
|
113
114
|
unaccounted: 0,
|
|
115
|
+
htmlErrorCount: 0,
|
|
114
116
|
yieldEligibleRuns: 0,
|
|
115
117
|
yieldEligibleConsidered: 0,
|
|
116
118
|
yieldEligibleWritten: 0,
|
|
@@ -128,6 +130,8 @@ function createUnknownImproveMetrics() {
|
|
|
128
130
|
cacheHitRate: 0,
|
|
129
131
|
truncations: 0,
|
|
130
132
|
failures: 0,
|
|
133
|
+
htmlErrors: 0,
|
|
134
|
+
retryAttempts: 0,
|
|
131
135
|
durationMs: 0,
|
|
132
136
|
},
|
|
133
137
|
sessionExtraction: {
|
|
@@ -337,18 +341,26 @@ function projectRunMetrics(result) {
|
|
|
337
341
|
metrics.consolidation.mergedSecondaries += toFiniteNumber(consolidation.mergedSecondaries);
|
|
338
342
|
metrics.consolidation.failedChunkMemories += toFiniteNumber(consolidation.failedChunkMemories);
|
|
339
343
|
// Structured emitter (new on this branch): consolidate.ts now pushes
|
|
340
|
-
// `{
|
|
341
|
-
// post-LLM rejection.
|
|
342
|
-
//
|
|
344
|
+
// per-ref grouped `{ref, skips: [{op, reason}]}` entries to `skipReasons`
|
|
345
|
+
// for every deterministic post-LLM rejection. Each ref appears once but
|
|
346
|
+
// may carry multiple skips; aggregate every reason. Pre-fix envelopes have
|
|
347
|
+
// neither field, so be defensive.
|
|
343
348
|
const skipReasons = consolidation.skipReasons;
|
|
344
349
|
if (Array.isArray(skipReasons)) {
|
|
345
350
|
for (const entry of skipReasons) {
|
|
346
351
|
if (!entry || typeof entry !== "object")
|
|
347
352
|
continue;
|
|
348
|
-
const
|
|
349
|
-
if (
|
|
353
|
+
const skips = entry.skips;
|
|
354
|
+
if (!Array.isArray(skips))
|
|
350
355
|
continue;
|
|
351
|
-
|
|
356
|
+
for (const skip of skips) {
|
|
357
|
+
if (!skip || typeof skip !== "object")
|
|
358
|
+
continue;
|
|
359
|
+
const reason = skip.reason;
|
|
360
|
+
if (typeof reason !== "string" || !reason.trim())
|
|
361
|
+
continue;
|
|
362
|
+
metrics.consolidation.skipReasons[reason] = (metrics.consolidation.skipReasons[reason] ?? 0) + 1;
|
|
363
|
+
}
|
|
352
364
|
}
|
|
353
365
|
}
|
|
354
366
|
}
|
|
@@ -358,12 +370,14 @@ function projectRunMetrics(result) {
|
|
|
358
370
|
const writtenFacts = toFiniteNumber(memoryInference.writtenFacts);
|
|
359
371
|
metrics.memoryInference.considered += considered;
|
|
360
372
|
metrics.memoryInference.cacheHits += toFiniteNumber(memoryInference.cacheHits);
|
|
373
|
+
metrics.memoryInference.retryAttempts += toFiniteNumber(memoryInference.retryAttempts);
|
|
361
374
|
metrics.memoryInference.splitParents += toFiniteNumber(memoryInference.splitParents);
|
|
362
375
|
metrics.memoryInference.written += writtenFacts;
|
|
363
376
|
metrics.memoryInference.skippedNoFacts += toFiniteNumber(memoryInference.skippedNoFacts);
|
|
364
377
|
metrics.memoryInference.skippedChildExists += toFiniteNumber(memoryInference.skippedChildExists);
|
|
365
378
|
metrics.memoryInference.skippedAborted += toFiniteNumber(memoryInference.skippedAborted);
|
|
366
379
|
metrics.memoryInference.unaccounted += toFiniteNumber(memoryInference.unaccounted);
|
|
380
|
+
metrics.memoryInference.htmlErrorCount += toFiniteNumber(memoryInference.htmlErrorCount);
|
|
367
381
|
// Yield-rate gating: pre-cache-feature envelopes lack the `cacheHits`
|
|
368
382
|
// field entirely. Treating their `considered` as freshAttempts (since
|
|
369
383
|
// cacheHits=0) is mathematically tempting but operationally wrong —
|
|
@@ -391,6 +405,8 @@ function projectRunMetrics(result) {
|
|
|
391
405
|
metrics.graphExtraction.cacheMisses += toFiniteNumber(telemetry.cacheMisses);
|
|
392
406
|
metrics.graphExtraction.truncations += toFiniteNumber(telemetry.truncationCount);
|
|
393
407
|
metrics.graphExtraction.failures += toFiniteNumber(telemetry.failureCount);
|
|
408
|
+
metrics.graphExtraction.htmlErrors += toFiniteNumber(telemetry.htmlErrorCount);
|
|
409
|
+
metrics.graphExtraction.retryAttempts += toFiniteNumber(telemetry.retryAttempts);
|
|
394
410
|
}
|
|
395
411
|
}
|
|
396
412
|
metrics.graphExtraction.durationMs += toFiniteNumber(result.graphExtractionDurationMs);
|
|
@@ -520,6 +536,7 @@ function mergeImproveMetrics(dst, src) {
|
|
|
520
536
|
dst.memoryInference.skippedChildExists += src.memoryInference.skippedChildExists;
|
|
521
537
|
dst.memoryInference.skippedAborted += src.memoryInference.skippedAborted;
|
|
522
538
|
dst.memoryInference.unaccounted += src.memoryInference.unaccounted;
|
|
539
|
+
dst.memoryInference.htmlErrorCount += src.memoryInference.htmlErrorCount;
|
|
523
540
|
dst.memoryInference.yieldEligibleRuns += src.memoryInference.yieldEligibleRuns;
|
|
524
541
|
dst.memoryInference.yieldEligibleConsidered += src.memoryInference.yieldEligibleConsidered;
|
|
525
542
|
dst.memoryInference.yieldEligibleWritten += src.memoryInference.yieldEligibleWritten;
|
|
@@ -531,6 +548,7 @@ function mergeImproveMetrics(dst, src) {
|
|
|
531
548
|
dst.graphExtraction.cacheMisses += src.graphExtraction.cacheMisses;
|
|
532
549
|
dst.graphExtraction.truncations += src.graphExtraction.truncations;
|
|
533
550
|
dst.graphExtraction.failures += src.graphExtraction.failures;
|
|
551
|
+
dst.graphExtraction.htmlErrors += src.graphExtraction.htmlErrors;
|
|
534
552
|
dst.graphExtraction.durationMs += src.graphExtraction.durationMs;
|
|
535
553
|
dst.sessionExtraction.sessionsScanned += src.sessionExtraction.sessionsScanned;
|
|
536
554
|
dst.sessionExtraction.sessionsExtracted += src.sessionExtraction.sessionsExtracted;
|
|
@@ -539,15 +557,9 @@ function mergeImproveMetrics(dst, src) {
|
|
|
539
557
|
dst.sessionExtraction.warnings += src.sessionExtraction.warnings;
|
|
540
558
|
dst.sessionExtraction.durationMs += src.sessionExtraction.durationMs;
|
|
541
559
|
}
|
|
542
|
-
function loadImproveRunRows(db, since, until) {
|
|
543
|
-
const sql = until
|
|
544
|
-
? "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND started_at < ? AND dry_run = 0 ORDER BY started_at DESC"
|
|
545
|
-
: "SELECT id, started_at, completed_at, ok, scope_mode, scope_value, result_json FROM improve_runs WHERE started_at >= ? AND dry_run = 0 ORDER BY started_at DESC";
|
|
546
|
-
return (until ? db.prepare(sql).all(since, until) : db.prepare(sql).all(since));
|
|
547
|
-
}
|
|
548
560
|
function summarizeImproveRuns(db, since, until) {
|
|
549
561
|
const accum = createUnknownImproveMetrics();
|
|
550
|
-
const rows =
|
|
562
|
+
const rows = queryImproveRuns(db, since, until);
|
|
551
563
|
// Per-phase wall-time samples. Each entry is one envelope's durationMs for
|
|
552
564
|
// that phase. Phases that did not run on a given envelope are simply
|
|
553
565
|
// omitted (NOT counted as 0) so the median/p95 reflect actual phase work.
|
|
@@ -666,10 +678,7 @@ function loadTaskIntervals(db, since, until) {
|
|
|
666
678
|
const untilMs = until ? new Date(until).getTime() : Number.POSITIVE_INFINITY;
|
|
667
679
|
const widenedSince = new Date(sinceMs - 5 * 60 * 1000).toISOString();
|
|
668
680
|
const widenedUntil = Number.isFinite(untilMs) ? new Date(untilMs + 5 * 60 * 1000).toISOString() : undefined;
|
|
669
|
-
const
|
|
670
|
-
? "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND started_at < ? AND completed_at IS NOT NULL ORDER BY started_at"
|
|
671
|
-
: "SELECT started_at, completed_at FROM task_history WHERE task_id = 'akm-improve' AND started_at >= ? AND completed_at IS NOT NULL ORDER BY started_at";
|
|
672
|
-
const rows = (widenedUntil ? db.prepare(sql).all(widenedSince, widenedUntil) : db.prepare(sql).all(widenedSince));
|
|
681
|
+
const rows = queryCompletedTaskIntervals(db, widenedSince, widenedUntil);
|
|
673
682
|
const intervals = [];
|
|
674
683
|
for (const row of rows) {
|
|
675
684
|
const startMs = new Date(row.started_at).getTime();
|
|
@@ -702,18 +711,27 @@ function findContainingTaskInterval(timestampMs, intervals) {
|
|
|
702
711
|
return undefined;
|
|
703
712
|
}
|
|
704
713
|
function buildPerRunSummaries(db, since, until) {
|
|
705
|
-
const rows =
|
|
714
|
+
const rows = queryImproveRuns(db, since, until);
|
|
706
715
|
const taskIntervals = loadTaskIntervals(db, since, until);
|
|
707
716
|
const summaries = [];
|
|
708
717
|
for (const row of rows) {
|
|
709
718
|
const startMs = new Date(row.started_at).getTime();
|
|
710
719
|
const endMs = new Date(row.completed_at).getTime();
|
|
711
|
-
// Prefer the
|
|
712
|
-
//
|
|
713
|
-
//
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
720
|
+
// Prefer the improve_runs row's own (completed_at - started_at) delta:
|
|
721
|
+
// recordImproveRun now persists distinct start/end timestamps, so the
|
|
722
|
+
// row's own delta is the authoritative per-run wall time even for
|
|
723
|
+
// manually-invoked `akm improve` runs with no enclosing task_history.
|
|
724
|
+
// Only fall back to the task_history containing-interval join for legacy/
|
|
725
|
+
// backfill rows where started_at == completed_at (row delta is 0).
|
|
726
|
+
const hasRowDelta = Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs;
|
|
727
|
+
let wallTimeMs;
|
|
728
|
+
if (hasRowDelta) {
|
|
729
|
+
wallTimeMs = endMs - startMs;
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
const interval = Number.isFinite(startMs) ? findContainingTaskInterval(startMs, taskIntervals) : undefined;
|
|
733
|
+
wallTimeMs = interval?.durationMs ?? 0;
|
|
734
|
+
}
|
|
717
735
|
summaries.push(projectImproveRunSummary(row, wallTimeMs));
|
|
718
736
|
}
|
|
719
737
|
return summaries;
|
|
@@ -759,108 +777,11 @@ function probeStateDbRoundTrip(stateDbPath) {
|
|
|
759
777
|
}
|
|
760
778
|
return { ok: true, durationMs };
|
|
761
779
|
}
|
|
762
|
-
function runAgentProbe() {
|
|
763
|
-
const config = loadConfig();
|
|
764
|
-
// v2: check profiles.agent first
|
|
765
|
-
if (config.profiles?.agent) {
|
|
766
|
-
const defaultName = config.defaults?.agent;
|
|
767
|
-
const profileCount = Object.keys(config.profiles.agent).length;
|
|
768
|
-
if (profileCount === 0) {
|
|
769
|
-
return {
|
|
770
|
-
name: "agent-profile",
|
|
771
|
-
kind: "deterministic",
|
|
772
|
-
status: "unknown",
|
|
773
|
-
confidence: "high",
|
|
774
|
-
message: "No agent profiles configured in profiles.agent.",
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
const profileName = defaultName ?? Object.keys(config.profiles.agent)[0];
|
|
778
|
-
const profile = config.profiles.agent[profileName];
|
|
779
|
-
return {
|
|
780
|
-
name: "agent-profile",
|
|
781
|
-
kind: "deterministic",
|
|
782
|
-
status: "pass",
|
|
783
|
-
confidence: "high",
|
|
784
|
-
message: `v2 agent profile "${profileName}" configured (platform: ${profile?.platform ?? "unknown"}).`,
|
|
785
|
-
evidence: { profile: profileName, platform: profile?.platform, profileCount },
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
if (!config.profiles?.agent && !config.defaults?.agent) {
|
|
789
|
-
return {
|
|
790
|
-
name: "agent-profile",
|
|
791
|
-
kind: "deterministic",
|
|
792
|
-
status: "unknown",
|
|
793
|
-
confidence: "high",
|
|
794
|
-
message: "No agent config present.",
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
let profile;
|
|
798
|
-
try {
|
|
799
|
-
profile = requireAgentProfile(config);
|
|
800
|
-
}
|
|
801
|
-
catch (error) {
|
|
802
|
-
return {
|
|
803
|
-
name: "agent-profile",
|
|
804
|
-
kind: "deterministic",
|
|
805
|
-
status: "warn",
|
|
806
|
-
confidence: "high",
|
|
807
|
-
message: error instanceof Error ? error.message : String(error),
|
|
808
|
-
};
|
|
809
|
-
}
|
|
810
|
-
if (profile.sdkMode === true) {
|
|
811
|
-
return {
|
|
812
|
-
name: "agent-profile",
|
|
813
|
-
kind: "deterministic",
|
|
814
|
-
status: profile.model ? "pass" : "warn",
|
|
815
|
-
confidence: "high",
|
|
816
|
-
message: profile.model
|
|
817
|
-
? `SDK mode profile "${profile.name}" is configured.`
|
|
818
|
-
: `SDK mode profile "${profile.name}" has no explicit model.`,
|
|
819
|
-
evidence: { profile: profile.name, sdkMode: true, model: profile.model ?? null },
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
const detections = detectAgentCliProfiles(config);
|
|
823
|
-
const detection = detections.find((entry) => entry.name === profile.name);
|
|
824
|
-
if (!detection?.available) {
|
|
825
|
-
return {
|
|
826
|
-
name: "agent-profile",
|
|
827
|
-
kind: "deterministic",
|
|
828
|
-
status: "fail",
|
|
829
|
-
confidence: "high",
|
|
830
|
-
message: `Default agent profile "${profile.name}" is not available on PATH.`,
|
|
831
|
-
evidence: { profile: profile.name, bin: profile.bin },
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
const version = spawnSync(profile.bin, ["--version"], { encoding: "utf8", timeout: 5_000 });
|
|
835
|
-
if ((version.status ?? 1) !== 0) {
|
|
836
|
-
return {
|
|
837
|
-
name: "agent-profile",
|
|
838
|
-
kind: "deterministic",
|
|
839
|
-
status: "warn",
|
|
840
|
-
confidence: "medium",
|
|
841
|
-
message: `Agent binary "${profile.bin}" was found but \`--version\` failed.`,
|
|
842
|
-
evidence: {
|
|
843
|
-
profile: profile.name,
|
|
844
|
-
bin: profile.bin,
|
|
845
|
-
exitCode: version.status ?? null,
|
|
846
|
-
stderr: (version.stderr ?? "").trim(),
|
|
847
|
-
},
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
return {
|
|
851
|
-
name: "agent-profile",
|
|
852
|
-
kind: "deterministic",
|
|
853
|
-
status: "pass",
|
|
854
|
-
confidence: "high",
|
|
855
|
-
message: `Agent profile "${profile.name}" is available.`,
|
|
856
|
-
evidence: { profile: profile.name, bin: profile.bin, version: (version.stdout ?? "").trim() },
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
780
|
/**
|
|
860
781
|
* Parse a `--window-compare <duration>` shorthand into two adjacent windows
|
|
861
782
|
* (current, prior). Duration syntax matches {@link parseHealthSince}.
|
|
862
783
|
*/
|
|
863
|
-
function resolveWindowCompare(duration) {
|
|
784
|
+
function resolveWindowCompare(duration, now = () => Date.now()) {
|
|
864
785
|
const trimmed = duration.trim();
|
|
865
786
|
const durationMatch = trimmed.match(/^(\d+)([dhm])$/i);
|
|
866
787
|
if (!durationMatch) {
|
|
@@ -873,10 +794,10 @@ function resolveWindowCompare(duration) {
|
|
|
873
794
|
}
|
|
874
795
|
const multiplier = unit === "h" ? 60 * 60 * 1000 : unit === "m" ? 60 * 1000 : 24 * 60 * 60 * 1000;
|
|
875
796
|
const ms = amount * multiplier;
|
|
876
|
-
const
|
|
877
|
-
const currentSince = new Date(
|
|
878
|
-
const currentUntil = new Date(
|
|
879
|
-
const priorSince = new Date(
|
|
797
|
+
const nowMs = now();
|
|
798
|
+
const currentSince = new Date(nowMs - ms).toISOString();
|
|
799
|
+
const currentUntil = new Date(nowMs).toISOString();
|
|
800
|
+
const priorSince = new Date(nowMs - 2 * ms).toISOString();
|
|
880
801
|
const priorUntil = currentSince;
|
|
881
802
|
return [
|
|
882
803
|
{ name: "current", since: currentSince, until: currentUntil },
|
|
@@ -924,8 +845,10 @@ const INTERESTING_DELTA_PATHS = [
|
|
|
924
845
|
"improve.memoryInference.written",
|
|
925
846
|
"improve.memoryInference.yieldRate",
|
|
926
847
|
"improve.memoryInference.skippedNoFacts",
|
|
848
|
+
"improve.memoryInference.htmlErrorCount",
|
|
927
849
|
"improve.graphExtraction.cacheHitRate",
|
|
928
850
|
"improve.graphExtraction.failures",
|
|
851
|
+
"improve.graphExtraction.htmlErrors",
|
|
929
852
|
"improve.sessionExtraction.sessionsScanned",
|
|
930
853
|
"improve.sessionExtraction.proposalsCreated",
|
|
931
854
|
"improve.autoAccept.promoted",
|
|
@@ -961,17 +884,87 @@ function computeDeltas(first, last) {
|
|
|
961
884
|
}
|
|
962
885
|
return out;
|
|
963
886
|
}
|
|
964
|
-
|
|
887
|
+
/**
|
|
888
|
+
* Partition task_history rows into "should have a log" (non-null log_path) and
|
|
889
|
+
* "log is actually backed". A run counts as backed when logs.db holds rows for
|
|
890
|
+
* its run_id (#579 — the DB is the primary record); rows written before logs.db
|
|
891
|
+
* existed fall back to the transitional on-disk file check. `logsDb` may be
|
|
892
|
+
* undefined when logs.db could not be opened — then only the file check runs.
|
|
893
|
+
*/
|
|
894
|
+
function partitionLogBackedRows(taskRows, logsDb) {
|
|
895
|
+
const withLogs = taskRows.filter((row) => row.log_path !== null);
|
|
896
|
+
const loggedRunIds = logsDb
|
|
897
|
+
? getLoggedRunIds(logsDb, withLogs.map((row) => buildTaskRunId(row.task_id, row.started_at)))
|
|
898
|
+
: new Set();
|
|
899
|
+
const backed = withLogs.filter((row) => loggedRunIds.has(buildTaskRunId(row.task_id, row.started_at)) ||
|
|
900
|
+
(row.log_path !== null && fs.existsSync(row.log_path)));
|
|
901
|
+
return { withLogs, backed };
|
|
902
|
+
}
|
|
903
|
+
/** Stage key used for `llm_usage` events recorded outside any stage scope. */
|
|
904
|
+
const UNATTRIBUTED_STAGE = "unattributed";
|
|
905
|
+
function emptyLlmUsageStageAggregate() {
|
|
906
|
+
return {
|
|
907
|
+
calls: 0,
|
|
908
|
+
totalDurationMs: 0,
|
|
909
|
+
promptTokens: 0,
|
|
910
|
+
completionTokens: 0,
|
|
911
|
+
totalTokens: 0,
|
|
912
|
+
reasoningTokens: 0,
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
function emptyLlmUsageAggregate() {
|
|
916
|
+
return { ...emptyLlmUsageStageAggregate(), byStage: {} };
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Aggregate `llm_usage` events (#576) into a window total plus a per-stage
|
|
920
|
+
* breakdown of call count, wall-time, and token usage. Token fields absent from
|
|
921
|
+
* a best-effort record contribute 0. Calls with no `stage` land under
|
|
922
|
+
* {@link UNATTRIBUTED_STAGE}.
|
|
923
|
+
*/
|
|
924
|
+
function summarizeLlmUsage(events) {
|
|
925
|
+
const aggregate = emptyLlmUsageAggregate();
|
|
926
|
+
for (const event of events) {
|
|
927
|
+
const meta = event.metadata ?? {};
|
|
928
|
+
const stageKey = typeof meta.stage === "string" && meta.stage ? meta.stage : UNATTRIBUTED_STAGE;
|
|
929
|
+
let stage = aggregate.byStage[stageKey];
|
|
930
|
+
if (!stage) {
|
|
931
|
+
stage = emptyLlmUsageStageAggregate();
|
|
932
|
+
aggregate.byStage[stageKey] = stage;
|
|
933
|
+
}
|
|
934
|
+
const durationMs = toFiniteNumber(meta.durationMs);
|
|
935
|
+
const promptTokens = toFiniteNumber(meta.promptTokens);
|
|
936
|
+
const completionTokens = toFiniteNumber(meta.completionTokens);
|
|
937
|
+
const totalTokens = toFiniteNumber(meta.totalTokens);
|
|
938
|
+
const reasoningTokens = toFiniteNumber(meta.reasoningTokens);
|
|
939
|
+
for (const target of [aggregate, stage]) {
|
|
940
|
+
target.calls += 1;
|
|
941
|
+
target.totalDurationMs += durationMs;
|
|
942
|
+
target.promptTokens += promptTokens;
|
|
943
|
+
target.completionTokens += completionTokens;
|
|
944
|
+
target.totalTokens += totalTokens;
|
|
945
|
+
target.reasoningTokens += reasoningTokens;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return aggregate;
|
|
949
|
+
}
|
|
950
|
+
function readLlmUsageAggregate(stateDbPath, since, until) {
|
|
951
|
+
const events = readEvents({ since, type: LLM_USAGE_EVENT }, { dbPath: stateDbPath }).events.filter((event) => {
|
|
952
|
+
if (until === undefined)
|
|
953
|
+
return true;
|
|
954
|
+
return new Date(event.ts ?? since).getTime() < new Date(until).getTime();
|
|
955
|
+
});
|
|
956
|
+
return summarizeLlmUsage(events);
|
|
957
|
+
}
|
|
958
|
+
function buildWindowMetrics(db, stateDbPath, since, until, now = () => Date.now(), logsDb) {
|
|
965
959
|
const taskRows = queryTaskHistory(db, { since }).filter((row) => {
|
|
966
960
|
const startMs = new Date(row.started_at).getTime();
|
|
967
961
|
const untilMs = new Date(until).getTime();
|
|
968
962
|
return !Number.isFinite(untilMs) || startMs < untilMs;
|
|
969
963
|
});
|
|
970
|
-
const taskRowsWithLogs
|
|
971
|
-
const existingLogRows = taskRowsWithLogs.filter((row) => row.log_path && fs.existsSync(row.log_path));
|
|
964
|
+
const { withLogs: taskRowsWithLogs, backed: existingLogRows } = partitionLogBackedRows(taskRows, logsDb);
|
|
972
965
|
const failedTaskRows = taskRows.filter((row) => row.status === "failed");
|
|
973
966
|
const activeRows = taskRows.filter((row) => row.status === "active");
|
|
974
|
-
const stuckActiveRuns = activeRows.filter((row) =>
|
|
967
|
+
const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
|
|
975
968
|
const promptRows = taskRows.filter((row) => row.target_kind === "prompt");
|
|
976
969
|
const promptFailures = promptRows.filter((row) => {
|
|
977
970
|
const detail = parseTaskMetadata(row).detail;
|
|
@@ -1002,6 +995,7 @@ function buildWindowMetrics(db, stateDbPath, since, until) {
|
|
|
1002
995
|
stuckActiveRuns,
|
|
1003
996
|
logBackingRate: roundRate(logBackingRate),
|
|
1004
997
|
probeRoundTripMs: null,
|
|
998
|
+
llmUsage: readLlmUsageAggregate(stateDbPath, since, until),
|
|
1005
999
|
};
|
|
1006
1000
|
return { improve: improveSummary, metrics, runs: runCount };
|
|
1007
1001
|
}
|
|
@@ -1027,8 +1021,9 @@ function validateAkmHealthOptions(options) {
|
|
|
1027
1021
|
}
|
|
1028
1022
|
export function akmHealth(options = {}) {
|
|
1029
1023
|
validateAkmHealthOptions(options);
|
|
1024
|
+
const now = options.now ?? (() => Date.now());
|
|
1030
1025
|
const since = parseHealthSince(options.since);
|
|
1031
|
-
const stateDbPath = getStateDbPathInDataDir();
|
|
1026
|
+
const stateDbPath = options.stateDbPath ?? getStateDbPathInDataDir();
|
|
1032
1027
|
const hardChecks = [];
|
|
1033
1028
|
const advisories = [];
|
|
1034
1029
|
const getExecutionLogCandidatesFn = options.getExecutionLogCandidatesFn ?? getExecutionLogCandidates;
|
|
@@ -1039,38 +1034,27 @@ export function akmHealth(options = {}) {
|
|
|
1039
1034
|
catch (error) {
|
|
1040
1035
|
throw new ConfigError(`Unable to open state.db: ${error instanceof Error ? error.message : String(error)}`, "INVALID_CONFIG_FILE");
|
|
1041
1036
|
}
|
|
1037
|
+
// logs.db backs the log-backing metric (#579). Best-effort: when it cannot
|
|
1038
|
+
// be opened, partitionLogBackedRows falls back to the on-disk file check, so
|
|
1039
|
+
// health never hard-fails on a missing/locked logs database.
|
|
1040
|
+
let logsDb;
|
|
1041
|
+
try {
|
|
1042
|
+
logsDb = openLogsDatabase(options.logsDbPath);
|
|
1043
|
+
}
|
|
1044
|
+
catch {
|
|
1045
|
+
logsDb = undefined;
|
|
1046
|
+
}
|
|
1042
1047
|
try {
|
|
1043
|
-
const tables = db
|
|
1044
|
-
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('events', 'task_history', 'proposals', 'schema_migrations') ORDER BY name")
|
|
1045
|
-
.all();
|
|
1048
|
+
const tables = listExistingTableNames(db, ["events", "task_history", "proposals", "schema_migrations"]);
|
|
1046
1049
|
const tableNames = tables.map((row) => row.name).sort();
|
|
1047
1050
|
const requiredTables = ["events", "proposals", "schema_migrations", "task_history"];
|
|
1048
1051
|
const missingTables = requiredTables.filter((name) => !tableNames.includes(name));
|
|
1049
|
-
hardChecks.push({
|
|
1050
|
-
name: "state-db-schema",
|
|
1051
|
-
kind: "deterministic",
|
|
1052
|
-
status: missingTables.length === 0 ? "pass" : "fail",
|
|
1053
|
-
confidence: "high",
|
|
1054
|
-
message: missingTables.length === 0
|
|
1055
|
-
? "state.db opened and required tables are present."
|
|
1056
|
-
: `state.db is missing required tables: ${missingTables.join(", ")}`,
|
|
1057
|
-
evidence: { path: stateDbPath, tables: tableNames },
|
|
1058
|
-
});
|
|
1059
1052
|
const probe = probeStateDbRoundTrip(stateDbPath);
|
|
1060
|
-
hardChecks.push({
|
|
1061
|
-
name: "state-db-round-trip",
|
|
1062
|
-
kind: "deterministic",
|
|
1063
|
-
status: probe.ok ? "pass" : "fail",
|
|
1064
|
-
confidence: "high",
|
|
1065
|
-
message: probe.ok ? "state.db append/read round-trip succeeded." : `state.db round-trip failed: ${probe.error}`,
|
|
1066
|
-
evidence: { path: stateDbPath, durationMs: probe.durationMs },
|
|
1067
|
-
});
|
|
1068
1053
|
const taskRows = queryTaskHistory(db, { since });
|
|
1069
|
-
const taskRowsWithLogs
|
|
1070
|
-
const existingLogRows = taskRowsWithLogs.filter((row) => row.log_path && fs.existsSync(row.log_path));
|
|
1054
|
+
const { withLogs: taskRowsWithLogs, backed: existingLogRows } = partitionLogBackedRows(taskRows, logsDb);
|
|
1071
1055
|
const failedTaskRows = taskRows.filter((row) => row.status === "failed");
|
|
1072
1056
|
const activeRows = taskRows.filter((row) => row.status === "active");
|
|
1073
|
-
const stuckActiveRuns = activeRows.filter((row) =>
|
|
1057
|
+
const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
|
|
1074
1058
|
const promptRows = taskRows.filter((row) => row.target_kind === "prompt");
|
|
1075
1059
|
const promptFailures = promptRows.filter((row) => {
|
|
1076
1060
|
const detail = parseTaskMetadata(row).detail;
|
|
@@ -1079,51 +1063,7 @@ export function akmHealth(options = {}) {
|
|
|
1079
1063
|
const logBackingRate = taskRowsWithLogs.length === 0 ? 1 : existingLogRows.length / taskRowsWithLogs.length;
|
|
1080
1064
|
const taskFailRate = taskRows.length === 0 ? 0 : failedTaskRows.length / taskRows.length;
|
|
1081
1065
|
const agentFailureRate = promptRows.length === 0 ? 0 : promptFailures.length / promptRows.length;
|
|
1082
|
-
hardChecks.push({
|
|
1083
|
-
name: "task-history-read",
|
|
1084
|
-
kind: "deterministic",
|
|
1085
|
-
status: "pass",
|
|
1086
|
-
confidence: "high",
|
|
1087
|
-
message: `Read ${taskRows.length} task-history row(s) since ${since}.`,
|
|
1088
|
-
evidence: { rows: taskRows.length, since },
|
|
1089
|
-
});
|
|
1090
|
-
hardChecks.push({
|
|
1091
|
-
name: "task-log-backing",
|
|
1092
|
-
kind: "deterministic",
|
|
1093
|
-
status: logBackingRate === 1 ? "pass" : "fail",
|
|
1094
|
-
confidence: "high",
|
|
1095
|
-
message: logBackingRate === 1
|
|
1096
|
-
? "Every task_history log_path resolved on disk."
|
|
1097
|
-
: `${taskRowsWithLogs.length - existingLogRows.length} task log(s) referenced in task_history are missing.`,
|
|
1098
|
-
evidence: { totalWithLogs: taskRowsWithLogs.length, existingLogs: existingLogRows.length },
|
|
1099
|
-
});
|
|
1100
|
-
hardChecks.push({
|
|
1101
|
-
name: "active-runs",
|
|
1102
|
-
kind: "deterministic",
|
|
1103
|
-
status: stuckActiveRuns === 0 ? "pass" : "warn",
|
|
1104
|
-
confidence: "high",
|
|
1105
|
-
message: stuckActiveRuns === 0
|
|
1106
|
-
? "No active task runs exceeded the stale threshold."
|
|
1107
|
-
: `${stuckActiveRuns} active task run(s) are older than ${Math.round(ACTIVE_RUN_WARN_MS / 60000)} minutes.`,
|
|
1108
|
-
evidence: { stuckActiveRuns },
|
|
1109
|
-
});
|
|
1110
|
-
hardChecks.push(runAgentProbe());
|
|
1111
1066
|
const semanticStatus = readSemanticStatus();
|
|
1112
|
-
advisories.push({
|
|
1113
|
-
name: "semantic-search-runtime",
|
|
1114
|
-
kind: "deterministic",
|
|
1115
|
-
status: !semanticStatus ||
|
|
1116
|
-
semanticStatus.status === "pending" ||
|
|
1117
|
-
semanticStatus.status === "ready-js" ||
|
|
1118
|
-
semanticStatus.status === "ready-vec"
|
|
1119
|
-
? "pass"
|
|
1120
|
-
: "warn",
|
|
1121
|
-
confidence: "medium",
|
|
1122
|
-
message: semanticStatus
|
|
1123
|
-
? `Semantic search status: ${semanticStatus.status}`
|
|
1124
|
-
: "No semantic-search runtime status recorded yet.",
|
|
1125
|
-
evidence: semanticStatus ? { ...semanticStatus } : undefined,
|
|
1126
|
-
});
|
|
1127
1067
|
const improveInvoked = readEvents({ since, type: "improve_invoked" }, { dbPath: stateDbPath }).events.length;
|
|
1128
1068
|
const improveCompletedEvents = readEvents({ since, type: IMPROVE_COMPLETED_EVENT }, { dbPath: stateDbPath }).events;
|
|
1129
1069
|
const improveSkippedEvents = readEvents({ since, type: "improve_skipped" }, { dbPath: stateDbPath }).events;
|
|
@@ -1139,7 +1079,7 @@ export function akmHealth(options = {}) {
|
|
|
1139
1079
|
improveSummary.wallTime = computeWallTimeStats(wallTimes, improveSummary.wallTime.byPhase);
|
|
1140
1080
|
let sessionLogEntries = [];
|
|
1141
1081
|
try {
|
|
1142
|
-
const sinceDays = Math.max(0, Math.ceil((
|
|
1082
|
+
const sinceDays = Math.max(0, Math.ceil((now() - new Date(since).getTime()) / (24 * 60 * 60 * 1000)));
|
|
1143
1083
|
sessionLogEntries = getExecutionLogCandidatesFn(sinceDays).map((entry) => ({
|
|
1144
1084
|
topic: entry.topic,
|
|
1145
1085
|
frequency: entry.frequency,
|
|
@@ -1150,65 +1090,40 @@ export function akmHealth(options = {}) {
|
|
|
1150
1090
|
catch {
|
|
1151
1091
|
sessionLogEntries = [];
|
|
1152
1092
|
}
|
|
1153
|
-
//
|
|
1154
|
-
//
|
|
1155
|
-
//
|
|
1156
|
-
//
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
? `Session extraction degraded: ${sxWarnReasons.join("; ")}.`
|
|
1181
|
-
: `Session extraction healthy: ${sx.sessionsScanned} scanned, ${sx.sessionsExtracted} extracted, ${sx.proposalsCreated} proposal(s) created.`
|
|
1182
|
-
: "Session extraction not active (feature disabled or no harness available).",
|
|
1183
|
-
evidence: {
|
|
1184
|
-
ran: sx.ran,
|
|
1185
|
-
sessionsScanned: sx.sessionsScanned,
|
|
1186
|
-
sessionsExtracted: sx.sessionsExtracted,
|
|
1187
|
-
sessionsSkipped: sx.sessionsSkipped,
|
|
1188
|
-
proposalsCreated: sx.proposalsCreated,
|
|
1189
|
-
warnings: sx.warnings,
|
|
1190
|
-
durationMs: sx.durationMs,
|
|
1191
|
-
},
|
|
1192
|
-
});
|
|
1193
|
-
const aa = improveSummary.autoAccept;
|
|
1194
|
-
advisories.push({
|
|
1195
|
-
name: "auto-accept-validation",
|
|
1196
|
-
kind: "heuristic",
|
|
1197
|
-
status: aa.validationFailed > 0 ? "warn" : "pass",
|
|
1198
|
-
confidence: aa.promoted + aa.validationFailed > 0 ? "high" : "low",
|
|
1199
|
-
message: aa.validationFailed > 0
|
|
1200
|
-
? `${aa.validationFailed} proposal(s) passed confidence threshold but failed auto-accept validation (truncated description, invalid frontmatter, etc.) — they remain in the queue for manual review.`
|
|
1201
|
-
: aa.promoted > 0
|
|
1202
|
-
? `Auto-accept healthy: ${aa.promoted} proposal(s) promoted, 0 validation failures.`
|
|
1203
|
-
: "Auto-accept gate did not run (disabled or no proposals above threshold).",
|
|
1204
|
-
evidence: { promoted: aa.promoted, validationFailed: aa.validationFailed },
|
|
1205
|
-
});
|
|
1093
|
+
// Run the ordered health-check registry. Each check projects the shared
|
|
1094
|
+
// context computed above into one HealthCheckResult; `channel` routes it to
|
|
1095
|
+
// hardChecks or advisories. Declaration order in HEALTH_CHECKS is the
|
|
1096
|
+
// emission order — see src/commands/health/checks.ts.
|
|
1097
|
+
const checkContext = {
|
|
1098
|
+
stateDbPath,
|
|
1099
|
+
since,
|
|
1100
|
+
tableNames,
|
|
1101
|
+
missingTables,
|
|
1102
|
+
probe,
|
|
1103
|
+
taskRowCount: taskRows.length,
|
|
1104
|
+
taskRowsWithLogsCount: taskRowsWithLogs.length,
|
|
1105
|
+
existingLogRowsCount: existingLogRows.length,
|
|
1106
|
+
logBackingRate,
|
|
1107
|
+
stuckActiveRuns,
|
|
1108
|
+
semanticStatus,
|
|
1109
|
+
sessionLogEntries,
|
|
1110
|
+
sessionExtraction: improveSummary.sessionExtraction,
|
|
1111
|
+
autoAccept: improveSummary.autoAccept,
|
|
1112
|
+
};
|
|
1113
|
+
for (const check of HEALTH_CHECKS) {
|
|
1114
|
+
const result = check.run(checkContext);
|
|
1115
|
+
if (check.channel === "hard")
|
|
1116
|
+
hardChecks.push(result);
|
|
1117
|
+
else
|
|
1118
|
+
advisories.push(result);
|
|
1119
|
+
}
|
|
1206
1120
|
const metrics = {
|
|
1207
1121
|
taskFailRate: roundRate(taskFailRate),
|
|
1208
1122
|
agentFailureRate: roundRate(agentFailureRate),
|
|
1209
1123
|
stuckActiveRuns,
|
|
1210
1124
|
logBackingRate: roundRate(logBackingRate),
|
|
1211
1125
|
probeRoundTripMs: probe.durationMs,
|
|
1126
|
+
llmUsage: readLlmUsageAggregate(stateDbPath, since),
|
|
1212
1127
|
};
|
|
1213
1128
|
const hardFailure = hardChecks.some((check) => check.status === "fail");
|
|
1214
1129
|
const deterministicWarnings = [...hardChecks, ...advisories].some((check) => check.status === "warn" && check.kind === "deterministic");
|
|
@@ -1216,7 +1131,7 @@ export function akmHealth(options = {}) {
|
|
|
1216
1131
|
// ── Window-compare mode (Phase 3) ─────────────────────────────────────
|
|
1217
1132
|
let windowSpecs;
|
|
1218
1133
|
if (options.windowCompare) {
|
|
1219
|
-
windowSpecs = resolveWindowCompare(options.windowCompare);
|
|
1134
|
+
windowSpecs = resolveWindowCompare(options.windowCompare, now);
|
|
1220
1135
|
}
|
|
1221
1136
|
else if (options.windows && options.windows.length > 0) {
|
|
1222
1137
|
windowSpecs = options.windows;
|
|
@@ -1229,8 +1144,8 @@ export function akmHealth(options = {}) {
|
|
|
1229
1144
|
if (windowSpecs && db) {
|
|
1230
1145
|
windowResults = windowSpecs.map((spec) => {
|
|
1231
1146
|
const winSince = parseHealthSince(spec.since);
|
|
1232
|
-
const winUntil = spec.until ? parseHealthSince(spec.until) : new Date().toISOString();
|
|
1233
|
-
const bundle = buildWindowMetrics(db, stateDbPath, winSince, winUntil);
|
|
1147
|
+
const winUntil = spec.until ? parseHealthSince(spec.until) : new Date(now()).toISOString();
|
|
1148
|
+
const bundle = buildWindowMetrics(db, stateDbPath, winSince, winUntil, now, logsDb);
|
|
1234
1149
|
return {
|
|
1235
1150
|
name: spec.name,
|
|
1236
1151
|
since: winSince,
|
|
@@ -1280,6 +1195,14 @@ export function akmHealth(options = {}) {
|
|
|
1280
1195
|
}
|
|
1281
1196
|
finally {
|
|
1282
1197
|
db.close();
|
|
1198
|
+
if (logsDb) {
|
|
1199
|
+
try {
|
|
1200
|
+
logsDb.close();
|
|
1201
|
+
}
|
|
1202
|
+
catch {
|
|
1203
|
+
// best-effort
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1283
1206
|
}
|
|
1284
1207
|
}
|
|
1285
1208
|
// ── Markdown renderers ───────────────────────────────────────────────────────
|