akm-cli 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/dist/{cli.js → src/cli.js} +712 -34
- package/dist/{commands → src/commands}/config-cli.js +47 -4
- package/dist/src/commands/distill.js +283 -0
- package/dist/src/commands/events.js +108 -0
- package/dist/src/commands/history.js +191 -0
- package/dist/{commands → src/commands}/installed-stashes.js +1 -1
- package/dist/src/commands/proposal.js +119 -0
- package/dist/src/commands/propose.js +171 -0
- package/dist/src/commands/reflect.js +193 -0
- package/dist/{commands → src/commands}/registry-search.js +71 -7
- package/dist/{commands → src/commands}/remember.js +12 -0
- package/dist/{commands → src/commands}/search.js +104 -4
- package/dist/{commands → src/commands}/self-update.js +4 -3
- package/dist/{commands → src/commands}/show.js +73 -0
- package/dist/{commands → src/commands}/source-add.js +5 -1
- package/dist/{commands → src/commands}/source-manage.js +7 -1
- package/dist/{core → src/core}/asset-ref.js +5 -5
- package/dist/{core → src/core}/asset-spec.js +12 -0
- package/dist/{core → src/core}/common.js +1 -1
- package/dist/{core → src/core}/config.js +203 -121
- package/dist/{core → src/core}/errors.js +4 -0
- package/dist/src/core/events.js +239 -0
- package/dist/src/core/lesson-lint.js +86 -0
- package/dist/src/core/proposals.js +406 -0
- package/dist/src/core/warn.js +72 -0
- package/dist/{core → src/core}/write-source.js +80 -5
- package/dist/{indexer → src/indexer}/db-search.js +114 -24
- package/dist/{indexer → src/indexer}/db.js +76 -23
- package/dist/{indexer → src/indexer}/file-context.js +0 -3
- package/dist/src/indexer/graph-boost.js +179 -0
- package/dist/src/indexer/graph-extraction.js +212 -0
- package/dist/{indexer → src/indexer}/indexer.js +88 -7
- package/dist/{indexer → src/indexer}/matchers.js +1 -1
- package/dist/src/indexer/memory-inference.js +263 -0
- package/dist/{indexer → src/indexer}/metadata.js +111 -3
- package/dist/{indexer → src/indexer}/search-source.js +4 -2
- package/dist/src/integrations/agent/config.js +292 -0
- package/dist/src/integrations/agent/detect.js +94 -0
- package/dist/src/integrations/agent/index.js +17 -0
- package/dist/src/integrations/agent/profiles.js +65 -0
- package/dist/src/integrations/agent/prompts.js +167 -0
- package/dist/src/integrations/agent/spawn.js +272 -0
- package/dist/{integrations → src/integrations}/github.js +9 -3
- package/dist/{integrations → src/integrations}/lockfile.js +0 -26
- package/dist/{llm → src/llm}/client.js +33 -2
- package/dist/{llm → src/llm}/embedders/remote.js +37 -3
- package/dist/src/llm/feature-gate.js +108 -0
- package/dist/src/llm/graph-extract.js +107 -0
- package/dist/src/llm/index-passes.js +35 -0
- package/dist/src/llm/memory-infer.js +86 -0
- package/dist/{output → src/output}/cli-hints.js +15 -2
- package/dist/{output → src/output}/renderers.js +63 -2
- package/dist/src/output/shapes.js +523 -0
- package/dist/src/output/text.js +1116 -0
- package/dist/{registry → src/registry}/build-index.js +19 -8
- package/dist/{registry → src/registry}/factory.js +0 -8
- package/dist/{registry → src/registry}/providers/static-index.js +6 -3
- package/dist/{registry → src/registry}/resolve.js +68 -2
- package/dist/{setup → src/setup}/setup.js +52 -5
- package/dist/{sources → src/sources}/providers/git.js +7 -15
- package/dist/{wiki → src/wiki}/wiki.js +54 -6
- package/dist/{workflows → src/workflows}/runs.js +37 -3
- package/dist/tests/add-website-source.test.js +119 -0
- package/dist/tests/agent/agent-config-loader.test.js +70 -0
- package/dist/tests/agent/agent-config.test.js +221 -0
- package/dist/tests/agent/agent-detect.test.js +100 -0
- package/dist/tests/agent/agent-spawn.test.js +234 -0
- package/dist/tests/agent-output.test.js +186 -0
- package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +103 -0
- package/dist/tests/architecture/agent-spawn-seam.test.js +193 -0
- package/dist/tests/architecture/llm-stateless-seam.test.js +112 -0
- package/dist/tests/asset-ref.test.js +192 -0
- package/dist/tests/asset-registry.test.js +103 -0
- package/dist/tests/asset-spec.test.js +241 -0
- package/dist/tests/bench/attribution.test.js +996 -0
- package/dist/tests/bench/cleanup-sigint.test.js +83 -0
- package/dist/tests/bench/cleanup.js +234 -0
- package/dist/tests/bench/cleanup.test.js +166 -0
- package/dist/tests/bench/cli.js +1018 -0
- package/dist/tests/bench/cli.test.js +445 -0
- package/dist/tests/bench/compare.test.js +556 -0
- package/dist/tests/bench/corpus.js +317 -0
- package/dist/tests/bench/corpus.test.js +258 -0
- package/dist/tests/bench/doctor.js +525 -0
- package/dist/tests/bench/driver.js +401 -0
- package/dist/tests/bench/driver.test.js +584 -0
- package/dist/tests/bench/environment.js +233 -0
- package/dist/tests/bench/environment.test.js +199 -0
- package/dist/tests/bench/evolve-metrics.js +179 -0
- package/dist/tests/bench/evolve-metrics.test.js +187 -0
- package/dist/tests/bench/evolve.js +647 -0
- package/dist/tests/bench/evolve.test.js +624 -0
- package/dist/tests/bench/failure-modes.test.js +349 -0
- package/dist/tests/bench/feedback-integrity.test.js +457 -0
- package/dist/tests/bench/leakage.test.js +228 -0
- package/dist/tests/bench/learning-curve.test.js +134 -0
- package/dist/tests/bench/metrics.js +2395 -0
- package/dist/tests/bench/metrics.test.js +1150 -0
- package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
- package/dist/tests/bench/opencode-config.js +194 -0
- package/dist/tests/bench/opencode-config.test.js +370 -0
- package/dist/tests/bench/report.js +1885 -0
- package/dist/tests/bench/report.test.js +1038 -0
- package/dist/tests/bench/run-config.js +355 -0
- package/dist/tests/bench/run-config.test.js +298 -0
- package/dist/tests/bench/run-curate-test.js +32 -0
- package/dist/tests/bench/run-failing-tasks.js +56 -0
- package/dist/tests/bench/run-full-bench.js +51 -0
- package/dist/tests/bench/run-items36-targeted.js +69 -0
- package/dist/tests/bench/run-nano-quick.js +42 -0
- package/dist/tests/bench/run-waveg-targeted.js +62 -0
- package/dist/tests/bench/runner.js +699 -0
- package/dist/tests/bench/runner.test.js +958 -0
- package/dist/tests/bench/search-bridge.test.js +331 -0
- package/dist/tests/bench/tmp.js +131 -0
- package/dist/tests/bench/trajectory.js +116 -0
- package/dist/tests/bench/trajectory.test.js +127 -0
- package/dist/tests/bench/verifier.js +114 -0
- package/dist/tests/bench/verifier.test.js +118 -0
- package/dist/tests/bench/workflow-evaluator.js +557 -0
- package/dist/tests/bench/workflow-evaluator.test.js +421 -0
- package/dist/tests/bench/workflow-spec.js +345 -0
- package/dist/tests/bench/workflow-spec.test.js +363 -0
- package/dist/tests/bench/workflow-trace.js +472 -0
- package/dist/tests/bench/workflow-trace.test.js +254 -0
- package/dist/tests/benchmark-search-quality.js +536 -0
- package/dist/tests/benchmark-suite.js +1441 -0
- package/dist/tests/capture-cli.test.js +112 -0
- package/dist/tests/cli-errors.test.js +204 -0
- package/dist/tests/commands/events.test.js +370 -0
- package/dist/tests/commands/history.test.js +418 -0
- package/dist/tests/commands/import.test.js +103 -0
- package/dist/tests/commands/proposal-cli.test.js +209 -0
- package/dist/tests/commands/reflect-propose-cli.test.js +333 -0
- package/dist/tests/commands/remember.test.js +97 -0
- package/dist/tests/commands/scope-flags.test.js +300 -0
- package/dist/tests/commands/search.test.js +537 -0
- package/dist/tests/commands/show-indexer-parity.test.js +117 -0
- package/dist/tests/commands/show.test.js +294 -0
- package/dist/tests/common.test.js +266 -0
- package/dist/tests/completions.test.js +142 -0
- package/dist/tests/config-cli.test.js +193 -0
- package/dist/tests/config-llm-features.test.js +139 -0
- package/dist/tests/config.test.js +569 -0
- package/dist/tests/contracts/migration-baseline.test.js +43 -0
- package/dist/tests/contracts/reflect-propose-envelope.test.js +139 -0
- package/dist/tests/contracts/spec-helpers.js +46 -0
- package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +228 -0
- package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +56 -0
- package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +34 -0
- package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +94 -0
- package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +39 -0
- package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +44 -0
- package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +47 -0
- package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +40 -0
- package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +58 -0
- package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +34 -0
- package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +75 -0
- package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +36 -0
- package/dist/tests/core/write-source.test.js +366 -0
- package/dist/tests/curate-command.test.js +87 -0
- package/dist/tests/db-scoring.test.js +201 -0
- package/dist/tests/db.test.js +654 -0
- package/dist/tests/distill-cli-flag.test.js +208 -0
- package/dist/tests/distill.test.js +515 -0
- package/dist/tests/docker-install.test.js +120 -0
- package/dist/tests/e2e.test.js +1419 -0
- package/dist/tests/embedder.test.js +340 -0
- package/dist/tests/embedding-model-config.test.js +379 -0
- package/dist/tests/feedback-command.test.js +172 -0
- package/dist/tests/file-context.test.js +552 -0
- package/dist/tests/fixtures/scripts/git/summarize-diff.js +9 -0
- package/dist/tests/fixtures/scripts/lint/eslint-check.js +7 -0
- package/dist/tests/fixtures/stashes/load.js +166 -0
- package/dist/tests/fixtures/stashes/load.test.js +97 -0
- package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +12 -0
- package/dist/tests/frontmatter.test.js +190 -0
- package/dist/tests/fts-field-weighting.test.js +254 -0
- package/dist/tests/fuzzy-search.test.js +230 -0
- package/dist/tests/git-provider-clone.test.js +45 -0
- package/dist/tests/github.test.js +161 -0
- package/dist/tests/graph-boost-ranking.test.js +305 -0
- package/dist/tests/graph-extraction.test.js +282 -0
- package/dist/tests/helpers/usage-events.js +8 -0
- package/dist/tests/index-pass-llm.test.js +161 -0
- package/dist/tests/indexer.test.js +570 -0
- package/dist/tests/info-command.test.js +166 -0
- package/dist/tests/init.test.js +69 -0
- package/dist/tests/install-script.test.js +246 -0
- package/dist/tests/integration/agent-real-profile.test.js +94 -0
- package/dist/tests/issue-36-repro.test.js +304 -0
- package/dist/tests/issues-191-194.test.js +160 -0
- package/dist/tests/lesson-lint.test.js +111 -0
- package/dist/tests/llm-client.test.js +115 -0
- package/dist/tests/llm-feature-gate.test.js +151 -0
- package/dist/tests/llm.test.js +139 -0
- package/dist/tests/lockfile.test.js +216 -0
- package/dist/tests/manifest.test.js +205 -0
- package/dist/tests/markdown.test.js +126 -0
- package/dist/tests/matchers-unit.test.js +189 -0
- package/dist/tests/memory-inference.test.js +299 -0
- package/dist/tests/merge-scoring.test.js +136 -0
- package/dist/tests/metadata.test.js +313 -0
- package/dist/tests/migration-help.test.js +89 -0
- package/dist/tests/origin-resolve.test.js +124 -0
- package/dist/tests/output-baseline.test.js +218 -0
- package/dist/tests/output-shapes-unit.test.js +478 -0
- package/dist/tests/parallel-search.test.js +272 -0
- package/dist/tests/parameter-metadata.test.js +365 -0
- package/dist/tests/paths.test.js +177 -0
- package/dist/tests/progressive-disclosure.test.js +280 -0
- package/dist/tests/proposals.test.js +279 -0
- package/dist/tests/proposed-quality.test.js +271 -0
- package/dist/tests/provider-registry.test.js +32 -0
- package/dist/tests/ranking-regression.test.js +548 -0
- package/dist/tests/reflect-propose.test.js +455 -0
- package/dist/tests/registry-build-index.test.js +394 -0
- package/dist/tests/registry-cli.test.js +290 -0
- package/dist/tests/registry-index-v2.test.js +430 -0
- package/dist/tests/registry-install.test.js +728 -0
- package/dist/tests/registry-providers/parity.test.js +189 -0
- package/dist/tests/registry-providers/skills-sh.test.js +309 -0
- package/dist/tests/registry-providers/static-index.test.js +238 -0
- package/dist/tests/registry-resolve.test.js +126 -0
- package/dist/tests/registry-search.test.js +923 -0
- package/dist/tests/remember-frontmatter.test.js +378 -0
- package/dist/tests/remember-unit.test.js +123 -0
- package/dist/tests/ripgrep-install.test.js +251 -0
- package/dist/tests/ripgrep-resolve.test.js +108 -0
- package/dist/tests/ripgrep.test.js +163 -0
- package/dist/tests/save-command.test.js +94 -0
- package/dist/tests/save-trust-qa-fixes.test.js +270 -0
- package/dist/tests/scoring-pipeline.test.js +648 -0
- package/dist/tests/search-include-proposed-cli.test.js +118 -0
- package/dist/tests/self-update.test.js +442 -0
- package/dist/tests/semantic-search-e2e.test.js +512 -0
- package/dist/tests/semantic-status.test.js +471 -0
- package/dist/tests/setup-run.integration.js +877 -0
- package/dist/tests/setup-wizard.test.js +198 -0
- package/dist/tests/setup.test.js +131 -0
- package/dist/tests/source-add.test.js +11 -0
- package/dist/tests/source-clone.test.js +254 -0
- package/dist/tests/source-manage.test.js +366 -0
- package/dist/tests/source-providers/filesystem.test.js +82 -0
- package/dist/tests/source-providers/git.test.js +252 -0
- package/dist/tests/source-providers/website.test.js +128 -0
- package/dist/tests/source-qa-fixes.test.js +286 -0
- package/dist/tests/source-registry.test.js +350 -0
- package/dist/tests/source-resolve.test.js +100 -0
- package/dist/tests/source-source.test.js +281 -0
- package/dist/tests/source.test.js +533 -0
- package/dist/tests/tar-utils-scan.test.js +73 -0
- package/dist/tests/toggle-components.test.js +73 -0
- package/dist/tests/usage-telemetry.test.js +265 -0
- package/dist/tests/utility-scoring.test.js +558 -0
- package/dist/tests/vault-load-error.test.js +78 -0
- package/dist/tests/vault-qa-fixes.test.js +194 -0
- package/dist/tests/vault.test.js +429 -0
- package/dist/tests/vector-search.test.js +608 -0
- package/dist/tests/walker.test.js +252 -0
- package/dist/tests/wave2-cluster-bc.test.js +228 -0
- package/dist/tests/wave2-cluster-d.test.js +180 -0
- package/dist/tests/wave2-cluster-e.test.js +179 -0
- package/dist/tests/wiki-qa-fixes.test.js +270 -0
- package/dist/tests/wiki.test.js +529 -0
- package/dist/tests/workflow-cli.test.js +271 -0
- package/dist/tests/workflow-markdown.test.js +171 -0
- package/dist/tests/workflow-path-escape.test.js +132 -0
- package/dist/tests/workflow-qa-fixes.test.js +395 -0
- package/dist/tests/workflows/indexer-rejection.test.js +213 -0
- package/docs/README.md +8 -0
- package/docs/migration/release-notes/0.7.0.md +244 -0
- package/package.json +2 -2
- package/dist/core/warn.js +0 -27
- package/dist/output/shapes.js +0 -212
- package/dist/output/text.js +0 -520
- /package/dist/{commands → src/commands}/completions.js +0 -0
- /package/dist/{commands → src/commands}/curate.js +0 -0
- /package/dist/{commands → src/commands}/info.js +0 -0
- /package/dist/{commands → src/commands}/init.js +0 -0
- /package/dist/{commands → src/commands}/install-audit.js +0 -0
- /package/dist/{commands → src/commands}/migration-help.js +0 -0
- /package/dist/{commands → src/commands}/source-clone.js +0 -0
- /package/dist/{commands → src/commands}/vault.js +0 -0
- /package/dist/{core → src/core}/asset-registry.js +0 -0
- /package/dist/{core → src/core}/frontmatter.js +0 -0
- /package/dist/{core → src/core}/markdown.js +0 -0
- /package/dist/{core → src/core}/paths.js +0 -0
- /package/dist/{indexer → src/indexer}/manifest.js +0 -0
- /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
- /package/dist/{indexer → src/indexer}/semantic-status.js +0 -0
- /package/dist/{indexer → src/indexer}/usage-events.js +0 -0
- /package/dist/{indexer → src/indexer}/walker.js +0 -0
- /package/dist/{llm → src/llm}/embedder.js +0 -0
- /package/dist/{llm → src/llm}/embedders/cache.js +0 -0
- /package/dist/{llm → src/llm}/embedders/local.js +0 -0
- /package/dist/{llm → src/llm}/embedders/types.js +0 -0
- /package/dist/{llm → src/llm}/metadata-enhance.js +0 -0
- /package/dist/{output → src/output}/context.js +0 -0
- /package/dist/{registry → src/registry}/create-provider-registry.js +0 -0
- /package/dist/{registry → src/registry}/origin-resolve.js +0 -0
- /package/dist/{registry → src/registry}/providers/index.js +0 -0
- /package/dist/{registry → src/registry}/providers/skills-sh.js +0 -0
- /package/dist/{registry → src/registry}/providers/types.js +0 -0
- /package/dist/{registry → src/registry}/types.js +0 -0
- /package/dist/{setup → src/setup}/detect.js +0 -0
- /package/dist/{setup → src/setup}/ripgrep-install.js +0 -0
- /package/dist/{setup → src/setup}/ripgrep-resolve.js +0 -0
- /package/dist/{setup → src/setup}/steps.js +0 -0
- /package/dist/{sources → src/sources}/include.js +0 -0
- /package/dist/{sources → src/sources}/provider-factory.js +0 -0
- /package/dist/{sources → src/sources}/provider.js +0 -0
- /package/dist/{sources → src/sources}/providers/filesystem.js +0 -0
- /package/dist/{sources → src/sources}/providers/index.js +0 -0
- /package/dist/{sources → src/sources}/providers/install-types.js +0 -0
- /package/dist/{sources → src/sources}/providers/npm.js +0 -0
- /package/dist/{sources → src/sources}/providers/provider-utils.js +0 -0
- /package/dist/{sources → src/sources}/providers/sync-from-ref.js +0 -0
- /package/dist/{sources → src/sources}/providers/tar-utils.js +0 -0
- /package/dist/{sources → src/sources}/providers/website.js +0 -0
- /package/dist/{sources → src/sources}/resolve.js +0 -0
- /package/dist/{sources → src/sources}/types.js +0 -0
- /package/dist/{templates → src/templates}/wiki-templates.js +0 -0
- /package/dist/{version.js → src/version.js} +0 -0
- /package/dist/{workflows → src/workflows}/authoring.js +0 -0
- /package/dist/{workflows → src/workflows}/cli.js +0 -0
- /package/dist/{workflows → src/workflows}/db.js +0 -0
- /package/dist/{workflows → src/workflows}/document-cache.js +0 -0
- /package/dist/{workflows → src/workflows}/parser.js +0 -0
- /package/dist/{workflows → src/workflows}/renderer.js +0 -0
- /package/dist/{workflows → src/workflows}/schema.js +0 -0
- /package/dist/{workflows → src/workflows}/validator.js +0 -0
|
@@ -31,6 +31,12 @@ export function parseConfigValue(key, value) {
|
|
|
31
31
|
return { embedding: mergeLlmLikeEmbedding(undefined, { model: requireNonEmptyString(value, key) }) };
|
|
32
32
|
case "embedding.apiKey":
|
|
33
33
|
return { embedding: mergeLlmLikeEmbedding(undefined, { apiKey: requireNonEmptyString(value, key) }) };
|
|
34
|
+
case "embedding.contextLength":
|
|
35
|
+
return { embedding: mergeLlmLikeEmbedding(undefined, { contextLength: parsePositiveInteger(value, key) }) };
|
|
36
|
+
case "embedding.ollamaOptions.numCtx":
|
|
37
|
+
return {
|
|
38
|
+
embedding: mergeLlmLikeEmbedding(undefined, { ollamaOptions: { num_ctx: parsePositiveInteger(value, key) } }),
|
|
39
|
+
};
|
|
34
40
|
case "llm":
|
|
35
41
|
return { llm: parseLlmConnectionValue(value) };
|
|
36
42
|
case "llm.endpoint":
|
|
@@ -62,9 +68,10 @@ export function parseConfigValue(key, value) {
|
|
|
62
68
|
case "security.installAudit.allowedFindings":
|
|
63
69
|
return { security: { installAudit: { allowedFindings: parseAllowedFindingsValue(value, key) } } };
|
|
64
70
|
default:
|
|
65
|
-
throw new UsageError(`Unknown config key: ${key}
|
|
71
|
+
throw new UsageError(`Unknown config key: ${key}`, "INVALID_FLAG_VALUE", UNKNOWN_CONFIG_KEY_HINT);
|
|
66
72
|
}
|
|
67
73
|
}
|
|
74
|
+
const UNKNOWN_CONFIG_KEY_HINT = "Valid top-level keys: stashDir, embedding, llm, registries, sources, agent, output, semanticSearchMode. Use dotted paths like `embedding.endpoint` or `output.format` for nested values.";
|
|
68
75
|
export function getConfigValue(config, key) {
|
|
69
76
|
switch (key) {
|
|
70
77
|
case "stashDir":
|
|
@@ -81,6 +88,10 @@ export function getConfigValue(config, key) {
|
|
|
81
88
|
return config.embedding?.model ?? null;
|
|
82
89
|
case "embedding.apiKey":
|
|
83
90
|
return config.embedding?.apiKey ?? null;
|
|
91
|
+
case "embedding.contextLength":
|
|
92
|
+
return config.embedding?.contextLength ?? null;
|
|
93
|
+
case "embedding.ollamaOptions.numCtx":
|
|
94
|
+
return config.embedding?.ollamaOptions?.num_ctx ?? null;
|
|
84
95
|
case "llm":
|
|
85
96
|
return config.llm ?? null;
|
|
86
97
|
case "llm.endpoint":
|
|
@@ -114,7 +125,7 @@ export function getConfigValue(config, key) {
|
|
|
114
125
|
case "security.installAudit.allowedFindings":
|
|
115
126
|
return config.security?.installAudit?.allowedFindings ?? null;
|
|
116
127
|
default:
|
|
117
|
-
throw new UsageError(`Unknown config key: ${key}
|
|
128
|
+
throw new UsageError(`Unknown config key: ${key}`, "INVALID_FLAG_VALUE", UNKNOWN_CONFIG_KEY_HINT);
|
|
118
129
|
}
|
|
119
130
|
}
|
|
120
131
|
export function setConfigValue(config, key, rawValue) {
|
|
@@ -151,6 +162,18 @@ export function setConfigValue(config, key, rawValue) {
|
|
|
151
162
|
...config,
|
|
152
163
|
embedding: mergeLlmLikeEmbedding(config.embedding, { apiKey: requireNonEmptyString(rawValue, key) }),
|
|
153
164
|
};
|
|
165
|
+
case "embedding.contextLength":
|
|
166
|
+
return {
|
|
167
|
+
...config,
|
|
168
|
+
embedding: mergeLlmLikeEmbedding(config.embedding, { contextLength: parsePositiveInteger(rawValue, key) }),
|
|
169
|
+
};
|
|
170
|
+
case "embedding.ollamaOptions.numCtx":
|
|
171
|
+
return {
|
|
172
|
+
...config,
|
|
173
|
+
embedding: mergeLlmLikeEmbedding(config.embedding, {
|
|
174
|
+
ollamaOptions: { ...(config.embedding?.ollamaOptions ?? {}), num_ctx: parsePositiveInteger(rawValue, key) },
|
|
175
|
+
}),
|
|
176
|
+
};
|
|
154
177
|
case "llm.endpoint":
|
|
155
178
|
return { ...config, llm: mergeLlmLike(config.llm, { endpoint: requireNonEmptyString(rawValue, key) }) };
|
|
156
179
|
case "llm.model":
|
|
@@ -168,7 +191,7 @@ export function setConfigValue(config, key, rawValue) {
|
|
|
168
191
|
return { ...config, defaultWriteTarget: name };
|
|
169
192
|
}
|
|
170
193
|
default:
|
|
171
|
-
throw new UsageError(`Unknown config key: ${key}
|
|
194
|
+
throw new UsageError(`Unknown config key: ${key}`, "INVALID_FLAG_VALUE", UNKNOWN_CONFIG_KEY_HINT);
|
|
172
195
|
}
|
|
173
196
|
}
|
|
174
197
|
export function unsetConfigValue(config, key) {
|
|
@@ -189,6 +212,19 @@ export function unsetConfigValue(config, key) {
|
|
|
189
212
|
const { apiKey: _a, ...rest } = config.embedding;
|
|
190
213
|
return { ...config, embedding: rest };
|
|
191
214
|
}
|
|
215
|
+
case "embedding.contextLength": {
|
|
216
|
+
if (!config.embedding)
|
|
217
|
+
return config;
|
|
218
|
+
const { contextLength: _cl, ...rest } = config.embedding;
|
|
219
|
+
return { ...config, embedding: rest };
|
|
220
|
+
}
|
|
221
|
+
case "embedding.ollamaOptions.numCtx": {
|
|
222
|
+
if (!config.embedding?.ollamaOptions)
|
|
223
|
+
return config;
|
|
224
|
+
const { num_ctx: _nc, ...restOpts } = config.embedding.ollamaOptions;
|
|
225
|
+
const ollamaOptions = Object.keys(restOpts).length > 0 ? restOpts : undefined;
|
|
226
|
+
return { ...config, embedding: { ...config.embedding, ollamaOptions } };
|
|
227
|
+
}
|
|
192
228
|
case "llm":
|
|
193
229
|
return { ...config, llm: undefined };
|
|
194
230
|
case "llm.endpoint":
|
|
@@ -241,7 +277,7 @@ export function unsetConfigValue(config, key) {
|
|
|
241
277
|
}),
|
|
242
278
|
};
|
|
243
279
|
default:
|
|
244
|
-
throw new UsageError(`Unknown or unsupported unset key: ${key}
|
|
280
|
+
throw new UsageError(`Unknown or unsupported unset key: ${key}`, "INVALID_FLAG_VALUE", UNKNOWN_CONFIG_KEY_HINT);
|
|
245
281
|
}
|
|
246
282
|
}
|
|
247
283
|
export function listConfig(config) {
|
|
@@ -478,6 +514,13 @@ function parseUnknownPositiveInteger(value, key) {
|
|
|
478
514
|
}
|
|
479
515
|
return value;
|
|
480
516
|
}
|
|
517
|
+
function parsePositiveInteger(value, key) {
|
|
518
|
+
const n = Number(value);
|
|
519
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
|
|
520
|
+
throw new UsageError(`Invalid value for ${key}: expected a positive integer`);
|
|
521
|
+
}
|
|
522
|
+
return n;
|
|
523
|
+
}
|
|
481
524
|
function parseStashesValue(value) {
|
|
482
525
|
if (value === "null" || value === "")
|
|
483
526
|
return undefined;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `akm distill <ref>` — feedback distillation into lesson proposals (#228).
|
|
3
|
+
*
|
|
4
|
+
* The command reads a target asset and any recent feedback events about it,
|
|
5
|
+
* asks an LLM to distil a *lesson* (per v1 spec §13) the agent should
|
|
6
|
+
* remember next time, and queues the result as a {@link Proposal} (source
|
|
7
|
+
* `"distill"`). The proposal queue is the *only* path to a live asset — this
|
|
8
|
+
* command never mutates source files directly. Acceptance is a human (or
|
|
9
|
+
* automated) decision via `akm proposal accept`.
|
|
10
|
+
*
|
|
11
|
+
* # Architectural seams
|
|
12
|
+
*
|
|
13
|
+
* - **Single bounded in-tree LLM call.** Wrapped in {@link tryLlmFeature}
|
|
14
|
+
* under the `feedback_distillation` gate (v1 spec §14). The wrapper
|
|
15
|
+
* enforces the 30 s hard timeout and converts disable / throw / timeout
|
|
16
|
+
* into a `null` return from `fn`, which we treat as a graceful
|
|
17
|
+
* "skipped" outcome (exit 0, no proposal, `distill_invoked` event with
|
|
18
|
+
* `outcome: "skipped"`).
|
|
19
|
+
* - **Stateless.** No module-level state — every callable is a pure
|
|
20
|
+
* function of its arguments and an injectable `chat` seam. The
|
|
21
|
+
* architecture seam test (`tests/architecture/llm-stateless-seam.test.ts`)
|
|
22
|
+
* applies.
|
|
23
|
+
* - **Output substrate.** Proposal creation goes through the `proposals`
|
|
24
|
+
* module so distill shares its persistence + validation pipeline with
|
|
25
|
+
* `akm reflect` / `akm propose`. Validation failures (LLM returned a
|
|
26
|
+
* lesson without required `description` / `when_to_use` frontmatter) are
|
|
27
|
+
* a *different* graceful path: no proposal is created, the structured
|
|
28
|
+
* error is surfaced, and the command exits non-zero.
|
|
29
|
+
*
|
|
30
|
+
* # Lesson-name derivation rule
|
|
31
|
+
*
|
|
32
|
+
* The proposed lesson ref is `lesson:<original-ref-slug>-lesson`, where
|
|
33
|
+
* `<original-ref-slug>` is `<type>-<name>` from the parsed input ref (so
|
|
34
|
+
* `skill:deploy` → `lesson:skill-deploy-lesson`, and `team//memory:auth-tips`
|
|
35
|
+
* → `lesson:memory-auth-tips-lesson`). Origin prefixes are dropped from the
|
|
36
|
+
* derived name so two sources with the same asset-type/name collapse onto
|
|
37
|
+
* the same lesson queue entry rather than each generating its own — the
|
|
38
|
+
* proposal queue tolerates duplicate refs (id is a UUID), so the human
|
|
39
|
+
* reviewer can decide which one to accept.
|
|
40
|
+
*
|
|
41
|
+
* # Why we do not call `runAgent`
|
|
42
|
+
*
|
|
43
|
+
* Distillation is in-tree per the v1 spec ("bounded in-tree LLM call"). The
|
|
44
|
+
* agent dispatch path is a heavier shell-out used by the curator/agent
|
|
45
|
+
* surfaces — distill must be cheap, deterministic-ish, and bounded so it can
|
|
46
|
+
* be invoked from CI / automation without spinning up an agent harness.
|
|
47
|
+
*/
|
|
48
|
+
import fs from "node:fs";
|
|
49
|
+
import { parseAssetRef } from "../core/asset-ref";
|
|
50
|
+
import { resolveStashDir } from "../core/common";
|
|
51
|
+
import { loadConfig } from "../core/config";
|
|
52
|
+
import { ConfigError, UsageError } from "../core/errors";
|
|
53
|
+
import { appendEvent, readEvents } from "../core/events";
|
|
54
|
+
import { parseFrontmatter } from "../core/frontmatter";
|
|
55
|
+
import { lintLessonContent } from "../core/lesson-lint";
|
|
56
|
+
import { createProposal } from "../core/proposals";
|
|
57
|
+
import { lookup as indexerLookup } from "../indexer/indexer";
|
|
58
|
+
import { chatCompletion } from "../llm/client";
|
|
59
|
+
import { tryLlmFeature } from "../llm/feature-gate";
|
|
60
|
+
// ── Lesson-ref derivation ───────────────────────────────────────────────────
|
|
61
|
+
/** Derive the proposed lesson ref from the input ref. See module docblock. */
|
|
62
|
+
export function deriveLessonRef(inputRef) {
|
|
63
|
+
const parsed = parseAssetRef(inputRef);
|
|
64
|
+
// Strip origin: a feedback signal recorded against `team//skill:deploy`
|
|
65
|
+
// distils into the same lesson namespace as `skill:deploy`. The proposal
|
|
66
|
+
// id (a UUID) keeps the queue entries distinct, so collisions are not a
|
|
67
|
+
// problem — and reviewers want to see them next to each other anyway.
|
|
68
|
+
const slug = `${parsed.type}-${parsed.name}`.toLowerCase();
|
|
69
|
+
// Replace anything outside the canonical asset-name charset with `-`. Keep
|
|
70
|
+
// it deterministic so re-runs produce the same ref.
|
|
71
|
+
const safe = slug
|
|
72
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
73
|
+
.replace(/-+/g, "-")
|
|
74
|
+
.replace(/^-|-$/g, "");
|
|
75
|
+
return `lesson:${safe}-lesson`;
|
|
76
|
+
}
|
|
77
|
+
// ── Prompt assembly ─────────────────────────────────────────────────────────
|
|
78
|
+
const SYSTEM_PROMPT = [
|
|
79
|
+
"You are the akm `distill` distiller.",
|
|
80
|
+
"Given an asset and recent feedback events about it, produce a single",
|
|
81
|
+
"concise *lesson* an agent should remember next time it works on this",
|
|
82
|
+
"asset's domain.",
|
|
83
|
+
"",
|
|
84
|
+
"Output MUST be a complete markdown file with YAML frontmatter:",
|
|
85
|
+
" ---",
|
|
86
|
+
" description: <one-line summary of what the lesson teaches>",
|
|
87
|
+
" when_to_use: <one-line trigger that should make a caller apply it>",
|
|
88
|
+
" ---",
|
|
89
|
+
"",
|
|
90
|
+
" <lesson body, plain markdown, 1–3 short paragraphs>",
|
|
91
|
+
"",
|
|
92
|
+
"Both `description` and `when_to_use` MUST be non-empty single-line strings.",
|
|
93
|
+
"Output ONLY the lesson file contents — no prose, no fences, no preamble.",
|
|
94
|
+
].join("\n");
|
|
95
|
+
/** Pure: build the user-prompt body. Exported for tests. */
|
|
96
|
+
export function buildDistillPrompt(input) {
|
|
97
|
+
const lines = [];
|
|
98
|
+
lines.push(`Asset ref: ${input.inputRef}`);
|
|
99
|
+
lines.push("");
|
|
100
|
+
lines.push("Asset content:");
|
|
101
|
+
if (input.assetContent) {
|
|
102
|
+
lines.push("```");
|
|
103
|
+
lines.push(input.assetContent.trim());
|
|
104
|
+
lines.push("```");
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
lines.push("(asset is not currently indexed; distil from feedback signal alone)");
|
|
108
|
+
}
|
|
109
|
+
lines.push("");
|
|
110
|
+
lines.push("Recent feedback events (most recent last):");
|
|
111
|
+
if (input.feedback.length === 0) {
|
|
112
|
+
lines.push("(no feedback events recorded — distil from the asset itself)");
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
for (const event of input.feedback) {
|
|
116
|
+
const meta = event.metadata ? ` ${JSON.stringify(event.metadata)}` : "";
|
|
117
|
+
lines.push(`- ${event.ts} ${event.eventType}${meta}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
lines.push("");
|
|
121
|
+
lines.push("Produce the lesson markdown file now.");
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
|
124
|
+
// ── Main entry point ────────────────────────────────────────────────────────
|
|
125
|
+
/**
|
|
126
|
+
* Run a single bounded distillation pass for `ref`. Always emits exactly one
|
|
127
|
+
* `distill_invoked` event (with `outcome` in the metadata) regardless of the
|
|
128
|
+
* branch taken — so observers can count invocations cheaply.
|
|
129
|
+
*/
|
|
130
|
+
export async function akmDistill(options) {
|
|
131
|
+
const inputRef = options.ref.trim();
|
|
132
|
+
if (!inputRef) {
|
|
133
|
+
throw new UsageError("Asset ref is required. Usage: akm distill <ref>", "MISSING_REQUIRED_ARGUMENT");
|
|
134
|
+
}
|
|
135
|
+
// Validate the ref shape up front so a typo never reaches the LLM.
|
|
136
|
+
parseAssetRef(inputRef);
|
|
137
|
+
const lessonRef = deriveLessonRef(inputRef);
|
|
138
|
+
const config = options.config ?? loadConfig();
|
|
139
|
+
const stash = options.stashDir ?? resolveStashDir();
|
|
140
|
+
const chat = options.chat ?? chatCompletion;
|
|
141
|
+
const lookup = options.lookupFn ?? defaultLookup;
|
|
142
|
+
const readEventsImpl = options.readEventsFn ?? readEvents;
|
|
143
|
+
// Best-effort load: when the asset is not yet indexed we still proceed —
|
|
144
|
+
// the LLM is asked to distil from "available signal" (feedback alone).
|
|
145
|
+
let assetContent = null;
|
|
146
|
+
try {
|
|
147
|
+
const filePath = await lookup(inputRef);
|
|
148
|
+
if (filePath && fs.existsSync(filePath)) {
|
|
149
|
+
assetContent = fs.readFileSync(filePath, "utf8");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
assetContent = null;
|
|
154
|
+
}
|
|
155
|
+
const { events } = readEventsImpl({ ref: inputRef, type: "feedback" });
|
|
156
|
+
// #267 — feedback exclusion. Filter events whose `ref` matches the
|
|
157
|
+
// exclusion list BEFORE the prompt is built. The original event stream
|
|
158
|
+
// is never mutated; only the `feedback` slice that reaches the LLM is
|
|
159
|
+
// affected. The exclusion set is normalised through `parseAssetRef` →
|
|
160
|
+
// re-serialised so callers can pass canonical or origin-prefixed refs
|
|
161
|
+
// and the comparison still works against the event payload's `ref`.
|
|
162
|
+
const exclusionList = options.excludeFeedbackFromRefs ?? [];
|
|
163
|
+
const exclusionSet = new Set(exclusionList.map((ref) => ref.trim()).filter((ref) => ref.length > 0));
|
|
164
|
+
const originalEventCount = events.length;
|
|
165
|
+
const filteredEvents = exclusionSet.size > 0 ? events.filter((e) => !(e.ref !== undefined && exclusionSet.has(e.ref))) : events;
|
|
166
|
+
const filteredFeedbackCount = originalEventCount - filteredEvents.length;
|
|
167
|
+
const feedbackFullyFiltered = exclusionSet.size > 0 && originalEventCount > 0 && filteredEvents.length === 0;
|
|
168
|
+
const feedback = filteredEvents.slice(-20).map((e) => ({
|
|
169
|
+
ts: e.ts,
|
|
170
|
+
eventType: e.eventType,
|
|
171
|
+
...(e.metadata !== undefined ? { metadata: e.metadata } : {}),
|
|
172
|
+
}));
|
|
173
|
+
const userPrompt = buildDistillPrompt({ inputRef, assetContent, feedback });
|
|
174
|
+
const messages = [
|
|
175
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
176
|
+
{ role: "user", content: userPrompt },
|
|
177
|
+
];
|
|
178
|
+
// Single bounded LLM call. The wrapper handles the gate-check, 30 s
|
|
179
|
+
// timeout, and error fallback (returning `null`).
|
|
180
|
+
const raw = await tryLlmFeature("feedback_distillation", config, async () => {
|
|
181
|
+
if (!config.llm) {
|
|
182
|
+
// No LLM connection configured — treat as gate-disabled. Throwing
|
|
183
|
+
// here lets `tryLlmFeature` route us through the "error" fallback,
|
|
184
|
+
// which is the same graceful skipped path.
|
|
185
|
+
throw new ConfigError("No LLM connection configured. Set `llm.endpoint` and `llm.model` in the akm config.", "LLM_NOT_CONFIGURED");
|
|
186
|
+
}
|
|
187
|
+
return chat(config.llm, messages);
|
|
188
|
+
}, null);
|
|
189
|
+
if (raw === null || raw.trim() === "") {
|
|
190
|
+
appendEvent({
|
|
191
|
+
eventType: "distill_invoked",
|
|
192
|
+
ref: inputRef,
|
|
193
|
+
metadata: {
|
|
194
|
+
outcome: "skipped",
|
|
195
|
+
lessonRef,
|
|
196
|
+
...(exclusionSet.size > 0 ? { filteredFeedbackCount } : {}),
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
schemaVersion: 1,
|
|
201
|
+
ok: true,
|
|
202
|
+
outcome: "skipped",
|
|
203
|
+
inputRef,
|
|
204
|
+
lessonRef,
|
|
205
|
+
message: "feedback distillation is disabled or the LLM call failed; no proposal created.",
|
|
206
|
+
...(exclusionSet.size > 0 ? { filteredFeedbackCount, feedbackFullyFiltered } : {}),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// Strip any stray fence the LLM might have added around the markdown.
|
|
210
|
+
const content = stripMarkdownFences(raw);
|
|
211
|
+
// Parse + lint the lesson before creating the proposal. The lint is the
|
|
212
|
+
// canonical gate for required frontmatter (v1 spec §13). On failure we
|
|
213
|
+
// surface a structured error and exit non-zero — but still emit
|
|
214
|
+
// `distill_invoked` so the failure is observable.
|
|
215
|
+
const lintReport = lintLessonContent(content, `distill:${inputRef}`);
|
|
216
|
+
if (lintReport.findings.length > 0) {
|
|
217
|
+
appendEvent({
|
|
218
|
+
eventType: "distill_invoked",
|
|
219
|
+
ref: inputRef,
|
|
220
|
+
metadata: {
|
|
221
|
+
outcome: "validation_failed",
|
|
222
|
+
lessonRef,
|
|
223
|
+
findingKinds: lintReport.findings.map((f) => f.kind),
|
|
224
|
+
...(exclusionSet.size > 0 ? { filteredFeedbackCount } : {}),
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
const message = lintReport.findings.map((f) => f.message).join("\n");
|
|
228
|
+
throw new UsageError(`Distilled lesson failed validation:\n${message}`, "MISSING_REQUIRED_ARGUMENT", "Lessons require non-empty `description` and `when_to_use` frontmatter fields. See v1 spec §13.");
|
|
229
|
+
}
|
|
230
|
+
// Round-trip the parsed frontmatter so the proposal carries it as a
|
|
231
|
+
// structured payload alongside the raw content (matches the shape used by
|
|
232
|
+
// other proposal sources).
|
|
233
|
+
const parsed = parseFrontmatter(content);
|
|
234
|
+
const proposal = createProposal(stash, {
|
|
235
|
+
ref: lessonRef,
|
|
236
|
+
source: "distill",
|
|
237
|
+
...(options.sourceRun !== undefined ? { sourceRun: options.sourceRun } : {}),
|
|
238
|
+
payload: {
|
|
239
|
+
content,
|
|
240
|
+
...(Object.keys(parsed.data).length > 0 ? { frontmatter: parsed.data } : {}),
|
|
241
|
+
},
|
|
242
|
+
}, options.ctx);
|
|
243
|
+
appendEvent({
|
|
244
|
+
eventType: "distill_invoked",
|
|
245
|
+
ref: inputRef,
|
|
246
|
+
metadata: {
|
|
247
|
+
outcome: "queued",
|
|
248
|
+
lessonRef,
|
|
249
|
+
proposalId: proposal.id,
|
|
250
|
+
...(options.sourceRun !== undefined ? { sourceRun: options.sourceRun } : {}),
|
|
251
|
+
...(exclusionSet.size > 0 ? { filteredFeedbackCount } : {}),
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
schemaVersion: 1,
|
|
256
|
+
ok: true,
|
|
257
|
+
outcome: "queued",
|
|
258
|
+
inputRef,
|
|
259
|
+
lessonRef,
|
|
260
|
+
proposalId: proposal.id,
|
|
261
|
+
proposal,
|
|
262
|
+
...(exclusionSet.size > 0 ? { filteredFeedbackCount, feedbackFullyFiltered } : {}),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
266
|
+
async function defaultLookup(ref) {
|
|
267
|
+
try {
|
|
268
|
+
const entry = await indexerLookup(parseAssetRef(ref));
|
|
269
|
+
return entry?.filePath ?? null;
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/** Best-effort fence stripping. Keeps the body intact when no fence is present. */
|
|
276
|
+
function stripMarkdownFences(raw) {
|
|
277
|
+
const trimmed = raw.trim();
|
|
278
|
+
// Only strip outer triple-fence pairs — leave inner code blocks alone.
|
|
279
|
+
const fence = trimmed.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/i);
|
|
280
|
+
if (fence)
|
|
281
|
+
return fence[1].trim();
|
|
282
|
+
return trimmed;
|
|
283
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `akm events list` and `akm events tail` (#204).
|
|
3
|
+
*
|
|
4
|
+
* Programmatic surface — the CLI dispatcher in `src/cli.ts` registers two
|
|
5
|
+
* verbs that delegate here. Both return JSON envelopes shaped by
|
|
6
|
+
* `src/output/shapes.ts` so the output flows through the same shape and
|
|
7
|
+
* text-renderer pipeline as the rest of the CLI (no silent
|
|
8
|
+
* `JSON.stringify` fallback).
|
|
9
|
+
*/
|
|
10
|
+
import { parseAssetRef } from "../core/asset-ref";
|
|
11
|
+
import { UsageError } from "../core/errors";
|
|
12
|
+
import { readEvents, tailEvents } from "../core/events";
|
|
13
|
+
/**
|
|
14
|
+
* Parse `--since` accepting either a byte-offset cursor (`@offset:<int>`) for
|
|
15
|
+
* cross-process resumption, or a timestamp / epoch-ms (the existing form).
|
|
16
|
+
* Returns one of `{ sinceOffset }` or `{ since }`.
|
|
17
|
+
*/
|
|
18
|
+
function parseSinceFlag(since) {
|
|
19
|
+
if (since === undefined)
|
|
20
|
+
return {};
|
|
21
|
+
const trimmed = since.trim();
|
|
22
|
+
if (!trimmed) {
|
|
23
|
+
throw new UsageError("--since cannot be empty.", "INVALID_FLAG_VALUE");
|
|
24
|
+
}
|
|
25
|
+
if (trimmed.startsWith("@offset:")) {
|
|
26
|
+
const raw = trimmed.slice("@offset:".length);
|
|
27
|
+
const value = Number.parseInt(raw, 10);
|
|
28
|
+
if (Number.isNaN(value) || value < 0) {
|
|
29
|
+
throw new UsageError(`Invalid --since byte offset: "${since}". Expected @offset:<non-negative integer>.`, "INVALID_FLAG_VALUE");
|
|
30
|
+
}
|
|
31
|
+
return { sinceOffset: value };
|
|
32
|
+
}
|
|
33
|
+
return { since: normalizeSince(trimmed) };
|
|
34
|
+
}
|
|
35
|
+
function validateRef(ref) {
|
|
36
|
+
if (ref === undefined)
|
|
37
|
+
return undefined;
|
|
38
|
+
const trimmed = ref.trim();
|
|
39
|
+
if (!trimmed) {
|
|
40
|
+
throw new UsageError("--ref cannot be empty.", "INVALID_FLAG_VALUE");
|
|
41
|
+
}
|
|
42
|
+
parseAssetRef(trimmed);
|
|
43
|
+
return trimmed;
|
|
44
|
+
}
|
|
45
|
+
function normalizeSince(since) {
|
|
46
|
+
if (since === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
const trimmed = since.trim();
|
|
49
|
+
if (!trimmed) {
|
|
50
|
+
throw new UsageError("--since cannot be empty.", "INVALID_FLAG_VALUE");
|
|
51
|
+
}
|
|
52
|
+
// Accept ISO timestamp (preferred), epoch ms, or plain date.
|
|
53
|
+
if (/^\d+$/.test(trimmed)) {
|
|
54
|
+
const ms = Number.parseInt(trimmed, 10);
|
|
55
|
+
const d = new Date(ms);
|
|
56
|
+
if (Number.isNaN(d.getTime())) {
|
|
57
|
+
throw new UsageError(`Invalid --since value: ${since}`, "INVALID_FLAG_VALUE");
|
|
58
|
+
}
|
|
59
|
+
return d.toISOString();
|
|
60
|
+
}
|
|
61
|
+
const parsed = new Date(trimmed);
|
|
62
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
63
|
+
throw new UsageError(`Invalid --since value: ${since}. Expected ISO timestamp (e.g. 2026-04-01T00:00:00Z) or epoch ms.`, "INVALID_FLAG_VALUE");
|
|
64
|
+
}
|
|
65
|
+
return parsed.toISOString();
|
|
66
|
+
}
|
|
67
|
+
export function akmEventsList(options = {}) {
|
|
68
|
+
const ref = validateRef(options.ref);
|
|
69
|
+
const parsed = parseSinceFlag(options.since);
|
|
70
|
+
const result = readEvents({ since: parsed.since, sinceOffset: parsed.sinceOffset, type: options.type, ref }, options.ctx);
|
|
71
|
+
return {
|
|
72
|
+
schemaVersion: 1,
|
|
73
|
+
totalCount: result.events.length,
|
|
74
|
+
...(ref !== undefined ? { ref } : {}),
|
|
75
|
+
...(options.type !== undefined ? { type: options.type } : {}),
|
|
76
|
+
...(parsed.since !== undefined ? { since: parsed.since } : {}),
|
|
77
|
+
...(parsed.sinceOffset !== undefined ? { sinceOffset: parsed.sinceOffset } : {}),
|
|
78
|
+
nextOffset: result.nextOffset,
|
|
79
|
+
events: result.events,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export async function akmEventsTail(options = {}) {
|
|
83
|
+
const ref = validateRef(options.ref);
|
|
84
|
+
const parsed = parseSinceFlag(options.since);
|
|
85
|
+
const tailOptions = {
|
|
86
|
+
since: parsed.since,
|
|
87
|
+
sinceOffset: parsed.sinceOffset,
|
|
88
|
+
type: options.type,
|
|
89
|
+
ref,
|
|
90
|
+
intervalMs: options.intervalMs,
|
|
91
|
+
maxDurationMs: options.maxDurationMs,
|
|
92
|
+
maxEvents: options.maxEvents,
|
|
93
|
+
signal: options.signal,
|
|
94
|
+
onEvent: options.onEvent,
|
|
95
|
+
};
|
|
96
|
+
const result = await tailEvents(tailOptions, options.ctx);
|
|
97
|
+
return {
|
|
98
|
+
schemaVersion: 1,
|
|
99
|
+
totalCount: result.events.length,
|
|
100
|
+
...(ref !== undefined ? { ref } : {}),
|
|
101
|
+
...(options.type !== undefined ? { type: options.type } : {}),
|
|
102
|
+
...(parsed.since !== undefined ? { since: parsed.since } : {}),
|
|
103
|
+
...(parsed.sinceOffset !== undefined ? { sinceOffset: parsed.sinceOffset } : {}),
|
|
104
|
+
nextOffset: result.nextOffset,
|
|
105
|
+
events: result.events,
|
|
106
|
+
reason: result.reason,
|
|
107
|
+
};
|
|
108
|
+
}
|