akm-cli 0.6.0 → 0.7.0-rc1
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} +672 -29
- package/dist/{commands → src/commands}/config-cli.js +5 -4
- package/dist/src/commands/distill.js +283 -0
- package/dist/src/commands/events.js +108 -0
- package/dist/src/commands/history.js +120 -0
- package/dist/{commands → src/commands}/installed-stashes.js +28 -2
- 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 +2 -1
- package/dist/{commands → src/commands}/remember.js +12 -0
- package/dist/{commands → src/commands}/search.js +74 -1
- package/dist/{commands → src/commands}/self-update.js +4 -3
- package/dist/{commands → src/commands}/show.js +67 -2
- 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 +175 -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 +119 -27
- 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 +73 -6
- package/dist/src/indexer/memory-inference.js +263 -0
- package/dist/{indexer → src/indexer}/metadata.js +114 -11
- 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 +221 -0
- package/dist/{integrations → src/integrations}/lockfile.js +0 -26
- package/dist/{llm → src/llm}/client.js +33 -2
- 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}/renderers.js +60 -1
- package/dist/src/output/shapes.js +516 -0
- package/dist/{output → src/output}/text.js +447 -4
- package/dist/{registry → src/registry}/build-index.js +14 -4
- package/dist/{registry → src/registry}/factory.js +0 -8
- package/dist/{registry → src/registry}/providers/static-index.js +3 -2
- package/dist/{registry → src/registry}/resolve.js +68 -2
- package/dist/{setup → src/setup}/setup.js +43 -5
- package/dist/{sources → src/sources}/providers/git.js +7 -15
- package/dist/{wiki → src/wiki}/wiki.js +9 -11
- 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 +995 -0
- package/dist/tests/bench/cleanup-sigint.test.js +83 -0
- package/dist/tests/bench/cleanup.js +203 -0
- package/dist/tests/bench/cleanup.test.js +166 -0
- package/dist/tests/bench/cli.js +683 -0
- package/dist/tests/bench/cli.test.js +177 -0
- package/dist/tests/bench/compare.test.js +556 -0
- package/dist/tests/bench/corpus.js +314 -0
- package/dist/tests/bench/corpus.test.js +258 -0
- package/dist/tests/bench/driver.js +346 -0
- package/dist/tests/bench/driver.test.js +443 -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 +580 -0
- package/dist/tests/bench/evolve.test.js +616 -0
- package/dist/tests/bench/failure-modes.test.js +300 -0
- package/dist/tests/bench/feedback-integrity.test.js +456 -0
- package/dist/tests/bench/leakage.test.js +125 -0
- package/dist/tests/bench/learning-curve.test.js +133 -0
- package/dist/tests/bench/metrics.js +2319 -0
- package/dist/tests/bench/metrics.test.js +1144 -0
- package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
- package/dist/tests/bench/report.js +1821 -0
- package/dist/tests/bench/report.test.js +989 -0
- package/dist/tests/bench/runner.js +536 -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 +41 -0
- package/dist/tests/bench/trajectory.js +116 -0
- package/dist/tests/bench/trajectory.test.js +127 -0
- package/dist/tests/bench/verifier.js +109 -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 +358 -0
- package/dist/tests/bench/workflow-spec.test.js +363 -0
- package/dist/tests/bench/workflow-trace.js +438 -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 +203 -0
- package/dist/tests/commands/events.test.js +370 -0
- package/dist/tests/commands/history.test.js +223 -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 +544 -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 +1398 -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 +88 -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 +559 -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 +217 -0
- package/dist/tests/output-shapes-unit.test.js +476 -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 +378 -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 +204 -0
- package/dist/tests/registry-resolve.test.js +126 -0
- package/dist/tests/registry-search.test.js +723 -0
- package/dist/tests/remember-frontmatter.test.js +380 -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 +268 -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 +221 -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 +377 -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/{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-add.js +0 -0
- /package/dist/{commands → src/commands}/source-clone.js +0 -0
- /package/dist/{commands → src/commands}/source-manage.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}/matchers.js +0 -0
- /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
- /package/dist/{indexer → src/indexer}/search-source.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/{integrations → src/integrations}/github.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/remote.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}/cli-hints.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}/runs.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();
|
|
@@ -255,10 +340,9 @@ const WIKI_INFRA_FILES = new Set(["schema.md", "index.md", "log.md"]);
|
|
|
255
340
|
* Apply wiki-specific index exclusions while leaving all other stash files
|
|
256
341
|
* untouched.
|
|
257
342
|
*
|
|
258
|
-
* - In a normal stash, excludes `
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
* root-level infrastructure files.
|
|
343
|
+
* - In a normal stash, excludes wiki-root `schema.md`, `index.md`, `log.md`.
|
|
344
|
+
* - In a wiki-root stash source (`wikiName`), excludes those same root-level
|
|
345
|
+
* infrastructure files.
|
|
262
346
|
*/
|
|
263
347
|
export function shouldIndexStashFile(stashRoot, file, options) {
|
|
264
348
|
const relPath = path.relative(stashRoot, file);
|
|
@@ -268,8 +352,6 @@ export function shouldIndexStashFile(stashRoot, file, options) {
|
|
|
268
352
|
if (segments.length === 0)
|
|
269
353
|
return true;
|
|
270
354
|
if (options?.treatStashRootAsWikiRoot) {
|
|
271
|
-
if (segments[0] === "raw")
|
|
272
|
-
return false;
|
|
273
355
|
return !(segments.length === 1 && WIKI_INFRA_FILES.has(segments[0]));
|
|
274
356
|
}
|
|
275
357
|
const wikisIdx = segments.indexOf("wikis");
|
|
@@ -278,8 +360,6 @@ export function shouldIndexStashFile(stashRoot, file, options) {
|
|
|
278
360
|
const wikiRelativeSegments = segments.slice(wikisIdx + 2);
|
|
279
361
|
if (wikiRelativeSegments.length === 0)
|
|
280
362
|
return true;
|
|
281
|
-
if (wikiRelativeSegments[0] === "raw")
|
|
282
|
-
return false;
|
|
283
363
|
return !(wikiRelativeSegments.length === 1 && WIKI_INFRA_FILES.has(wikiRelativeSegments[0]));
|
|
284
364
|
}
|
|
285
365
|
/**
|
|
@@ -405,6 +485,8 @@ export async function generateMetadata(dirPath, assetType, files, typeRoot = dir
|
|
|
405
485
|
entry.parameters = fmParams;
|
|
406
486
|
// Pass wiki-pattern frontmatter through onto the entry
|
|
407
487
|
applyWikiFrontmatter(entry, parsed.data);
|
|
488
|
+
// Pass canonical scope_* frontmatter through onto the entry
|
|
489
|
+
applyScopeFrontmatter(entry, parsed.data);
|
|
408
490
|
// Extract parameters from template placeholders ($1, $ARGUMENTS, {{named}})
|
|
409
491
|
if (entry.type === "command") {
|
|
410
492
|
const cmdParams = extractCommandParameters(parsed.content);
|
|
@@ -521,6 +603,8 @@ export async function generateMetadataFlat(stashRoot, files) {
|
|
|
521
603
|
entry.parameters = fmParams;
|
|
522
604
|
// Pass wiki-pattern frontmatter through onto the entry
|
|
523
605
|
applyWikiFrontmatter(entry, parsed.data);
|
|
606
|
+
// Pass canonical scope_* frontmatter through onto the entry
|
|
607
|
+
applyScopeFrontmatter(entry, parsed.data);
|
|
524
608
|
// Extract parameters from template placeholders ($1, $ARGUMENTS, {{named}})
|
|
525
609
|
if (entry.type === "command") {
|
|
526
610
|
const cmdParams = extractCommandParameters(parsed.content);
|
|
@@ -573,9 +657,28 @@ function buildMetadataSkipWarning(filePath, assetType, error) {
|
|
|
573
657
|
const warning = assetType === "workflow"
|
|
574
658
|
? `Skipped workflow ${filePath}:\n${detail}`
|
|
575
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
|
+
}
|
|
576
670
|
warn(warning);
|
|
577
671
|
return warning;
|
|
578
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
|
+
}
|
|
579
682
|
function normalizeTerms(values) {
|
|
580
683
|
const normalized = new Set();
|
|
581
684
|
for (const value of values) {
|
|
@@ -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: [],
|
|
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
|
+
}
|