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
|
@@ -3,10 +3,49 @@ import path from "node:path";
|
|
|
3
3
|
import { deriveCanonicalAssetName, deriveCanonicalAssetNameFromStashRoot, isRelevantAssetFile, } from "../core/asset-spec";
|
|
4
4
|
import { isAssetType } from "../core/common";
|
|
5
5
|
import { parseFrontmatter, toStringOrUndefined } from "../core/frontmatter";
|
|
6
|
-
import { warn } from "../core/warn";
|
|
6
|
+
import { isVerbose, warn } from "../core/warn";
|
|
7
7
|
import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "./file-context";
|
|
8
|
+
export const SCOPE_KEYS = ["user", "agent", "run", "channel"];
|
|
8
9
|
// ── Load / Write ────────────────────────────────────────────────────────────
|
|
9
10
|
const STASH_FILENAME = ".stash.json";
|
|
11
|
+
// ── Quality semantics (v1 spec §4.2) ────────────────────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* Well-known quality values. `generated` and `curated` are included in
|
|
14
|
+
* default search; `proposed` is excluded by default and opt-in via
|
|
15
|
+
* `--include-proposed`. Unknown values warn once and remain searchable.
|
|
16
|
+
*/
|
|
17
|
+
export const KNOWN_QUALITY_VALUES = new Set(["generated", "curated", "proposed"]);
|
|
18
|
+
/** Tracks unknown quality values we've already warned about (one warn per value per process). */
|
|
19
|
+
const warnedUnknownQualityValues = new Set();
|
|
20
|
+
/**
|
|
21
|
+
* Normalize a `quality` string off a stash entry. Known values pass through
|
|
22
|
+
* untouched. Unknown values are accepted as-is (preserved verbatim on the
|
|
23
|
+
* entry) but trigger a one-time warning per unique value via the shared
|
|
24
|
+
* `warn()` helper (honours --quiet / `setQuiet()`).
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeQuality(raw) {
|
|
27
|
+
if (KNOWN_QUALITY_VALUES.has(raw))
|
|
28
|
+
return raw;
|
|
29
|
+
if (!warnedUnknownQualityValues.has(raw)) {
|
|
30
|
+
warnedUnknownQualityValues.add(raw);
|
|
31
|
+
warn(`Warning: unknown quality value "${raw}" — entry remains searchable, but consider using "generated", "curated", or "proposed" (v1 spec §4.2).`);
|
|
32
|
+
}
|
|
33
|
+
return raw;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Test-only: clear the per-process unknown-quality warning memo so a test
|
|
37
|
+
* can re-trigger the warning. Not part of the public API.
|
|
38
|
+
*/
|
|
39
|
+
export function _resetUnknownQualityWarnings() {
|
|
40
|
+
warnedUnknownQualityValues.clear();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns true if an entry's quality marks it as "proposed". Proposed
|
|
44
|
+
* entries are excluded from default search per v1 spec §4.2.
|
|
45
|
+
*/
|
|
46
|
+
export function isProposedQuality(quality) {
|
|
47
|
+
return quality === "proposed";
|
|
48
|
+
}
|
|
10
49
|
export function stashFilePath(dirPath) {
|
|
11
50
|
return path.join(dirPath, STASH_FILENAME);
|
|
12
51
|
}
|
|
@@ -97,8 +136,9 @@ export function validateStashEntry(entry) {
|
|
|
97
136
|
}
|
|
98
137
|
if (typeof e.filename === "string" && e.filename)
|
|
99
138
|
result.filename = e.filename;
|
|
100
|
-
if (e.quality === "
|
|
101
|
-
result.quality = e.quality;
|
|
139
|
+
if (typeof e.quality === "string" && e.quality.length > 0) {
|
|
140
|
+
result.quality = normalizeQuality(e.quality);
|
|
141
|
+
}
|
|
102
142
|
if (typeof e.confidence === "number" && Number.isFinite(e.confidence))
|
|
103
143
|
result.confidence = Math.max(0, Math.min(1, e.confidence));
|
|
104
144
|
if (typeof e.source === "string" &&
|
|
@@ -157,6 +197,11 @@ export function validateStashEntry(entry) {
|
|
|
157
197
|
if (filtered.length > 0)
|
|
158
198
|
result.sources = filtered;
|
|
159
199
|
}
|
|
200
|
+
if (typeof e.scope === "object" && e.scope !== null && !Array.isArray(e.scope)) {
|
|
201
|
+
const scope = normalizeScopeObject(e.scope);
|
|
202
|
+
if (scope)
|
|
203
|
+
result.scope = scope;
|
|
204
|
+
}
|
|
160
205
|
if (Array.isArray(e.parameters)) {
|
|
161
206
|
const validated = e.parameters
|
|
162
207
|
.filter((p) => {
|
|
@@ -183,6 +228,46 @@ export function validateStashEntry(entry) {
|
|
|
183
228
|
}
|
|
184
229
|
return result;
|
|
185
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Coerce a raw `{ user, agent, run, channel }` object into a clean
|
|
233
|
+
* `StashEntryScope`, dropping non-string and empty values. Returns
|
|
234
|
+
* `undefined` when no recognized keys carry a value.
|
|
235
|
+
*/
|
|
236
|
+
function normalizeScopeObject(raw) {
|
|
237
|
+
const out = {};
|
|
238
|
+
for (const key of SCOPE_KEYS) {
|
|
239
|
+
const value = raw[key];
|
|
240
|
+
if (typeof value === "string") {
|
|
241
|
+
const trimmed = value.trim();
|
|
242
|
+
if (trimmed)
|
|
243
|
+
out[key] = trimmed;
|
|
244
|
+
}
|
|
245
|
+
else if (typeof value === "number" && Number.isFinite(value)) {
|
|
246
|
+
out[key] = String(value);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Pull `scope_user` / `scope_agent` / `scope_run` / `scope_channel` out of
|
|
253
|
+
* a parsed frontmatter block and attach them as `entry.scope`. Tolerates
|
|
254
|
+
* missing or malformed values; legacy memories without these keys are left
|
|
255
|
+
* untouched (no `scope` field added).
|
|
256
|
+
*/
|
|
257
|
+
export function applyScopeFrontmatter(entry, fmData) {
|
|
258
|
+
const collected = {};
|
|
259
|
+
for (const key of SCOPE_KEYS) {
|
|
260
|
+
const fmKey = `scope_${key}`;
|
|
261
|
+
if (Object.hasOwn(fmData, fmKey)) {
|
|
262
|
+
collected[key] = fmData[fmKey];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (Object.keys(collected).length === 0)
|
|
266
|
+
return;
|
|
267
|
+
const scope = normalizeScopeObject(collected);
|
|
268
|
+
if (scope)
|
|
269
|
+
entry.scope = scope;
|
|
270
|
+
}
|
|
186
271
|
function normalizeNonEmptyStringList(value) {
|
|
187
272
|
if (typeof value === "string") {
|
|
188
273
|
const trimmed = value.trim();
|
|
@@ -400,6 +485,8 @@ export async function generateMetadata(dirPath, assetType, files, typeRoot = dir
|
|
|
400
485
|
entry.parameters = fmParams;
|
|
401
486
|
// Pass wiki-pattern frontmatter through onto the entry
|
|
402
487
|
applyWikiFrontmatter(entry, parsed.data);
|
|
488
|
+
// Pass canonical scope_* frontmatter through onto the entry
|
|
489
|
+
applyScopeFrontmatter(entry, parsed.data);
|
|
403
490
|
// Extract parameters from template placeholders ($1, $ARGUMENTS, {{named}})
|
|
404
491
|
if (entry.type === "command") {
|
|
405
492
|
const cmdParams = extractCommandParameters(parsed.content);
|
|
@@ -516,6 +603,8 @@ export async function generateMetadataFlat(stashRoot, files) {
|
|
|
516
603
|
entry.parameters = fmParams;
|
|
517
604
|
// Pass wiki-pattern frontmatter through onto the entry
|
|
518
605
|
applyWikiFrontmatter(entry, parsed.data);
|
|
606
|
+
// Pass canonical scope_* frontmatter through onto the entry
|
|
607
|
+
applyScopeFrontmatter(entry, parsed.data);
|
|
519
608
|
// Extract parameters from template placeholders ($1, $ARGUMENTS, {{named}})
|
|
520
609
|
if (entry.type === "command") {
|
|
521
610
|
const cmdParams = extractCommandParameters(parsed.content);
|
|
@@ -568,9 +657,28 @@ function buildMetadataSkipWarning(filePath, assetType, error) {
|
|
|
568
657
|
const warning = assetType === "workflow"
|
|
569
658
|
? `Skipped workflow ${filePath}:\n${detail}`
|
|
570
659
|
: `Skipped malformed ${assetType} asset at ${filePath}: ${detail}`;
|
|
660
|
+
// Workflow validation warnings are noisy on cold-start search against fresh
|
|
661
|
+
// registry-cloned content (see issue #273). At default verbosity we suppress
|
|
662
|
+
// the per-spec stderr line and rely on a one-line summary emitted by the
|
|
663
|
+
// indexer driver after the run completes. The full per-file detail is still
|
|
664
|
+
// returned in the warnings[] array (and IndexResponse.warnings) for
|
|
665
|
+
// programmatic consumers, and verbose mode restores the immediate stderr
|
|
666
|
+
// print so workflow authors keep the rich feedback they expect.
|
|
667
|
+
if (assetType === "workflow" && !isVerbose()) {
|
|
668
|
+
return warning;
|
|
669
|
+
}
|
|
571
670
|
warn(warning);
|
|
572
671
|
return warning;
|
|
573
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Returns true when a metadata-skip warning was produced by the workflow
|
|
675
|
+
* validator. Used by the indexer driver to count workflow skips for the
|
|
676
|
+
* default-verbosity summary line. Matches the prefix produced by
|
|
677
|
+
* `buildMetadataSkipWarning` for `assetType === "workflow"`.
|
|
678
|
+
*/
|
|
679
|
+
export function isWorkflowSkipWarning(warning) {
|
|
680
|
+
return warning.startsWith("Skipped workflow ");
|
|
681
|
+
}
|
|
574
682
|
function normalizeTerms(values) {
|
|
575
683
|
const normalized = new Set();
|
|
576
684
|
for (const value of values) {
|
|
@@ -224,7 +224,9 @@ function isValidDirectory(dir) {
|
|
|
224
224
|
*/
|
|
225
225
|
export async function ensureSourceCaches(config) {
|
|
226
226
|
const cfg = config ?? loadConfig();
|
|
227
|
-
|
|
227
|
+
// Use sources[] (current key) with fallback to stashes[] (deprecated, one-release compat).
|
|
228
|
+
const entries = cfg.sources ?? cfg.stashes ?? [];
|
|
229
|
+
for (const entry of entries) {
|
|
228
230
|
if (!GIT_STASH_TYPES.has(entry.type) || !entry.url || entry.enabled === false)
|
|
229
231
|
continue;
|
|
230
232
|
try {
|
|
@@ -236,7 +238,7 @@ export async function ensureSourceCaches(config) {
|
|
|
236
238
|
warn(`Warning: failed to refresh git mirror for "${entry.url}": ${err instanceof Error ? err.message : String(err)}`);
|
|
237
239
|
}
|
|
238
240
|
}
|
|
239
|
-
for (const entry of
|
|
241
|
+
for (const entry of entries) {
|
|
240
242
|
if (entry.type !== "website" || !entry.url || entry.enabled === false)
|
|
241
243
|
continue;
|
|
242
244
|
try {
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser + resolver for the optional `agent` config block (v1 spec §12).
|
|
3
|
+
*
|
|
4
|
+
* The on-disk shape is:
|
|
5
|
+
*
|
|
6
|
+
* ```jsonc
|
|
7
|
+
* {
|
|
8
|
+
* "agent": {
|
|
9
|
+
* "default": "opencode",
|
|
10
|
+
* "timeoutMs": 60000,
|
|
11
|
+
* "profiles": {
|
|
12
|
+
* "opencode": { "bin": "opencode", "args": ["--non-interactive"], ... }
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Unknown keys at any level under `agent` are warn-and-ignored — this is the
|
|
19
|
+
* v1 §9.2 contract. Missing `agent` block disables agent commands; callers
|
|
20
|
+
* should reach for {@link requireAgentConfig} to surface a stable
|
|
21
|
+
* `ConfigError` with a hint pointing at setup.
|
|
22
|
+
*
|
|
23
|
+
* No LLM SDK is imported here. The runtime path is shell-out only (see
|
|
24
|
+
* `./spawn.ts`).
|
|
25
|
+
*/
|
|
26
|
+
import { ConfigError } from "../../core/errors";
|
|
27
|
+
import { warn } from "../../core/warn";
|
|
28
|
+
import { BUILTIN_AGENT_PROFILE_NAMES, getBuiltinAgentProfile, listBuiltinAgentProfiles, } from "./profiles";
|
|
29
|
+
/** Keys recognised at the top level of an `agent` config block. */
|
|
30
|
+
const KNOWN_AGENT_KEYS = new Set(["default", "timeoutMs", "profiles"]);
|
|
31
|
+
/** Keys recognised on a profile entry. */
|
|
32
|
+
const KNOWN_PROFILE_KEYS = new Set(["bin", "args", "stdio", "env", "envPassthrough", "timeoutMs", "parseOutput"]);
|
|
33
|
+
/**
|
|
34
|
+
* Default hard timeout for an agent CLI. Spec §12.2 calls for a hard
|
|
35
|
+
* timeout; 60s matches the example value in `docs/configuration.md`.
|
|
36
|
+
*/
|
|
37
|
+
export const DEFAULT_AGENT_TIMEOUT_MS = 60_000;
|
|
38
|
+
/**
|
|
39
|
+
* Parse a raw value (typically `rawConfig.agent` from `JSON.parse`) into a
|
|
40
|
+
* normalised {@link AgentConfig}. Returns `undefined` when the value is not
|
|
41
|
+
* an object (i.e. the block is absent or malformed at the root level — for
|
|
42
|
+
* malformed roots we emit a warning).
|
|
43
|
+
*
|
|
44
|
+
* Unknown keys (top-level and per-profile) are warn-and-ignore. Type errors
|
|
45
|
+
* on individual fields are warn-and-ignore so a bad `timeoutMs` does not
|
|
46
|
+
* break the rest of the block.
|
|
47
|
+
*/
|
|
48
|
+
export function parseAgentConfig(value) {
|
|
49
|
+
if (value === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
52
|
+
warn('[akm] Ignoring "agent" config: expected an object.');
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const raw = value;
|
|
56
|
+
const out = {};
|
|
57
|
+
for (const key of Object.keys(raw)) {
|
|
58
|
+
if (!KNOWN_AGENT_KEYS.has(key)) {
|
|
59
|
+
warn(`[akm] Ignoring unknown agent config key: "${key}"`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if ("default" in raw) {
|
|
63
|
+
if (typeof raw.default === "string" && raw.default.trim()) {
|
|
64
|
+
out.default = raw.default.trim();
|
|
65
|
+
}
|
|
66
|
+
else if (raw.default !== undefined) {
|
|
67
|
+
warn("[akm] Ignoring agent.default: expected a non-empty string.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if ("timeoutMs" in raw) {
|
|
71
|
+
if (typeof raw.timeoutMs === "number" &&
|
|
72
|
+
Number.isFinite(raw.timeoutMs) &&
|
|
73
|
+
Number.isInteger(raw.timeoutMs) &&
|
|
74
|
+
raw.timeoutMs > 0) {
|
|
75
|
+
out.timeoutMs = raw.timeoutMs;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
warn("[akm] Ignoring agent.timeoutMs: expected a positive integer (milliseconds).");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if ("profiles" in raw) {
|
|
82
|
+
const profiles = parseAgentProfilesMap(raw.profiles);
|
|
83
|
+
if (profiles)
|
|
84
|
+
out.profiles = profiles;
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
function parseAgentProfilesMap(value) {
|
|
89
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
90
|
+
warn("[akm] Ignoring agent.profiles: expected an object.");
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const out = {};
|
|
94
|
+
for (const [name, raw] of Object.entries(value)) {
|
|
95
|
+
const parsed = parseAgentProfileConfig(name, raw);
|
|
96
|
+
if (parsed)
|
|
97
|
+
out[name] = parsed;
|
|
98
|
+
}
|
|
99
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
100
|
+
}
|
|
101
|
+
function parseAgentProfileConfig(name, value) {
|
|
102
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
103
|
+
warn(`[akm] Ignoring agent.profiles."${name}": expected an object.`);
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
const raw = value;
|
|
107
|
+
const out = {};
|
|
108
|
+
for (const key of Object.keys(raw)) {
|
|
109
|
+
if (!KNOWN_PROFILE_KEYS.has(key)) {
|
|
110
|
+
warn(`[akm] Ignoring unknown agent.profiles."${name}" key: "${key}"`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (typeof raw.bin === "string" && raw.bin.trim()) {
|
|
114
|
+
out.bin = raw.bin.trim();
|
|
115
|
+
}
|
|
116
|
+
else if (raw.bin !== undefined) {
|
|
117
|
+
warn(`[akm] Ignoring agent.profiles."${name}".bin: expected a non-empty string.`);
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(raw.args)) {
|
|
120
|
+
const args = raw.args.filter((a) => typeof a === "string");
|
|
121
|
+
if (args.length === raw.args.length) {
|
|
122
|
+
out.args = args;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
warn(`[akm] Ignoring non-string entries in agent.profiles."${name}".args.`);
|
|
126
|
+
if (args.length > 0)
|
|
127
|
+
out.args = args;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (raw.args !== undefined) {
|
|
131
|
+
warn(`[akm] Ignoring agent.profiles."${name}".args: expected an array of strings.`);
|
|
132
|
+
}
|
|
133
|
+
if (raw.stdio === "captured" || raw.stdio === "interactive") {
|
|
134
|
+
out.stdio = raw.stdio;
|
|
135
|
+
}
|
|
136
|
+
else if (raw.stdio !== undefined) {
|
|
137
|
+
warn(`[akm] Ignoring agent.profiles."${name}".stdio: expected "captured" or "interactive".`);
|
|
138
|
+
}
|
|
139
|
+
if (typeof raw.env === "object" && raw.env !== null && !Array.isArray(raw.env)) {
|
|
140
|
+
const env = {};
|
|
141
|
+
for (const [k, v] of Object.entries(raw.env)) {
|
|
142
|
+
if (typeof v === "string")
|
|
143
|
+
env[k] = v;
|
|
144
|
+
}
|
|
145
|
+
if (Object.keys(env).length > 0)
|
|
146
|
+
out.env = env;
|
|
147
|
+
}
|
|
148
|
+
else if (raw.env !== undefined) {
|
|
149
|
+
warn(`[akm] Ignoring agent.profiles."${name}".env: expected a string-valued object.`);
|
|
150
|
+
}
|
|
151
|
+
if (Array.isArray(raw.envPassthrough)) {
|
|
152
|
+
const list = raw.envPassthrough.filter((s) => typeof s === "string" && s.length > 0);
|
|
153
|
+
if (list.length > 0)
|
|
154
|
+
out.envPassthrough = list;
|
|
155
|
+
}
|
|
156
|
+
else if (raw.envPassthrough !== undefined) {
|
|
157
|
+
warn(`[akm] Ignoring agent.profiles."${name}".envPassthrough: expected an array of strings.`);
|
|
158
|
+
}
|
|
159
|
+
if (typeof raw.timeoutMs === "number" &&
|
|
160
|
+
Number.isFinite(raw.timeoutMs) &&
|
|
161
|
+
Number.isInteger(raw.timeoutMs) &&
|
|
162
|
+
raw.timeoutMs > 0) {
|
|
163
|
+
out.timeoutMs = raw.timeoutMs;
|
|
164
|
+
}
|
|
165
|
+
else if (raw.timeoutMs !== undefined) {
|
|
166
|
+
warn(`[akm] Ignoring agent.profiles."${name}".timeoutMs: expected a positive integer.`);
|
|
167
|
+
}
|
|
168
|
+
if (raw.parseOutput === "text" || raw.parseOutput === "json") {
|
|
169
|
+
out.parseOutput = raw.parseOutput;
|
|
170
|
+
}
|
|
171
|
+
else if (raw.parseOutput !== undefined) {
|
|
172
|
+
warn(`[akm] Ignoring agent.profiles."${name}".parseOutput: expected "text" or "json".`);
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Merge a user override (from `agent.profiles[<name>]`) on top of the
|
|
178
|
+
* built-in profile (if any) and return the resolved profile. If `name`
|
|
179
|
+
* matches no built-in and the user override has no `bin`, returns
|
|
180
|
+
* `undefined` — the profile is unusable.
|
|
181
|
+
*
|
|
182
|
+
* Used at the spawn site, never at config-load time. Keeping merge logic
|
|
183
|
+
* here means the parser stays a pure shape-checker.
|
|
184
|
+
*/
|
|
185
|
+
export function resolveAgentProfile(name, overrides) {
|
|
186
|
+
const builtin = getBuiltinAgentProfile(name);
|
|
187
|
+
if (!builtin && !overrides?.bin)
|
|
188
|
+
return undefined;
|
|
189
|
+
const base = builtin ??
|
|
190
|
+
{
|
|
191
|
+
name,
|
|
192
|
+
bin: overrides?.bin ?? name,
|
|
193
|
+
args: [],
|
|
194
|
+
stdio: "captured",
|
|
195
|
+
envPassthrough: [],
|
|
196
|
+
parseOutput: "text",
|
|
197
|
+
};
|
|
198
|
+
if (!overrides)
|
|
199
|
+
return base;
|
|
200
|
+
const merged = {
|
|
201
|
+
name,
|
|
202
|
+
bin: overrides.bin ?? base.bin,
|
|
203
|
+
args: overrides.args ?? base.args,
|
|
204
|
+
stdio: overrides.stdio ?? base.stdio,
|
|
205
|
+
env: overrides.env ?? base.env,
|
|
206
|
+
envPassthrough: overrides.envPassthrough
|
|
207
|
+
? mergePassthrough(base.envPassthrough, overrides.envPassthrough)
|
|
208
|
+
: base.envPassthrough,
|
|
209
|
+
timeoutMs: overrides.timeoutMs ?? base.timeoutMs,
|
|
210
|
+
parseOutput: overrides.parseOutput ?? base.parseOutput,
|
|
211
|
+
};
|
|
212
|
+
return merged;
|
|
213
|
+
}
|
|
214
|
+
function mergePassthrough(base, extra) {
|
|
215
|
+
const seen = new Set();
|
|
216
|
+
const out = [];
|
|
217
|
+
for (const k of [...base, ...extra]) {
|
|
218
|
+
if (!seen.has(k)) {
|
|
219
|
+
seen.add(k);
|
|
220
|
+
out.push(k);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Resolve the runnable profile for `name`, or `undefined` if none is
|
|
227
|
+
* available (no built-in and no user override with a `bin`).
|
|
228
|
+
*/
|
|
229
|
+
export function resolveProfileFromConfig(name, agent) {
|
|
230
|
+
return resolveAgentProfile(name, agent?.profiles?.[name]);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Return the names of every profile available in `agent` config (built-in
|
|
234
|
+
* names plus any user-defined ones). Sorted, deduplicated.
|
|
235
|
+
*/
|
|
236
|
+
export function listAgentProfileNames(agent) {
|
|
237
|
+
const seen = new Set(BUILTIN_AGENT_PROFILE_NAMES);
|
|
238
|
+
for (const name of Object.keys(agent?.profiles ?? {}))
|
|
239
|
+
seen.add(name);
|
|
240
|
+
return [...seen].sort();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Resolve the default profile name. Order: explicit `name` arg → config
|
|
244
|
+
* `agent.default` → undefined.
|
|
245
|
+
*/
|
|
246
|
+
export function resolveDefaultProfileName(agent, requested) {
|
|
247
|
+
if (requested?.trim())
|
|
248
|
+
return requested.trim();
|
|
249
|
+
if (agent?.default?.trim())
|
|
250
|
+
return agent.default.trim();
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Throw a {@link ConfigError} with a stable hint when the caller needs
|
|
255
|
+
* `agent` config but it is missing or unresolvable.
|
|
256
|
+
*
|
|
257
|
+
* Covers two cases per acceptance criteria:
|
|
258
|
+
*
|
|
259
|
+
* 1. The `agent` block is absent — agent commands are disabled.
|
|
260
|
+
* 2. The block exists but no usable profile (no `default`, no requested
|
|
261
|
+
* name, or the named profile cannot be resolved).
|
|
262
|
+
*
|
|
263
|
+
* Use as `const profile = requireAgentProfile(config.agent, requestedName)`.
|
|
264
|
+
*/
|
|
265
|
+
export function requireAgentProfile(agent, requested) {
|
|
266
|
+
if (!agent) {
|
|
267
|
+
throw new ConfigError("agent commands are disabled: no `agent` block in config.json.", "INVALID_CONFIG_FILE", 'Run `akm setup` to detect and configure an agent CLI, or add an `agent` block manually (see docs/configuration.md "agent.*").');
|
|
268
|
+
}
|
|
269
|
+
const name = resolveDefaultProfileName(agent, requested);
|
|
270
|
+
if (!name) {
|
|
271
|
+
throw new ConfigError("agent commands require a profile: pass --profile or set `agent.default` in config.json.", "INVALID_CONFIG_FILE", `Available profiles: ${listAgentProfileNames(agent).join(", ")}.`);
|
|
272
|
+
}
|
|
273
|
+
const profile = resolveProfileFromConfig(name, agent);
|
|
274
|
+
if (!profile) {
|
|
275
|
+
throw new ConfigError(`agent profile "${name}" is not built-in and has no \`bin\` override.`, "INVALID_CONFIG_FILE", `Define agent.profiles."${name}".bin in config.json, or pick one of: ${listAgentProfileNames(agent).join(", ")}.`);
|
|
276
|
+
}
|
|
277
|
+
return profile;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Convenience: list every fully-resolved profile (built-ins merged with
|
|
281
|
+
* any user overrides). Used by setup detection to enumerate candidates.
|
|
282
|
+
*/
|
|
283
|
+
export function listResolvedAgentProfiles(agent) {
|
|
284
|
+
const resolved = [];
|
|
285
|
+
const builtins = listBuiltinAgentProfiles();
|
|
286
|
+
for (const name of listAgentProfileNames(agent)) {
|
|
287
|
+
const profile = resolveProfileFromConfig(name, agent) ?? builtins[name];
|
|
288
|
+
if (profile)
|
|
289
|
+
resolved.push(profile);
|
|
290
|
+
}
|
|
291
|
+
return resolved;
|
|
292
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup-time agent CLI detection (v1 spec §12.3).
|
|
3
|
+
*
|
|
4
|
+
* Probes every known/configured agent profile by checking `which <bin>` on
|
|
5
|
+
* PATH. We do **not** call `<bin> --version` — that would execute the
|
|
6
|
+
* binary at setup time, which is unnecessarily side-effectful for
|
|
7
|
+
* detection. The presence of the binary on PATH is sufficient signal; the
|
|
8
|
+
* spawn wrapper handles failures at run-time.
|
|
9
|
+
*
|
|
10
|
+
* Tests inject a fake `whichFn` so detection branches can be exercised
|
|
11
|
+
* without poking at the real PATH.
|
|
12
|
+
*/
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { listResolvedAgentProfiles } from "./config";
|
|
16
|
+
/**
|
|
17
|
+
* Default PATH lookup. Walks `process.env.PATH` and returns the first
|
|
18
|
+
* existing executable file. Returns `undefined` when the bin is not on
|
|
19
|
+
* PATH or the env is empty.
|
|
20
|
+
*
|
|
21
|
+
* `process.env.PATH` is split on the platform-correct delimiter; on
|
|
22
|
+
* Windows the binary may have an executable extension, but for v1 we
|
|
23
|
+
* keep this Unix-flavoured (Bun's primary target) and look for an exact
|
|
24
|
+
* match.
|
|
25
|
+
*/
|
|
26
|
+
export function defaultWhich(bin, envSource = process.env) {
|
|
27
|
+
if (!bin || bin.includes("/") || bin.includes("\\")) {
|
|
28
|
+
// Absolute / relative paths: caller already specified location.
|
|
29
|
+
try {
|
|
30
|
+
return fs.statSync(bin).isFile() ? bin : undefined;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const pathVar = envSource.PATH ?? envSource.Path ?? envSource.path ?? "";
|
|
37
|
+
if (!pathVar)
|
|
38
|
+
return undefined;
|
|
39
|
+
const sep = pathVar.includes(";") && !pathVar.includes(":") ? ";" : path.delimiter;
|
|
40
|
+
for (const dir of pathVar.split(sep)) {
|
|
41
|
+
if (!dir)
|
|
42
|
+
continue;
|
|
43
|
+
const candidate = path.join(dir, bin);
|
|
44
|
+
try {
|
|
45
|
+
const st = fs.statSync(candidate);
|
|
46
|
+
if (st.isFile())
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* keep walking */
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Probe every resolvable agent profile (built-ins plus user overrides)
|
|
57
|
+
* for an installed CLI.
|
|
58
|
+
*
|
|
59
|
+
* @param agent Optional `agent` config block. When omitted we probe the
|
|
60
|
+
* built-ins.
|
|
61
|
+
* @param whichFn Binary lookup. Tests should inject a stub.
|
|
62
|
+
*/
|
|
63
|
+
export function detectAgentCliProfiles(agent, whichFn = defaultWhich) {
|
|
64
|
+
const profiles = listResolvedAgentProfiles(agent);
|
|
65
|
+
return profiles.map((profile) => probeProfile(profile, whichFn));
|
|
66
|
+
}
|
|
67
|
+
function probeProfile(profile, whichFn) {
|
|
68
|
+
const resolved = whichFn(profile.bin);
|
|
69
|
+
return {
|
|
70
|
+
name: profile.name,
|
|
71
|
+
bin: profile.bin,
|
|
72
|
+
available: Boolean(resolved),
|
|
73
|
+
...(resolved ? { resolvedPath: resolved } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Pick the default profile to persist after a setup-time detection run.
|
|
78
|
+
*
|
|
79
|
+
* Strategy:
|
|
80
|
+
* 1. If the user already set `agent.default` and that profile is
|
|
81
|
+
* available, keep it (round-trip stability).
|
|
82
|
+
* 2. Otherwise, pick the first available result in detection order.
|
|
83
|
+
* 3. If nothing is available, return `undefined` and the caller skips
|
|
84
|
+
* writing `agent.default`.
|
|
85
|
+
*/
|
|
86
|
+
export function pickDefaultAgentProfile(results, existingDefault) {
|
|
87
|
+
if (existingDefault) {
|
|
88
|
+
const match = results.find((r) => r.name === existingDefault && r.available);
|
|
89
|
+
if (match)
|
|
90
|
+
return match.name;
|
|
91
|
+
}
|
|
92
|
+
const first = results.find((r) => r.available);
|
|
93
|
+
return first?.name;
|
|
94
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal entry point for the `agent` integration. CLI-only project — no
|
|
3
|
+
* public exports map. Other akm modules import from this barrel for the
|
|
4
|
+
* sake of grouping imports.
|
|
5
|
+
*
|
|
6
|
+
* Surface:
|
|
7
|
+
* • Types: AgentProfile, AgentConfig, AgentRunResult, AgentFailureReason.
|
|
8
|
+
* • Profiles: getBuiltinAgentProfile, listBuiltinAgentProfiles, BUILTIN_AGENT_PROFILE_NAMES.
|
|
9
|
+
* • Config: parseAgentConfig, resolveProfileFromConfig, requireAgentProfile, listResolvedAgentProfiles, listAgentProfileNames.
|
|
10
|
+
* • Spawn: runAgent.
|
|
11
|
+
* • Detection: detectAgentCliProfiles, pickDefaultAgentProfile, defaultWhich.
|
|
12
|
+
*/
|
|
13
|
+
export { DEFAULT_AGENT_TIMEOUT_MS, listAgentProfileNames, listResolvedAgentProfiles, parseAgentConfig, requireAgentProfile, resolveAgentProfile, resolveDefaultProfileName, resolveProfileFromConfig, } from "./config";
|
|
14
|
+
export { defaultWhich, detectAgentCliProfiles, pickDefaultAgentProfile } from "./detect";
|
|
15
|
+
export { BUILTIN_AGENT_PROFILE_NAMES, getBuiltinAgentProfile, listBuiltinAgentProfiles, } from "./profiles";
|
|
16
|
+
export { buildProposePrompt, buildReflectPrompt, parseAgentProposalPayload, stripJsonFences } from "./prompts";
|
|
17
|
+
export { runAgent } from "./spawn";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const COMMON_PASSTHROUGH = ["HOME", "PATH", "USER", "LANG", "LC_ALL", "TERM", "TMPDIR"];
|
|
2
|
+
/**
|
|
3
|
+
* Built-in profiles for the five agent CLIs the v1 spec calls out
|
|
4
|
+
* explicitly. The fields here are conservative defaults — every value is
|
|
5
|
+
* overridable from user config.
|
|
6
|
+
*/
|
|
7
|
+
const BUILTINS = {
|
|
8
|
+
opencode: {
|
|
9
|
+
name: "opencode",
|
|
10
|
+
bin: "opencode",
|
|
11
|
+
args: ["run"],
|
|
12
|
+
stdio: "interactive",
|
|
13
|
+
envPassthrough: [...COMMON_PASSTHROUGH, "OPENCODE_API_KEY", "OPENCODE_CONFIG"],
|
|
14
|
+
parseOutput: "text",
|
|
15
|
+
},
|
|
16
|
+
claude: {
|
|
17
|
+
name: "claude",
|
|
18
|
+
bin: "claude",
|
|
19
|
+
args: [],
|
|
20
|
+
stdio: "interactive",
|
|
21
|
+
envPassthrough: [...COMMON_PASSTHROUGH, "ANTHROPIC_API_KEY", "CLAUDE_CONFIG"],
|
|
22
|
+
parseOutput: "text",
|
|
23
|
+
},
|
|
24
|
+
codex: {
|
|
25
|
+
name: "codex",
|
|
26
|
+
bin: "codex",
|
|
27
|
+
args: [],
|
|
28
|
+
stdio: "interactive",
|
|
29
|
+
envPassthrough: [...COMMON_PASSTHROUGH, "OPENAI_API_KEY", "CODEX_CONFIG"],
|
|
30
|
+
parseOutput: "text",
|
|
31
|
+
},
|
|
32
|
+
gemini: {
|
|
33
|
+
name: "gemini",
|
|
34
|
+
bin: "gemini",
|
|
35
|
+
args: [],
|
|
36
|
+
stdio: "interactive",
|
|
37
|
+
envPassthrough: [...COMMON_PASSTHROUGH, "GEMINI_API_KEY", "GOOGLE_API_KEY"],
|
|
38
|
+
parseOutput: "text",
|
|
39
|
+
},
|
|
40
|
+
aider: {
|
|
41
|
+
name: "aider",
|
|
42
|
+
bin: "aider",
|
|
43
|
+
args: ["--no-auto-commits"],
|
|
44
|
+
stdio: "interactive",
|
|
45
|
+
envPassthrough: [...COMMON_PASSTHROUGH, "OPENAI_API_KEY", "ANTHROPIC_API_KEY"],
|
|
46
|
+
parseOutput: "text",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
/** Names of every built-in profile. Stable, sorted. */
|
|
50
|
+
export const BUILTIN_AGENT_PROFILE_NAMES = Object.freeze(Object.keys(BUILTINS).sort());
|
|
51
|
+
/** Returns the built-in profile by name, or `undefined` if not built-in. */
|
|
52
|
+
export function getBuiltinAgentProfile(name) {
|
|
53
|
+
return BUILTINS[name];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Return a deep copy of every built-in profile keyed by name. Callers
|
|
57
|
+
* should not assume reference equality with subsequent calls.
|
|
58
|
+
*/
|
|
59
|
+
export function listBuiltinAgentProfiles() {
|
|
60
|
+
const out = {};
|
|
61
|
+
for (const [name, profile] of Object.entries(BUILTINS)) {
|
|
62
|
+
out[name] = { ...profile };
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|