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
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* - z.record(...) → JSON-parse value, validate
|
|
22
22
|
*/
|
|
23
23
|
import { z } from "zod";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
24
|
+
import { UsageError } from "../errors.js";
|
|
25
|
+
import { AkmConfigBaseSchema, listTopLevelConfigKeys } from "./config-schema.js";
|
|
26
26
|
/**
|
|
27
27
|
* Parse a dotted path into segments. Empty segments are rejected. Bracket
|
|
28
28
|
* notation (e.g. `sources[0]`) is NOT supported — arrays are set as JSON.
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
export { stripJsonComments } from "./config-io";
|
|
11
|
-
import { getCacheDir, getConfigPath } from "
|
|
12
|
-
import { warn } from "
|
|
6
|
+
import { ConfigError } from "../errors.js";
|
|
7
|
+
import { backupExistingConfig, parseConfigText, withConfigLock, writeConfigAtomic } from "./config-io.js";
|
|
8
|
+
import { CURRENT_CONFIG_VERSION, compareConfigVersion, migrateConfigShape } from "./config-migration.js";
|
|
9
|
+
import { AkmConfigSchema } from "./config-schema.js";
|
|
10
|
+
export { stripJsonComments } from "./config-io.js";
|
|
11
|
+
import { getCacheDir, getConfigPath } from "../paths.js";
|
|
12
|
+
import { warn } from "../warn.js";
|
|
13
|
+
// Canonical harness-id source of truth (#565) — runtime value re-export.
|
|
14
|
+
export { VALID_HARNESS_IDS } from "./config-types.js";
|
|
13
15
|
// ── Feedback failure-mode constants (F-3 / #384) ────────────────────────────
|
|
14
16
|
/**
|
|
15
17
|
* Curated taxonomy of failure modes for negative feedback.
|
|
@@ -409,7 +411,7 @@ export function getIndexPassConfig(config, passName) {
|
|
|
409
411
|
return entry;
|
|
410
412
|
}
|
|
411
413
|
// Re-export source runtime helpers — implementation lives in config-sources.ts.
|
|
412
|
-
export { parseSourceSpec, resolveConfiguredSources } from "./config-sources";
|
|
414
|
+
export { parseSourceSpec, resolveConfiguredSources } from "./config-sources.js";
|
|
413
415
|
/**
|
|
414
416
|
* Merge a partial user-config override onto a base config. Used by
|
|
415
417
|
* {@link loadUserConfig} (DEFAULT_CONFIG + on-disk) and {@link updateConfig}
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
* Shared ref-resolution helpers for the `env` and `secret` command families
|
|
6
|
+
* (WS6). These were duplicated/co-located inline in `src/cli.ts`; hoisting them
|
|
7
|
+
* here lets `src/commands/env/env-cli.ts` and `src/commands/env/secret-cli.ts` import a
|
|
8
|
+
* single copy of the parse/resolve/make + path-traversal-guard logic (the WS6
|
|
9
|
+
* "env traversal-guard copies 5 → 1" KPI). Behaviour is byte-identical to the
|
|
10
|
+
* inline forms: env/secret VALUES are never read or surfaced here — these
|
|
11
|
+
* helpers only resolve refs to absolute paths and guard against directory
|
|
12
|
+
* traversal.
|
|
13
|
+
*/
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { resolveSourceEntries } from "../indexer/search/search-source.js";
|
|
16
|
+
import { assertFlatAssetName, combineCreatePath, normalizeCreateSubPath } from "./asset/asset-create.js";
|
|
17
|
+
import { parseAssetRef } from "./asset/asset-ref.js";
|
|
18
|
+
import { resolveAssetPathFromName } from "./asset/asset-spec.js";
|
|
19
|
+
import { isWithin } from "./common.js";
|
|
20
|
+
import { loadConfig } from "./config/config.js";
|
|
21
|
+
import { NotFoundError, UsageError } from "./errors.js";
|
|
22
|
+
export function parseEnvRef(ref) {
|
|
23
|
+
return parseAssetRef(ref.includes(":") ? ref : `env:${ref}`);
|
|
24
|
+
}
|
|
25
|
+
export function findEnvSource(origin) {
|
|
26
|
+
const sources = resolveSourceEntries(undefined, loadConfig());
|
|
27
|
+
if (sources.length === 0) {
|
|
28
|
+
throw new UsageError("No stashes configured. Run `akm init` to create your working stash.");
|
|
29
|
+
}
|
|
30
|
+
if (!origin || origin === "local")
|
|
31
|
+
return sources[0];
|
|
32
|
+
const named = sources.find((source) => source.registryId === origin);
|
|
33
|
+
if (!named) {
|
|
34
|
+
throw new NotFoundError(`Source not found for origin: ${origin}`);
|
|
35
|
+
}
|
|
36
|
+
return named;
|
|
37
|
+
}
|
|
38
|
+
export function makeEnvRef(name, source) {
|
|
39
|
+
return source?.registryId ? `${source.registryId}//env:${name}` : `env:${name}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve an env ref to an absolute `.env` path. Accepts `env:` and
|
|
43
|
+
* `environment:` (alias) refs as well as bare names. The path is returned even
|
|
44
|
+
* when the file does not yet exist (so `create` writes under `env/`).
|
|
45
|
+
*/
|
|
46
|
+
export function resolveEnvPath(ref) {
|
|
47
|
+
const parsed = parseEnvRef(ref);
|
|
48
|
+
if (parsed.type !== "env") {
|
|
49
|
+
throw new UsageError(`Expected an env ref (env:<name>); got "${ref}".`);
|
|
50
|
+
}
|
|
51
|
+
const source = findEnvSource(parsed.origin);
|
|
52
|
+
const envRoot = path.join(source.path, "env");
|
|
53
|
+
const envPath = resolveAssetPathFromName("env", envRoot, parsed.name);
|
|
54
|
+
// Defense-in-depth: ensure the resolved path stays inside the env directory.
|
|
55
|
+
// validateName already rejects traversal patterns like "../../foo", but an
|
|
56
|
+
// absolute-path override or symlink-based attack could still escape without
|
|
57
|
+
// this second check.
|
|
58
|
+
if (!isWithin(envPath, envRoot)) {
|
|
59
|
+
throw new UsageError(`Env name "${parsed.name}" escapes the env directory.`);
|
|
60
|
+
}
|
|
61
|
+
return { name: parsed.name, absPath: envPath, source, parsedRef: parsed, dir: "env" };
|
|
62
|
+
}
|
|
63
|
+
export function parseSecretRef(ref) {
|
|
64
|
+
return parseAssetRef(ref.includes(":") ? ref : `secret:${ref}`);
|
|
65
|
+
}
|
|
66
|
+
export function makeSecretRef(name, source) {
|
|
67
|
+
return source?.registryId ? `${source.registryId}//secret:${name}` : `secret:${name}`;
|
|
68
|
+
}
|
|
69
|
+
export function resolveSecretPath(ref,
|
|
70
|
+
// Create-only (`secret set`): enforce a flat ref name and apply `--path` as
|
|
71
|
+
// the subdirectory. Lookup callers omit this so nested refs keep resolving.
|
|
72
|
+
create) {
|
|
73
|
+
const parsed = parseSecretRef(ref);
|
|
74
|
+
if (parsed.type !== "secret") {
|
|
75
|
+
throw new UsageError(`Expected a secret ref (secret:<name>); got "${ref}".`);
|
|
76
|
+
}
|
|
77
|
+
if (create) {
|
|
78
|
+
assertFlatAssetName(parsed.name);
|
|
79
|
+
parsed.name = combineCreatePath(normalizeCreateSubPath(create.subPath), parsed.name);
|
|
80
|
+
}
|
|
81
|
+
// Source resolution is identical for every asset type; reuse the env helper.
|
|
82
|
+
const source = findEnvSource(parsed.origin);
|
|
83
|
+
const typeRoot = path.join(source.path, "secrets");
|
|
84
|
+
const absPath = resolveAssetPathFromName("secret", typeRoot, parsed.name);
|
|
85
|
+
// Defense-in-depth: ensure the resolved path stays inside the secrets dir.
|
|
86
|
+
if (!isWithin(absPath, typeRoot)) {
|
|
87
|
+
throw new UsageError(`Secret name "${parsed.name}" escapes the secrets directory.`);
|
|
88
|
+
}
|
|
89
|
+
return { name: parsed.name, absPath, source };
|
|
90
|
+
}
|
package/dist/core/errors.js
CHANGED
|
@@ -34,8 +34,16 @@ const NOT_FOUND_HINTS = {
|
|
|
34
34
|
WORKFLOW_NOT_FOUND: "Run `akm workflow list --active` to see runs.",
|
|
35
35
|
FILE_NOT_FOUND: "Check the path exists and is readable.",
|
|
36
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Base class for all akm-thrown, classified errors. Carries the `kind`
|
|
39
|
+
* discriminant consumed by the CLI exit-code classifier. Errors that are NOT
|
|
40
|
+
* instances of `AkmError` are treated as genuinely unexpected (INTERNAL).
|
|
41
|
+
*/
|
|
42
|
+
export class AkmError extends Error {
|
|
43
|
+
}
|
|
37
44
|
/** Raised when configuration or environment is invalid or missing. */
|
|
38
|
-
export class ConfigError extends
|
|
45
|
+
export class ConfigError extends AkmError {
|
|
46
|
+
kind = "config";
|
|
39
47
|
code;
|
|
40
48
|
_hint;
|
|
41
49
|
constructor(msg, code = "INVALID_CONFIG_FILE", hint) {
|
|
@@ -51,7 +59,8 @@ export class ConfigError extends Error {
|
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
/** Raised when the user supplies invalid arguments or input. */
|
|
54
|
-
export class UsageError extends
|
|
62
|
+
export class UsageError extends AkmError {
|
|
63
|
+
kind = "usage";
|
|
55
64
|
code;
|
|
56
65
|
_hint;
|
|
57
66
|
constructor(msg, code = "INVALID_FLAG_VALUE", hint) {
|
|
@@ -67,7 +76,8 @@ export class UsageError extends Error {
|
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
/** Raised when a requested resource (asset, entry, file) is not found. */
|
|
70
|
-
export class NotFoundError extends
|
|
79
|
+
export class NotFoundError extends AkmError {
|
|
80
|
+
kind = "not-found";
|
|
71
81
|
code;
|
|
72
82
|
_hint;
|
|
73
83
|
constructor(msg, code = "ASSET_NOT_FOUND", hint) {
|
package/dist/core/events.js
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Append-only events stream — backed by state.db (#204, Phase 3).
|
|
6
|
+
*
|
|
7
|
+
* Every mutating CLI verb funnels through `appendEvent` so external
|
|
8
|
+
* observers (sync, replication, audit, dashboards) can react to stash
|
|
9
|
+
* changes. Events are stored in the `events` table in `state.db`
|
|
10
|
+
* (SQLite, WAL mode) instead of a flat `events.jsonl` file.
|
|
11
|
+
*
|
|
12
|
+
* The helper is the only thing in akm that writes to the events table. It
|
|
13
|
+
* accepts an injectable `dbPath` (via `EventsContext`) so tests can pin a
|
|
14
|
+
* tmpdir without any global mutation.
|
|
15
|
+
*
|
|
16
|
+
* Format (each EventEnvelope):
|
|
17
|
+
* { "schemaVersion": 1, "id": <number>, "ts": "<ISO>",
|
|
18
|
+
* "eventType": "<verb>", "ref"?: "<asset-ref>", ... }
|
|
19
|
+
*
|
|
20
|
+
* - `id` is a monotonic SQLite AUTOINCREMENT rowid. Callers can persist it
|
|
21
|
+
* as a durable cursor for `--since` resumption (replaces the old byte-offset
|
|
22
|
+
* cursor). The public API still surfaces this as `nextOffset` (an opaque
|
|
23
|
+
* number) for backward compatibility with callers that stored byte-offset
|
|
24
|
+
* cursors.
|
|
25
|
+
* - `ts` is ISO-8601 (UTC, millisecond precision).
|
|
26
|
+
*/
|
|
4
27
|
import path from "node:path";
|
|
5
|
-
import { rethrowIfTestIsolationError } from "./errors";
|
|
6
|
-
import { getDataDir } from "./paths";
|
|
7
|
-
import { insertEvent, openStateDatabase, readStateEvents } from "./state-db";
|
|
8
|
-
import { error } from "./warn";
|
|
28
|
+
import { rethrowIfTestIsolationError } from "./errors.js";
|
|
29
|
+
import { getDataDir } from "./paths.js";
|
|
30
|
+
import { insertEvent, openStateDatabase, readStateEvents } from "./state-db.js";
|
|
31
|
+
import { error } from "./warn.js";
|
|
9
32
|
/**
|
|
10
33
|
* Legacy events.jsonl path — used only by the migration script
|
|
11
34
|
* (`scripts/migrate-storage.ts`) to import existing event history into
|
package/dist/core/file-lock.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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 fs from "node:fs";
|
|
5
|
-
import { isProcessAlive } from "./common";
|
|
5
|
+
import { isProcessAlive } from "./common.js";
|
|
6
6
|
/**
|
|
7
7
|
* Atomically create a sentinel at `lockPath` with `payload` as the body.
|
|
8
8
|
* Returns true if we now own the lock, false if a sentinel already
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { assertNever } from "./assert.js";
|
|
5
|
+
/**
|
|
6
|
+
* Map an {@link ImproveActionMode} to its coarse audit bucket. Single source of
|
|
7
|
+
* truth shared by `state-db.ts#computeImproveRunMetrics` and
|
|
8
|
+
* `improve.ts#emitImproveCompletedEvent` so the aggregate accepted/rejected/
|
|
9
|
+
* error counts can never disagree between the persisted `metrics_json` and the
|
|
10
|
+
* emitted `improve_completed` event.
|
|
11
|
+
*
|
|
12
|
+
* Buckets:
|
|
13
|
+
* - `accepted` — a write/content-authoring action succeeded.
|
|
14
|
+
* - `rejected` — the action was deliberately not applied (cooldown, skip,
|
|
15
|
+
* distill-skip, or a content-policy guard rejection). NOTE: as of the
|
|
16
|
+
* round-2 health pass `reflect-guard-rejected` is bucketed here. Previously
|
|
17
|
+
* the `state-db.ts` switch omitted it entirely (no case, no default), so a
|
|
18
|
+
* guard rejection silently vanished from accepted/rejected/error totals — a
|
|
19
|
+
* data-integrity miscount. It is a deliberate non-application of the action,
|
|
20
|
+
* so it belongs with the other "rejected" outcomes.
|
|
21
|
+
* - `error` — the action failed (LLM/runtime error).
|
|
22
|
+
* - `noop` — bookkeeping that is neither a write nor a rejection (memory-prune);
|
|
23
|
+
* intentionally counted in none of the three numeric buckets.
|
|
24
|
+
*
|
|
25
|
+
* The `default: assertNever(mode)` arm makes any future union variant a
|
|
26
|
+
* compile-time error here, forcing an explicit bucket choice.
|
|
27
|
+
*/
|
|
28
|
+
export function classifyImproveAction(mode) {
|
|
29
|
+
switch (mode) {
|
|
30
|
+
case "reflect":
|
|
31
|
+
case "distill":
|
|
32
|
+
case "memory-inference":
|
|
33
|
+
case "graph-extraction":
|
|
34
|
+
return "accepted";
|
|
35
|
+
case "reflect-cooldown":
|
|
36
|
+
case "reflect-skipped":
|
|
37
|
+
case "distill-skipped":
|
|
38
|
+
case "reflect-guard-rejected":
|
|
39
|
+
return "rejected";
|
|
40
|
+
case "reflect-failed":
|
|
41
|
+
case "error":
|
|
42
|
+
return "error";
|
|
43
|
+
case "memory-prune":
|
|
44
|
+
return "noop";
|
|
45
|
+
default:
|
|
46
|
+
return assertNever(mode);
|
|
47
|
+
}
|
|
48
|
+
}
|
package/dist/core/lesson-lint.js
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* without dragging in the rest of the runtime.
|
|
24
24
|
*/
|
|
25
25
|
import fs from "node:fs";
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
26
|
+
import { parseFrontmatter } from "./asset/frontmatter.js";
|
|
27
|
+
import { UsageError } from "./errors.js";
|
|
28
28
|
function isNonEmptyString(value) {
|
|
29
29
|
return typeof value === "string" && value.trim().length > 0;
|
|
30
30
|
}
|
|
@@ -0,0 +1,304 @@
|
|
|
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
|
+
* logs.db — Dedicated SQLite database for task/run log lines (#579).
|
|
6
|
+
*
|
|
7
|
+
* Replaces grep-the-flat-file consumption of `<cacheDir>/tasks/logs/<id>/<ts>.log`
|
|
8
|
+
* with structured, indexed rows: `{ts, task_id, run_id, stream, level, line}`.
|
|
9
|
+
* The strategic direction (stop scattering data across files/folders) means
|
|
10
|
+
* every NEW log consumer queries this database; the per-run text file written
|
|
11
|
+
* by the task runner is retained only as a transitional tail for humans —
|
|
12
|
+
* see docs/technical/logs-audit.md for the full producer audit.
|
|
13
|
+
*
|
|
14
|
+
* ## Why a separate database from state.db
|
|
15
|
+
*
|
|
16
|
+
* Log lines are high-volume, append-only, and freely purgeable; state.db rows
|
|
17
|
+
* (events, proposals, task_history) are durable records. Separating them keeps
|
|
18
|
+
* state.db small and lets log retention be aggressive without touching durable
|
|
19
|
+
* state. Cross-db queries (e.g. "failed task_history row → its log lines") use
|
|
20
|
+
* SQLite ATTACH — see {@link attachStateDatabase}.
|
|
21
|
+
*
|
|
22
|
+
* ## run_id
|
|
23
|
+
*
|
|
24
|
+
* state.db's `task_history` identifies a run by the unique pair
|
|
25
|
+
* `(task_id, started_at)` (see migration 002 in state-db.ts). logs.db encodes
|
|
26
|
+
* that pair as a single string — {@link buildTaskRunId} — so log rows can be
|
|
27
|
+
* joined back to their history row:
|
|
28
|
+
*
|
|
29
|
+
* l.run_id = th.task_id || '@' || th.started_at
|
|
30
|
+
*
|
|
31
|
+
* ## Schema evolution
|
|
32
|
+
*
|
|
33
|
+
* Same migration-safety contract as state.db: append-only `MIGRATIONS` applied
|
|
34
|
+
* through the shared runner in src/storage/engines/sqlite-migrations.ts.
|
|
35
|
+
*
|
|
36
|
+
* @module logs-db
|
|
37
|
+
*/
|
|
38
|
+
import fs from "node:fs";
|
|
39
|
+
import path from "node:path";
|
|
40
|
+
import { openDatabase } from "../storage/database.js";
|
|
41
|
+
import { runMigrations as runSqliteMigrations } from "../storage/engines/sqlite-migrations.js";
|
|
42
|
+
import { getDataDir } from "./paths.js";
|
|
43
|
+
import { getStateDbPath } from "./state-db.js";
|
|
44
|
+
// ── Path helper ──────────────────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Default path: `<dataDir>/logs.db` — alongside state.db so cooperating
|
|
47
|
+
* processes sharing a data root automatically share the same logs database
|
|
48
|
+
* (same `AKM_DATA_DIR` / XDG env-isolation as {@link getStateDbPath}).
|
|
49
|
+
*/
|
|
50
|
+
export function getLogsDbPath() {
|
|
51
|
+
return path.join(getDataDir(), "logs.db");
|
|
52
|
+
}
|
|
53
|
+
// ── Database open ────────────────────────────────────────────────────────────
|
|
54
|
+
/**
|
|
55
|
+
* Open (and initialise / migrate) the logs database.
|
|
56
|
+
*
|
|
57
|
+
* @param dbPath - Override the database file path (tests pass a tmpdir path).
|
|
58
|
+
*
|
|
59
|
+
* PRAGMA rationale:
|
|
60
|
+
*
|
|
61
|
+
* journal_mode = WAL
|
|
62
|
+
* Readers never block writers and vice-versa; crashes are safe (the WAL is
|
|
63
|
+
* replayed on next open). Required because the task runner writes log rows
|
|
64
|
+
* while `akm health` may be reading them.
|
|
65
|
+
*
|
|
66
|
+
* busy_timeout = 30000
|
|
67
|
+
* Log writes happen at the end of scheduled task runs, which can pile up
|
|
68
|
+
* (cron fan-out). 30 s of retry absorbs a slow concurrent writer instead of
|
|
69
|
+
* surfacing SQLITE_BUSY and dropping log lines.
|
|
70
|
+
*/
|
|
71
|
+
export function openLogsDatabase(dbPath) {
|
|
72
|
+
const resolvedPath = dbPath ?? getLogsDbPath();
|
|
73
|
+
const dir = path.dirname(resolvedPath);
|
|
74
|
+
if (!fs.existsSync(dir)) {
|
|
75
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
const db = openDatabase(resolvedPath);
|
|
78
|
+
// PRAGMAs must run before any DDL or DML.
|
|
79
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
80
|
+
db.exec("PRAGMA busy_timeout = 30000");
|
|
81
|
+
runMigrations(db);
|
|
82
|
+
return db;
|
|
83
|
+
}
|
|
84
|
+
// ── Migrations ───────────────────────────────────────────────────────────────
|
|
85
|
+
/**
|
|
86
|
+
* All migrations in application order. APPEND only — never insert in the
|
|
87
|
+
* middle or reorder. Same contract as state.db's MIGRATIONS array.
|
|
88
|
+
*/
|
|
89
|
+
const MIGRATIONS = [
|
|
90
|
+
// ── Migration 001 — task_logs ───────────────────────────────────────────────
|
|
91
|
+
//
|
|
92
|
+
// One row per log line emitted by a task run.
|
|
93
|
+
//
|
|
94
|
+
// Indexed (query) columns:
|
|
95
|
+
// ts TEXT — ISO-8601 UTC; range queries ("logs in the last hour").
|
|
96
|
+
// task_id TEXT — task identifier; per-task log views.
|
|
97
|
+
// run_id TEXT — buildTaskRunId(task_id, started_at); per-run log views
|
|
98
|
+
// and the join key back to state.db task_history.
|
|
99
|
+
//
|
|
100
|
+
// Non-indexed columns:
|
|
101
|
+
// stream TEXT — 'stdout' | 'stderr'; which pipe the line came from.
|
|
102
|
+
// level TEXT — 'info' | 'warn' | 'error'; runner-assigned severity
|
|
103
|
+
// ('info' for captured stdout, 'error' for stderr and
|
|
104
|
+
// failure diagnostics).
|
|
105
|
+
// line TEXT — the log line itself (no trailing newline).
|
|
106
|
+
//
|
|
107
|
+
// ADD COLUMN extension points (future migrations):
|
|
108
|
+
// ALTER TABLE task_logs ADD COLUMN seq INTEGER DEFAULT NULL;
|
|
109
|
+
// ALTER TABLE task_logs ADD COLUMN source TEXT DEFAULT NULL;
|
|
110
|
+
//
|
|
111
|
+
// TTL: rows where ts < NOW() - retention can be deleted by purgeOldTaskLogs().
|
|
112
|
+
// No automatic deletion occurs here.
|
|
113
|
+
{
|
|
114
|
+
id: "001-task-logs",
|
|
115
|
+
up: `
|
|
116
|
+
CREATE TABLE IF NOT EXISTS task_logs (
|
|
117
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
118
|
+
ts TEXT NOT NULL,
|
|
119
|
+
task_id TEXT NOT NULL,
|
|
120
|
+
run_id TEXT NOT NULL,
|
|
121
|
+
stream TEXT NOT NULL DEFAULT 'stdout',
|
|
122
|
+
level TEXT NOT NULL DEFAULT 'info',
|
|
123
|
+
line TEXT NOT NULL
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
-- Query patterns:
|
|
127
|
+
-- SELECT … WHERE ts >= ? AND ts <= ? → idx_task_logs_ts (purge, windows)
|
|
128
|
+
-- SELECT … WHERE task_id = ? → idx_task_logs_task_id
|
|
129
|
+
-- SELECT … WHERE run_id = ? → idx_task_logs_run_id (per-run tail)
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_task_logs_ts ON task_logs(ts);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_task_logs_task_id ON task_logs(task_id);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_task_logs_run_id ON task_logs(run_id);
|
|
133
|
+
`,
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
/**
|
|
137
|
+
* Apply every pending migration. Called automatically by
|
|
138
|
+
* {@link openLogsDatabase}; exported for the same test seams state-db exposes.
|
|
139
|
+
*/
|
|
140
|
+
export function runMigrations(db) {
|
|
141
|
+
runSqliteMigrations(db, MIGRATIONS);
|
|
142
|
+
}
|
|
143
|
+
// ── run_id ───────────────────────────────────────────────────────────────────
|
|
144
|
+
/**
|
|
145
|
+
* Encode a task run's identity — the unique `(task_id, started_at)` pair from
|
|
146
|
+
* state.db `task_history` — as a single run_id string.
|
|
147
|
+
*
|
|
148
|
+
* The format MUST stay in sync with the SQL expression
|
|
149
|
+
* `task_id || '@' || started_at` used by {@link queryFailedRunLogLines}.
|
|
150
|
+
*/
|
|
151
|
+
export function buildTaskRunId(taskId, startedAtIso) {
|
|
152
|
+
return `${taskId}@${startedAtIso}`;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Insert a batch of log lines for one task run in a single transaction.
|
|
156
|
+
* Returns the number of rows inserted. Lines are stored in array order
|
|
157
|
+
* (ascending rowid), so reading back `ORDER BY id` reproduces emission order.
|
|
158
|
+
*
|
|
159
|
+
* Errors propagate — the task runner wraps this in its own best-effort
|
|
160
|
+
* handling (mirroring `appendHistory`) so an unwritable logs.db never fails
|
|
161
|
+
* a task run.
|
|
162
|
+
*/
|
|
163
|
+
export function insertTaskLogLines(db, input) {
|
|
164
|
+
if (input.lines.length === 0)
|
|
165
|
+
return 0;
|
|
166
|
+
const stmt = db.prepare(`INSERT INTO task_logs (ts, task_id, run_id, stream, level, line)
|
|
167
|
+
VALUES (?, ?, ?, ?, ?, ?)`);
|
|
168
|
+
db.transaction(() => {
|
|
169
|
+
for (const entry of input.lines) {
|
|
170
|
+
stmt.run(input.ts, input.taskId, input.runId, entry.stream ?? "stdout", entry.level ?? "info", entry.line);
|
|
171
|
+
}
|
|
172
|
+
})();
|
|
173
|
+
return input.lines.length;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Read log lines matching the filter, in emission order (ascending id).
|
|
177
|
+
*
|
|
178
|
+
* Connection-lifetime rule (WS5): `.all()` materializes a plain array before
|
|
179
|
+
* returning.
|
|
180
|
+
*/
|
|
181
|
+
export function queryTaskLogs(db, options = {}) {
|
|
182
|
+
const conditions = [];
|
|
183
|
+
const params = [];
|
|
184
|
+
if (options.taskId) {
|
|
185
|
+
conditions.push("task_id = ?");
|
|
186
|
+
params.push(options.taskId);
|
|
187
|
+
}
|
|
188
|
+
if (options.runId) {
|
|
189
|
+
conditions.push("run_id = ?");
|
|
190
|
+
params.push(options.runId);
|
|
191
|
+
}
|
|
192
|
+
if (options.stream) {
|
|
193
|
+
conditions.push("stream = ?");
|
|
194
|
+
params.push(options.stream);
|
|
195
|
+
}
|
|
196
|
+
if (options.since) {
|
|
197
|
+
conditions.push("ts >= ?");
|
|
198
|
+
params.push(options.since);
|
|
199
|
+
}
|
|
200
|
+
if (options.until) {
|
|
201
|
+
conditions.push("ts < ?");
|
|
202
|
+
params.push(options.until);
|
|
203
|
+
}
|
|
204
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
205
|
+
const limit = options.limit !== undefined && options.limit >= 0 ? ` LIMIT ${Math.floor(options.limit)}` : "";
|
|
206
|
+
return db
|
|
207
|
+
.prepare(`SELECT id, ts, task_id, run_id, stream, level, line FROM task_logs ${where} ORDER BY id ASC${limit}`)
|
|
208
|
+
.all(...params);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Bulk membership check: which of `runIds` have at least one log row?
|
|
212
|
+
* Used by `akm health` to compute the log-backing rate from the database
|
|
213
|
+
* instead of `fs.existsSync` over scattered files. Chunked to stay under
|
|
214
|
+
* SQLite's bound-parameter ceiling.
|
|
215
|
+
*/
|
|
216
|
+
export function getLoggedRunIds(db, runIds) {
|
|
217
|
+
const out = new Set();
|
|
218
|
+
if (runIds.length === 0)
|
|
219
|
+
return out;
|
|
220
|
+
const CHUNK = 500;
|
|
221
|
+
for (let i = 0; i < runIds.length; i += CHUNK) {
|
|
222
|
+
const chunk = runIds.slice(i, i + CHUNK);
|
|
223
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
224
|
+
const rows = db
|
|
225
|
+
.prepare(`SELECT DISTINCT run_id FROM task_logs WHERE run_id IN (${placeholders})`)
|
|
226
|
+
.all(...chunk);
|
|
227
|
+
for (const row of rows)
|
|
228
|
+
out.add(row.run_id);
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
// ── Cross-db: ATTACH state.db ────────────────────────────────────────────────
|
|
233
|
+
/**
|
|
234
|
+
* ATTACH state.db to an open logs.db handle under the schema name `state`,
|
|
235
|
+
* enabling cross-db joins like task_history × task_logs.
|
|
236
|
+
*
|
|
237
|
+
* The state.db file must already exist (callers always open state.db first in
|
|
238
|
+
* practice); attaching a non-existent path would silently create an empty,
|
|
239
|
+
* unmigrated database file, so this throws instead.
|
|
240
|
+
*/
|
|
241
|
+
export function attachStateDatabase(db, stateDbPath) {
|
|
242
|
+
const resolved = stateDbPath ?? getStateDbPath();
|
|
243
|
+
if (!fs.existsSync(resolved)) {
|
|
244
|
+
throw new Error(`Cannot ATTACH state.db: file does not exist at ${resolved}`);
|
|
245
|
+
}
|
|
246
|
+
// prepare().run() rather than db.run(): both drivers support parameterised
|
|
247
|
+
// ATTACH through a prepared statement, and no other call site uses db.run().
|
|
248
|
+
db.prepare("ATTACH DATABASE ? AS state").run(resolved);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Convenience: open logs.db with state.db attached as `state`. The returned
|
|
252
|
+
* handle supports cross-db queries such as {@link queryFailedRunLogLines}.
|
|
253
|
+
* Close it like any other handle (DETACH is implicit on close).
|
|
254
|
+
*/
|
|
255
|
+
export function openLogsDatabaseWithState(logsDbPath, stateDbPath) {
|
|
256
|
+
const db = openLogsDatabase(logsDbPath);
|
|
257
|
+
try {
|
|
258
|
+
attachStateDatabase(db, stateDbPath);
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
db.close();
|
|
262
|
+
throw err;
|
|
263
|
+
}
|
|
264
|
+
return db;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Cross-db join: every log line belonging to a FAILED task_history run whose
|
|
268
|
+
* `started_at` is `>= since` (all failed runs when omitted). Requires a handle
|
|
269
|
+
* opened via {@link openLogsDatabaseWithState}.
|
|
270
|
+
*
|
|
271
|
+
* The join key is the run_id encoding documented on {@link buildTaskRunId}:
|
|
272
|
+
* `task_logs.run_id = task_history.task_id || '@' || task_history.started_at`.
|
|
273
|
+
*/
|
|
274
|
+
export function queryFailedRunLogLines(db, options = {}) {
|
|
275
|
+
const conditions = ["th.status = 'failed'"];
|
|
276
|
+
const params = [];
|
|
277
|
+
if (options.since) {
|
|
278
|
+
conditions.push("th.started_at >= ?");
|
|
279
|
+
params.push(options.since);
|
|
280
|
+
}
|
|
281
|
+
const limit = options.limit !== undefined && options.limit >= 0 ? ` LIMIT ${Math.floor(options.limit)}` : "";
|
|
282
|
+
return db
|
|
283
|
+
.prepare(`SELECT th.task_id, l.run_id, th.started_at, th.status, l.ts, l.stream, l.level, l.line
|
|
284
|
+
FROM state.task_history th
|
|
285
|
+
JOIN task_logs l ON l.run_id = th.task_id || '@' || th.started_at
|
|
286
|
+
WHERE ${conditions.join(" AND ")}
|
|
287
|
+
ORDER BY th.started_at DESC, l.id ASC${limit}`)
|
|
288
|
+
.all(...params);
|
|
289
|
+
}
|
|
290
|
+
// ── Retention ────────────────────────────────────────────────────────────────
|
|
291
|
+
/**
|
|
292
|
+
* Delete task_logs rows older than `retentionDays` (default: 90). Mirrors
|
|
293
|
+
* `purgeOldEvents` / `purgeOldImproveRuns` in state-db.ts — same default, same
|
|
294
|
+
* return shape (rows deleted), same disabled-when-non-positive semantics.
|
|
295
|
+
* Wired into the improve maintenance pass alongside the state.db purges.
|
|
296
|
+
*/
|
|
297
|
+
export function purgeOldTaskLogs(db, retentionDays = 90) {
|
|
298
|
+
if (!Number.isFinite(retentionDays) || retentionDays <= 0)
|
|
299
|
+
return 0;
|
|
300
|
+
const cutoff = new Date(Date.now() - retentionDays * 86_400_000).toISOString();
|
|
301
|
+
const result = db.prepare("DELETE FROM task_logs WHERE ts < ?").run(cutoff);
|
|
302
|
+
const changes = result.changes ?? 0;
|
|
303
|
+
return typeof changes === "bigint" ? Number(changes) : changes;
|
|
304
|
+
}
|
package/dist/core/paths.js
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
import path from "node:path";
|
|
13
|
-
import { IS_WINDOWS } from "./common";
|
|
14
|
-
import { ConfigError } from "./errors";
|
|
13
|
+
import { IS_WINDOWS } from "./common.js";
|
|
14
|
+
import { ConfigError } from "./errors.js";
|
|
15
15
|
/**
|
|
16
16
|
* Returns true when the current process appears to be running under
|
|
17
17
|
* `bun test` (either via the BUN_TEST sentinel Bun sets on the test
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import { IS_WINDOWS } from "../common";
|
|
8
|
-
import { RG_BINARY, resolveRg } from "./resolve";
|
|
7
|
+
import { IS_WINDOWS } from "../common.js";
|
|
8
|
+
import { RG_BINARY, resolveRg } from "./resolve.js";
|
|
9
9
|
/**
|
|
10
10
|
* Platform and architecture detection for ripgrep binary downloads.
|
|
11
11
|
*/
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import { IS_WINDOWS } from "../common";
|
|
7
|
-
import { getBinDir } from "../paths";
|
|
6
|
+
import { IS_WINDOWS } from "../common.js";
|
|
7
|
+
import { getBinDir } from "../paths.js";
|
|
8
8
|
export const RG_BINARY = IS_WINDOWS ? "rg.exe" : "rg";
|
|
9
9
|
function canExecute(filePath) {
|
|
10
10
|
if (!fs.existsSync(filePath))
|