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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { toErrorMessage } from "../core/common";
|
|
2
2
|
import { DEFAULT_CONFIG, loadConfig } from "../core/config";
|
|
3
|
+
import { warn } from "../core/warn";
|
|
3
4
|
import { resolveProviderFactory } from "../registry/factory";
|
|
4
5
|
// ── Eagerly import providers to trigger self-registration ───────────────────
|
|
5
6
|
import "../registry/providers/index";
|
|
@@ -22,6 +23,9 @@ export async function searchRegistry(query, options) {
|
|
|
22
23
|
return provider.search({ query: trimmed, limit, includeAssets: options?.includeAssets });
|
|
23
24
|
}));
|
|
24
25
|
// Merge results grouped by provider
|
|
26
|
+
// Each provider batch is normalized to [0, 1] before merging so that raw
|
|
27
|
+
// scores from different providers (e.g. static-index can exceed 1.85 while
|
|
28
|
+
// skills-sh uses installs-relative scoring) are comparable in the merged list.
|
|
25
29
|
const allHits = [];
|
|
26
30
|
const allAssetHits = [];
|
|
27
31
|
for (let i = 0; i < results.length; i++) {
|
|
@@ -35,23 +39,34 @@ export async function searchRegistry(query, options) {
|
|
|
35
39
|
continue;
|
|
36
40
|
const registryLabel = entries[i].name ? `"${entries[i].name}"` : entries[i].url;
|
|
37
41
|
let dropped = 0;
|
|
42
|
+
const validHits = [];
|
|
38
43
|
for (const hit of value.hits) {
|
|
39
44
|
if (isCompleteHit(hit)) {
|
|
40
|
-
|
|
45
|
+
validHits.push(hit);
|
|
41
46
|
}
|
|
42
47
|
else {
|
|
43
48
|
dropped++;
|
|
44
49
|
}
|
|
45
50
|
}
|
|
51
|
+
// Normalize scores within this provider's batch before merging
|
|
52
|
+
normalizeScores(validHits);
|
|
53
|
+
for (const hit of validHits) {
|
|
54
|
+
allHits.push(hit);
|
|
55
|
+
}
|
|
46
56
|
if (value.assetHits) {
|
|
57
|
+
const validAssetHits = [];
|
|
47
58
|
for (const hit of value.assetHits) {
|
|
48
59
|
if (isCompleteAssetHit(hit)) {
|
|
49
|
-
|
|
60
|
+
validAssetHits.push(hit);
|
|
50
61
|
}
|
|
51
62
|
else {
|
|
52
63
|
dropped++;
|
|
53
64
|
}
|
|
54
65
|
}
|
|
66
|
+
normalizeScores(validAssetHits);
|
|
67
|
+
for (const hit of validAssetHits) {
|
|
68
|
+
allAssetHits.push(hit);
|
|
69
|
+
}
|
|
55
70
|
}
|
|
56
71
|
if (dropped > 0) {
|
|
57
72
|
warnings.push(`Registry ${registryLabel} returned ${dropped} incomplete hit(s); dropped from response.`);
|
|
@@ -59,7 +74,7 @@ export async function searchRegistry(query, options) {
|
|
|
59
74
|
if (value.warnings)
|
|
60
75
|
warnings.push(...value.warnings);
|
|
61
76
|
}
|
|
62
|
-
// Sort merged hits by score descending, apply limit
|
|
77
|
+
// Sort merged hits by normalized score descending, apply limit
|
|
63
78
|
allHits.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
64
79
|
const limitedHits = allHits.slice(0, limit);
|
|
65
80
|
allAssetHits.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
@@ -79,6 +94,11 @@ export async function searchRegistry(query, options) {
|
|
|
79
94
|
* 1. AKM_REGISTRY_URL env var (CI override, comma-separated)
|
|
80
95
|
* 2. config.registries (filtered by enabled !== false)
|
|
81
96
|
* 3. Default registries from DEFAULT_CONFIG
|
|
97
|
+
*
|
|
98
|
+
* AKM_REGISTRY_URL syntax (comma-separated):
|
|
99
|
+
* - Bare URL: `https://example.com/index.json` → defaults to provider "static-index"
|
|
100
|
+
* - Typed URL: `skills-sh::https://skills.sh/api` → explicit provider type
|
|
101
|
+
* Format: `<provider-type>::<url>`
|
|
82
102
|
*/
|
|
83
103
|
export function resolveRegistries(configRegistries) {
|
|
84
104
|
// Allow env var override (comma-separated URLs) — CI escape hatch
|
|
@@ -86,14 +106,30 @@ export function resolveRegistries(configRegistries) {
|
|
|
86
106
|
if (envUrls) {
|
|
87
107
|
const entries = [];
|
|
88
108
|
for (const raw of envUrls.split(",")) {
|
|
89
|
-
const
|
|
90
|
-
if (!
|
|
109
|
+
const trimmed = raw.trim();
|
|
110
|
+
if (!trimmed)
|
|
91
111
|
continue;
|
|
112
|
+
// Parse optional `<provider-type>::<url>` prefix
|
|
113
|
+
let provider;
|
|
114
|
+
let url;
|
|
115
|
+
const colonColonIdx = trimmed.indexOf("::");
|
|
116
|
+
if (colonColonIdx !== -1 && !trimmed.startsWith("http://") && !trimmed.startsWith("https://")) {
|
|
117
|
+
// Only treat as `provider::url` if the prefix doesn't look like a URL scheme itself
|
|
118
|
+
provider = trimmed.slice(0, colonColonIdx).trim();
|
|
119
|
+
url = trimmed.slice(colonColonIdx + 2).trim();
|
|
120
|
+
if (!provider) {
|
|
121
|
+
warn(`[akm] Ignoring AKM_REGISTRY_URL entry: empty provider type before "::" in "${trimmed}"`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
url = trimmed;
|
|
127
|
+
}
|
|
92
128
|
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
93
|
-
|
|
129
|
+
warn(`[akm] Ignoring AKM_REGISTRY_URL entry: must start with http:// or https://, got "${url}"`);
|
|
94
130
|
continue;
|
|
95
131
|
}
|
|
96
|
-
entries.push({ url });
|
|
132
|
+
entries.push(provider ? { url, provider } : { url });
|
|
97
133
|
}
|
|
98
134
|
return entries;
|
|
99
135
|
}
|
|
@@ -112,6 +148,34 @@ function createProvider(entry, warnings) {
|
|
|
112
148
|
return factory(entry);
|
|
113
149
|
}
|
|
114
150
|
// ── Utilities ───────────────────────────────────────────────────────────────
|
|
151
|
+
/**
|
|
152
|
+
* Normalize the `score` field of a batch of hits in-place to [0, 1].
|
|
153
|
+
*
|
|
154
|
+
* Different registry providers use incompatible score scales
|
|
155
|
+
* (static-index can exceed 1.85; skills-sh uses installs-relative values
|
|
156
|
+
* in [0, 1]). Normalizing each provider's batch independently before merging
|
|
157
|
+
* makes the merged sort order meaningful.
|
|
158
|
+
*
|
|
159
|
+
* When all scores are identical (or absent), scores are left unchanged so
|
|
160
|
+
* relative ordering within the batch is preserved (all-same is effectively
|
|
161
|
+
* already normalized).
|
|
162
|
+
*/
|
|
163
|
+
function normalizeScores(hits) {
|
|
164
|
+
if (hits.length === 0)
|
|
165
|
+
return;
|
|
166
|
+
const rawScores = hits.map((h) => h.score ?? 0);
|
|
167
|
+
const max = Math.max(...rawScores);
|
|
168
|
+
if (max <= 0)
|
|
169
|
+
return; // all zero or negative — leave as-is
|
|
170
|
+
const min = Math.min(...rawScores);
|
|
171
|
+
const range = max - min;
|
|
172
|
+
for (let i = 0; i < hits.length; i++) {
|
|
173
|
+
const raw = rawScores[i];
|
|
174
|
+
// Min-max normalize: [0, 1]. When all scores are equal (range === 0),
|
|
175
|
+
// fall back to dividing by max so the value stays in [0, 1].
|
|
176
|
+
hits[i].score = range > 0 ? (raw - min) / range : raw / max;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
115
179
|
function clampLimit(limit) {
|
|
116
180
|
if (!limit || !Number.isFinite(limit))
|
|
117
181
|
return 20;
|
|
@@ -10,6 +10,7 @@ import { toErrorMessage, tryReadStdinText } from "../core/common";
|
|
|
10
10
|
import { loadConfig } from "../core/config";
|
|
11
11
|
import { UsageError } from "../core/errors";
|
|
12
12
|
import { warn } from "../core/warn";
|
|
13
|
+
import { SCOPE_KEYS } from "../indexer/metadata";
|
|
13
14
|
/**
|
|
14
15
|
* Parse a shorthand duration string to a number of milliseconds.
|
|
15
16
|
* Supports: `30d` (days), `12h` (hours), `6m` (months, approximated as 30d).
|
|
@@ -52,6 +53,17 @@ export function buildMemoryFrontmatter(fields) {
|
|
|
52
53
|
obj.expires = fields.expires;
|
|
53
54
|
if (fields.subjective)
|
|
54
55
|
obj.subjective = true;
|
|
56
|
+
// Scope keys are emitted as flat top-level keys (`scope_user`, …) so the
|
|
57
|
+
// existing one-level frontmatter parser can read them without nesting.
|
|
58
|
+
// A scope object with no populated values is dropped.
|
|
59
|
+
if (fields.scope) {
|
|
60
|
+
for (const key of SCOPE_KEYS) {
|
|
61
|
+
const value = fields.scope[key];
|
|
62
|
+
if (typeof value === "string" && value.trim()) {
|
|
63
|
+
obj[`scope_${key}`] = value.trim();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
55
67
|
// No fields populated → emit a bare delimiter pair so callers don't
|
|
56
68
|
// produce `---\n{}\n---` (the YAML serializer's empty-object form).
|
|
57
69
|
if (Object.keys(obj).length === 0)
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { loadConfig } from "../core/config";
|
|
12
12
|
import { UsageError } from "../core/errors";
|
|
13
|
+
import { appendEvent } from "../core/events";
|
|
13
14
|
import { closeDatabase, openDatabase } from "../indexer/db";
|
|
14
15
|
import { searchLocal } from "../indexer/db-search";
|
|
15
16
|
import { resolveSourceEntries } from "../indexer/search-source";
|
|
@@ -45,6 +46,8 @@ export async function akmSearch(input) {
|
|
|
45
46
|
// Primary stash directory — used for DB path lookups and as the default
|
|
46
47
|
// stash root. Safe because the empty-sources case is handled above.
|
|
47
48
|
const stashDir = sources[0].path;
|
|
49
|
+
const filters = normalizeScopeFilters(input.filters);
|
|
50
|
+
const includeProposed = input.includeProposed === true;
|
|
48
51
|
const localResult = source === "registry"
|
|
49
52
|
? undefined
|
|
50
53
|
: await searchLocal({
|
|
@@ -54,6 +57,8 @@ export async function akmSearch(input) {
|
|
|
54
57
|
stashDir,
|
|
55
58
|
sources,
|
|
56
59
|
config,
|
|
60
|
+
filters,
|
|
61
|
+
includeProposed,
|
|
57
62
|
});
|
|
58
63
|
const registryResult = source === "stash" ? undefined : await searchRegistry(query, { limit, registries: config.registries });
|
|
59
64
|
if (source === "stash") {
|
|
@@ -76,6 +81,8 @@ export async function akmSearch(input) {
|
|
|
76
81
|
// prefixed), otherwise derive it from source + ref for backward compat.
|
|
77
82
|
const installRef = hit.installRef ??
|
|
78
83
|
(hit.source === "npm" ? `npm:${hit.ref}` : hit.source === "git" ? `git+${hit.ref}` : `github:${hit.ref}`);
|
|
84
|
+
// The legacy registry boolean `curated` was removed in v1 (spec §4.2).
|
|
85
|
+
// Hit-level `warnings` are forwarded when the provider surfaced any.
|
|
79
86
|
return {
|
|
80
87
|
type: "registry",
|
|
81
88
|
name: hit.title,
|
|
@@ -83,8 +90,8 @@ export async function akmSearch(input) {
|
|
|
83
90
|
description: hit.description,
|
|
84
91
|
action: `akm add ${installRef} -> then search again`,
|
|
85
92
|
score: hit.score,
|
|
86
|
-
curated: hit.curated,
|
|
87
93
|
registryName: hit.registryName,
|
|
94
|
+
...(hit.warnings && hit.warnings.length > 0 ? { warnings: hit.warnings } : {}),
|
|
88
95
|
};
|
|
89
96
|
});
|
|
90
97
|
if (source === "registry") {
|
|
@@ -141,13 +148,30 @@ function resolveEntryIds(db, hits) {
|
|
|
141
148
|
/**
|
|
142
149
|
* Fire-and-forget: log a search event to the usage_events table.
|
|
143
150
|
* Never blocks the caller; errors are silently ignored.
|
|
151
|
+
*
|
|
152
|
+
* Result count semantics:
|
|
153
|
+
* - `stashHitCount`: number of local stash hits (response.hits, source-only
|
|
154
|
+
* entries). Always 0 for registry-only searches.
|
|
155
|
+
* - `registryHitCount`: number of registry hits (response.registryHits).
|
|
156
|
+
* Only non-zero when source is "registry" or "both".
|
|
157
|
+
* - `resultCount`: total across both pools so telemetry reflects the actual
|
|
158
|
+
* number of results the user saw, regardless of source mode.
|
|
159
|
+
*
|
|
160
|
+
* Per-entry events are recorded only for stash hits because registry hits
|
|
161
|
+
* have no local entry_id to reference.
|
|
144
162
|
*/
|
|
145
163
|
function logSearchEvent(query, response, existingDb) {
|
|
164
|
+
// Emit a structured event to events.jsonl so workflow-trace consumers
|
|
165
|
+
// detect akm search invocations without relying on stdout scraping.
|
|
166
|
+
const stashHits = response.hits.filter((h) => h.type !== "registry");
|
|
167
|
+
appendEvent({
|
|
168
|
+
eventType: "search",
|
|
169
|
+
metadata: { query, hitCount: stashHits.length, resultRefs: stashHits.map((h) => h.ref) },
|
|
170
|
+
});
|
|
146
171
|
try {
|
|
147
172
|
const db = existingDb ?? openDatabase();
|
|
148
173
|
try {
|
|
149
|
-
const
|
|
150
|
-
const resolved = resolveEntryIds(db, stashHits);
|
|
174
|
+
const resolved = resolveEntryIds(db, stashHits.slice(0, 50));
|
|
151
175
|
for (const { entryId, ref } of resolved) {
|
|
152
176
|
insertUsageEvent(db, {
|
|
153
177
|
event_type: "search",
|
|
@@ -156,10 +180,19 @@ function logSearchEvent(query, response, existingDb) {
|
|
|
156
180
|
entry_ref: ref,
|
|
157
181
|
});
|
|
158
182
|
}
|
|
183
|
+
// Count registry hits separately so registry-only searches record a
|
|
184
|
+
// non-zero resultCount. response.hits is always [] when source="registry".
|
|
185
|
+
const stashHitCount = response.hits.length;
|
|
186
|
+
const registryHitCount = Array.isArray(response.registryHits) ? response.registryHits.length : 0;
|
|
159
187
|
insertUsageEvent(db, {
|
|
160
188
|
event_type: "search",
|
|
161
189
|
query,
|
|
162
|
-
metadata: JSON.stringify({
|
|
190
|
+
metadata: JSON.stringify({
|
|
191
|
+
resultCount: stashHitCount + registryHitCount,
|
|
192
|
+
stashHitCount,
|
|
193
|
+
registryHitCount,
|
|
194
|
+
resolvedCount: resolved.length,
|
|
195
|
+
}),
|
|
163
196
|
});
|
|
164
197
|
}
|
|
165
198
|
finally {
|
|
@@ -188,6 +221,73 @@ export function parseSearchSource(source) {
|
|
|
188
221
|
return "stash";
|
|
189
222
|
throw new UsageError(`Invalid value for --source: ${String(source)}. Expected one of: stash|registry|both`, "INVALID_SOURCE_VALUE");
|
|
190
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Strip empty / non-string values from a scope filter object. Returns
|
|
226
|
+
* `undefined` when nothing meaningful remains, so callers don't pay for an
|
|
227
|
+
* empty-filter post-walk in `searchLocal`.
|
|
228
|
+
*/
|
|
229
|
+
function normalizeScopeFilters(raw) {
|
|
230
|
+
if (!raw)
|
|
231
|
+
return undefined;
|
|
232
|
+
const out = {};
|
|
233
|
+
for (const key of ["user", "agent", "run", "channel"]) {
|
|
234
|
+
const value = raw[key];
|
|
235
|
+
if (typeof value === "string" && value.trim()) {
|
|
236
|
+
out[key] = value.trim();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Parse repeated `--filter k=v` / `--scope k=v` argv tokens into a
|
|
243
|
+
* `StashEntryScope`. Throws a {@link UsageError} for malformed tokens
|
|
244
|
+
* (missing `=`, unknown key) so callers don't see ambiguous misses.
|
|
245
|
+
*
|
|
246
|
+
* Used by both `akm search --filter` and `akm show --scope`.
|
|
247
|
+
*/
|
|
248
|
+
export function parseScopeFilterFlags(values, flagName = "--filter") {
|
|
249
|
+
if (values.length === 0)
|
|
250
|
+
return undefined;
|
|
251
|
+
const out = {};
|
|
252
|
+
for (const raw of values) {
|
|
253
|
+
const eq = raw.indexOf("=");
|
|
254
|
+
if (eq <= 0) {
|
|
255
|
+
throw new UsageError(`Invalid ${flagName} value "${raw}". Expected key=value (e.g. user=alice).`);
|
|
256
|
+
}
|
|
257
|
+
const key = raw.slice(0, eq).trim();
|
|
258
|
+
const value = raw.slice(eq + 1).trim();
|
|
259
|
+
if (key !== "user" && key !== "agent" && key !== "run" && key !== "channel") {
|
|
260
|
+
throw new UsageError(`Unknown scope key "${key}" in ${flagName}. Valid keys: user, agent, run, channel.`);
|
|
261
|
+
}
|
|
262
|
+
if (!value) {
|
|
263
|
+
throw new UsageError(`${flagName} ${key}=… requires a non-empty value.`);
|
|
264
|
+
}
|
|
265
|
+
out[key] = value;
|
|
266
|
+
}
|
|
267
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Returns true iff `entry.scope` (when present) satisfies every key in
|
|
271
|
+
* `filters`. A missing `entry.scope` (legacy memories) only matches when
|
|
272
|
+
* `filters` is empty / undefined.
|
|
273
|
+
*
|
|
274
|
+
* Filter semantics:
|
|
275
|
+
* - No filter passed → all entries match (legacy behavior preserved).
|
|
276
|
+
* - `filters.user = "alice"` → entry must have `scope.user === "alice"`.
|
|
277
|
+
* - Multiple keys → AND-joined; every supplied key must match.
|
|
278
|
+
*/
|
|
279
|
+
export function entryMatchesScopeFilters(scope, filters) {
|
|
280
|
+
if (!filters)
|
|
281
|
+
return true;
|
|
282
|
+
for (const key of ["user", "agent", "run", "channel"]) {
|
|
283
|
+
const expected = filters[key];
|
|
284
|
+
if (expected === undefined)
|
|
285
|
+
continue;
|
|
286
|
+
if (!scope || scope[key] !== expected)
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
191
291
|
/**
|
|
192
292
|
* Merge stash hits and registry hits via simple concatenation.
|
|
193
293
|
*/
|
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fetchWithRetry, IS_WINDOWS } from "../core/common";
|
|
6
|
+
import { warn } from "../core/warn";
|
|
6
7
|
import { githubHeaders } from "../integrations/github";
|
|
7
8
|
const REPO = "itlackey/akm";
|
|
8
9
|
const DEFAULT_PACKAGE_NAME = "akm-cli";
|
|
@@ -147,7 +148,7 @@ export async function performUpgrade(check, opts) {
|
|
|
147
148
|
const checksumsResponse = await fetchWithRetry(checksumsUrl);
|
|
148
149
|
if (!checksumsResponse.ok) {
|
|
149
150
|
if (skipChecksum) {
|
|
150
|
-
|
|
151
|
+
warn(`WARNING: checksums.txt fetch failed (HTTP ${checksumsResponse.status}). Proceeding without verification because --skip-checksum was provided.`);
|
|
151
152
|
}
|
|
152
153
|
else {
|
|
153
154
|
throw new Error(`Checksum verification failed: could not fetch ${checksumsUrl} (HTTP ${checksumsResponse.status}). ` +
|
|
@@ -166,7 +167,7 @@ export async function performUpgrade(check, opts) {
|
|
|
166
167
|
}
|
|
167
168
|
else {
|
|
168
169
|
if (skipChecksum) {
|
|
169
|
-
|
|
170
|
+
warn(`WARNING: ${binaryName} not found in checksums.txt. Proceeding without verification because --skip-checksum was provided.`);
|
|
170
171
|
}
|
|
171
172
|
else {
|
|
172
173
|
throw new Error(`Checksum verification failed: ${binaryName} not listed in checksums.txt. ` +
|
|
@@ -182,7 +183,7 @@ export async function performUpgrade(check, opts) {
|
|
|
182
183
|
}
|
|
183
184
|
// Network or parse failure
|
|
184
185
|
if (skipChecksum) {
|
|
185
|
-
|
|
186
|
+
warn(`WARNING: Could not fetch or parse checksums: ${err instanceof Error ? err.message : String(err)}. Proceeding because --skip-checksum was provided.`);
|
|
186
187
|
}
|
|
187
188
|
else {
|
|
188
189
|
throw new Error(`Checksum verification failed: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
@@ -24,6 +24,7 @@ import path from "node:path";
|
|
|
24
24
|
import { parseAssetRef } from "../core/asset-ref";
|
|
25
25
|
import { loadConfig } from "../core/config";
|
|
26
26
|
import { NotFoundError, UsageError } from "../core/errors";
|
|
27
|
+
import { appendEvent, readEvents } from "../core/events";
|
|
27
28
|
import { parseFrontmatter, toStringOrUndefined } from "../core/frontmatter";
|
|
28
29
|
import { closeDatabase, findEntryIdByRef, openDatabase } from "../indexer/db";
|
|
29
30
|
import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "../indexer/file-context";
|
|
@@ -35,6 +36,7 @@ import { resolveSourcesForOrigin } from "../registry/origin-resolve";
|
|
|
35
36
|
// Eagerly import source providers to trigger self-registration.
|
|
36
37
|
import "../sources/providers/index";
|
|
37
38
|
import { resolveAssetPath } from "../sources/resolve";
|
|
39
|
+
import { getActiveWorkflowRun } from "../workflows/runs";
|
|
38
40
|
/**
|
|
39
41
|
* Show a wiki root (no page path) — returns the same payload as
|
|
40
42
|
* `akm wiki show <name>`.
|
|
@@ -129,10 +131,77 @@ export async function akmShowUnified(input) {
|
|
|
129
131
|
}
|
|
130
132
|
// Try local filesystem (FTS5 index lookup, then on-disk fallback)
|
|
131
133
|
const result = await showLocal(input);
|
|
134
|
+
// Scope filter narrows resolution: if --scope was supplied, the asset's
|
|
135
|
+
// frontmatter scope must satisfy every supplied key. We re-read the file
|
|
136
|
+
// (cheap — already on the show hot path) so we don't have to thread scope
|
|
137
|
+
// through the renderer chain just for one verification step.
|
|
138
|
+
if (input.scope && hasAnyScopeKey(input.scope) && result.path) {
|
|
139
|
+
enforceScopeOrThrow(result.path, ref, input.scope);
|
|
140
|
+
}
|
|
141
|
+
// Count prior shows of this ref before logging the current one.
|
|
142
|
+
const priorShowCount = recentShowCount(ref);
|
|
132
143
|
logShowEvent(ref);
|
|
144
|
+
if (priorShowCount >= 2) {
|
|
145
|
+
// Agent has shown this same asset 3+ times — inject a loop-break hint.
|
|
146
|
+
result.showLoopWarning = priorShowCount + 1;
|
|
147
|
+
}
|
|
133
148
|
return result;
|
|
134
149
|
}
|
|
150
|
+
function hasAnyScopeKey(scope) {
|
|
151
|
+
return Boolean(scope.user || scope.agent || scope.run || scope.channel);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Read the asset file's frontmatter and verify its `scope_*` keys satisfy
|
|
155
|
+
* every supplied filter. Throws a {@link NotFoundError} on mismatch so the
|
|
156
|
+
* caller surfaces a uniform "not found in this scope" envelope rather than
|
|
157
|
+
* leaking out-of-scope content.
|
|
158
|
+
*/
|
|
159
|
+
function enforceScopeOrThrow(filePath, ref, scope) {
|
|
160
|
+
let raw;
|
|
161
|
+
try {
|
|
162
|
+
raw = fs.readFileSync(filePath, "utf8");
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// The file path was just resolved by the indexer/disk-walk — a read
|
|
166
|
+
// failure here means the on-disk state moved out from under us. Treat
|
|
167
|
+
// that as "not found in this scope" so the caller does not learn the
|
|
168
|
+
// file's prior contents.
|
|
169
|
+
throw new NotFoundError(`Asset not found for scope filter: ${ref}`);
|
|
170
|
+
}
|
|
171
|
+
const fm = parseFrontmatter(raw).data;
|
|
172
|
+
const expected = [
|
|
173
|
+
["user", scope.user],
|
|
174
|
+
["agent", scope.agent],
|
|
175
|
+
["run", scope.run],
|
|
176
|
+
["channel", scope.channel],
|
|
177
|
+
];
|
|
178
|
+
for (const [key, expectedValue] of expected) {
|
|
179
|
+
if (expectedValue === undefined)
|
|
180
|
+
continue;
|
|
181
|
+
const actual = toStringOrUndefined(fm[`scope_${key}`]);
|
|
182
|
+
if (actual !== expectedValue) {
|
|
183
|
+
throw new NotFoundError(`Asset "${ref}" exists but is out of scope (expected scope_${key}="${expectedValue}").`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Count how many times `ref` has been shown in the current session by reading
|
|
189
|
+
* recent events. Returns the count BEFORE the current invocation.
|
|
190
|
+
*/
|
|
191
|
+
function recentShowCount(ref) {
|
|
192
|
+
try {
|
|
193
|
+
const { events } = readEvents({ type: "show", ref });
|
|
194
|
+
return events.length;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
135
200
|
function logShowEvent(ref, existingDb) {
|
|
201
|
+
// Emit a structured event to events.jsonl so workflow-trace consumers
|
|
202
|
+
// detect akm show invocations without relying on stdout scraping.
|
|
203
|
+
const parsed = parseAssetRef(ref);
|
|
204
|
+
appendEvent({ eventType: "show", ref, metadata: { type: parsed.type, name: parsed.name } });
|
|
136
205
|
try {
|
|
137
206
|
const db = existingDb ?? openDatabase();
|
|
138
207
|
try {
|
|
@@ -251,6 +320,10 @@ export async function showLocal(input) {
|
|
|
251
320
|
editable,
|
|
252
321
|
...(!editable ? { editHint: buildEditHint(assetPath, parsed.type, parsed.name, source?.registryId) } : {}),
|
|
253
322
|
};
|
|
323
|
+
const activeRun = getActiveWorkflowRun();
|
|
324
|
+
if (activeRun) {
|
|
325
|
+
fullResponse.activeRun = activeRun;
|
|
326
|
+
}
|
|
254
327
|
if (input.detail === "brief") {
|
|
255
328
|
return buildBriefResponse(fullResponse, assetPath);
|
|
256
329
|
}
|
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { isHttpUrl, resolveStashDir } from "../core/common";
|
|
4
4
|
import { loadConfig, loadUserConfig, saveConfig } from "../core/config";
|
|
5
|
-
import { UsageError } from "../core/errors";
|
|
5
|
+
import { ConfigError, UsageError } from "../core/errors";
|
|
6
6
|
import { warn } from "../core/warn";
|
|
7
7
|
import { akmIndex } from "../indexer/indexer";
|
|
8
8
|
import { upsertLockEntry } from "../integrations/lockfile";
|
|
@@ -190,6 +190,10 @@ async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
|
|
|
190
190
|
* persisting the lock entry.
|
|
191
191
|
*/
|
|
192
192
|
async function addRegistryStash(ref, stashDir, trustThisInstall, writable, wikiName) {
|
|
193
|
+
const parsedRef = parseRegistryRef(ref);
|
|
194
|
+
if (writable === true && parsedRef.source !== "git") {
|
|
195
|
+
throw new ConfigError("writable: true is only supported on filesystem and git sources", "INVALID_CONFIG_FILE");
|
|
196
|
+
}
|
|
193
197
|
// Pre-sync registry-policy enforcement uses just the parsed ref (no fetch needed),
|
|
194
198
|
// so we keep parity with the historical behavior where `enforceRegistryInstallPolicy`
|
|
195
199
|
// ran before `extractTarGzSecure` etc.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { loadConfig, loadUserConfig, saveConfig } from "../core/config";
|
|
3
|
-
import { UsageError } from "../core/errors";
|
|
3
|
+
import { ConfigError, UsageError } from "../core/errors";
|
|
4
4
|
import { resolveSourceEntries } from "../indexer/search-source";
|
|
5
5
|
// ── Operations ──────────────────────────────────────────────────────────────
|
|
6
6
|
/**
|
|
@@ -12,6 +12,12 @@ import { resolveSourceEntries } from "../indexer/search-source";
|
|
|
12
12
|
*/
|
|
13
13
|
export function addStash(opts) {
|
|
14
14
|
const { target, name, providerType, options: providerOptions, writable } = opts;
|
|
15
|
+
if (providerType === "openviking") {
|
|
16
|
+
throw new ConfigError("openviking is not supported in akm v1.", "INVALID_CONFIG_FILE");
|
|
17
|
+
}
|
|
18
|
+
if (writable === true && providerType && providerType !== "filesystem" && providerType !== "git") {
|
|
19
|
+
throw new ConfigError("writable: true is only supported on filesystem and git sources", "INVALID_CONFIG_FILE");
|
|
20
|
+
}
|
|
15
21
|
const config = loadUserConfig();
|
|
16
22
|
const sources = [...(config.sources ?? config.stashes ?? [])];
|
|
17
23
|
const isRemoteUrl = target.startsWith("http://") ||
|
|
@@ -56,16 +56,16 @@ export function parseAssetRef(ref) {
|
|
|
56
56
|
// ── Validation ──────────────────────────────────────────────────────────────
|
|
57
57
|
function validateName(name) {
|
|
58
58
|
if (!name)
|
|
59
|
-
throw new UsageError("Empty asset name.");
|
|
59
|
+
throw new UsageError("Empty asset name.", "MISSING_REQUIRED_ARGUMENT");
|
|
60
60
|
if (name.includes("\0"))
|
|
61
|
-
throw new UsageError("Null byte in asset name.");
|
|
61
|
+
throw new UsageError("Null byte in asset name.", "MISSING_REQUIRED_ARGUMENT");
|
|
62
62
|
if (/^[A-Za-z]:/.test(name))
|
|
63
|
-
throw new UsageError("Windows drive path in asset name.");
|
|
63
|
+
throw new UsageError("Windows drive path in asset name.", "MISSING_REQUIRED_ARGUMENT");
|
|
64
64
|
const normalized = path.posix.normalize(name.replace(/\\/g, "/"));
|
|
65
65
|
if (path.posix.isAbsolute(normalized))
|
|
66
|
-
throw new UsageError("Absolute path in asset name.");
|
|
66
|
+
throw new UsageError("Absolute path in asset name.", "MISSING_REQUIRED_ARGUMENT");
|
|
67
67
|
if (normalized === ".." || normalized.startsWith("../")) {
|
|
68
|
-
throw new UsageError("Path traversal in asset name.");
|
|
68
|
+
throw new UsageError("Path traversal in asset name.", "MISSING_REQUIRED_ARGUMENT");
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
function normalizeName(name) {
|
|
@@ -90,6 +90,18 @@ const ASSET_SPECS_INTERNAL = {
|
|
|
90
90
|
rendererName: "wiki-md",
|
|
91
91
|
actionBuilder: (ref) => `akm show ${ref} -> read the wiki page`,
|
|
92
92
|
},
|
|
93
|
+
// v1 spec §13 — `lesson` asset type. Required frontmatter fields are
|
|
94
|
+
// `description` and `when_to_use`; lint enforces both (see
|
|
95
|
+
// `src/core/lesson-lint.ts`). Lessons normally arrive as proposals via
|
|
96
|
+
// `akm distill <ref>` (§14.5) and are promoted via `akm proposal accept`,
|
|
97
|
+
// but they can also be authored directly with `akm import` /
|
|
98
|
+
// `akm remember`-style flows.
|
|
99
|
+
lesson: {
|
|
100
|
+
stashDir: "lessons",
|
|
101
|
+
...markdownSpec,
|
|
102
|
+
rendererName: "lesson-md",
|
|
103
|
+
actionBuilder: (ref) => `akm show ${ref} -> read the lesson and apply when_to_use`,
|
|
104
|
+
},
|
|
93
105
|
};
|
|
94
106
|
export const ASSET_SPECS = ASSET_SPECS_INTERNAL;
|
|
95
107
|
/**
|
|
@@ -111,7 +111,7 @@ export function isWithin(candidate, root) {
|
|
|
111
111
|
* consistent even when the directory hierarchy contains symlinks (e.g.
|
|
112
112
|
* macOS /tmp → /private/tmp, or a HOME that is itself a symlink).
|
|
113
113
|
*/
|
|
114
|
-
function safeRealpath(p) {
|
|
114
|
+
export function safeRealpath(p) {
|
|
115
115
|
const resolved = path.resolve(p);
|
|
116
116
|
try {
|
|
117
117
|
return fs.realpathSync(resolved);
|