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
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `akm proposal {list,show,accept,reject,diff}` — review surface for the
|
|
3
|
+
* proposal substrate (#225).
|
|
4
|
+
*
|
|
5
|
+
* Each function returns a plain JSON envelope; the CLI dispatcher in
|
|
6
|
+
* `src/cli.ts` flows the result through the standard
|
|
7
|
+
* `shapeForCommand` + `formatPlain` pipeline. There is no `JSON.stringify`
|
|
8
|
+
* fallback in the output layer — every shape is registered explicitly in
|
|
9
|
+
* `src/output/shapes.ts` and `src/output/text.ts`.
|
|
10
|
+
*/
|
|
11
|
+
import { resolveStashDir } from "../core/common";
|
|
12
|
+
import { loadConfig } from "../core/config";
|
|
13
|
+
import { UsageError } from "../core/errors";
|
|
14
|
+
import { appendEvent } from "../core/events";
|
|
15
|
+
import { archiveProposal, createProposal, diffProposal, getProposal, listProposals, promoteProposal, validateProposal, } from "../core/proposals";
|
|
16
|
+
// ── Shared helpers ──────────────────────────────────────────────────────────
|
|
17
|
+
function resolveStash(stashDir) {
|
|
18
|
+
if (stashDir)
|
|
19
|
+
return stashDir;
|
|
20
|
+
return resolveStashDir();
|
|
21
|
+
}
|
|
22
|
+
export function akmProposalList(options = {}) {
|
|
23
|
+
const stash = resolveStash(options.stashDir);
|
|
24
|
+
// `--status accepted|rejected` implies archive-inclusion since the live
|
|
25
|
+
// queue only ever contains pending entries.
|
|
26
|
+
const includeArchive = options.includeArchive === true || options.status === "accepted" || options.status === "rejected";
|
|
27
|
+
const proposals = listProposals(stash, {
|
|
28
|
+
includeArchive,
|
|
29
|
+
status: options.status,
|
|
30
|
+
ref: options.ref,
|
|
31
|
+
});
|
|
32
|
+
return { schemaVersion: 1, totalCount: proposals.length, proposals };
|
|
33
|
+
}
|
|
34
|
+
export function akmProposalShow(options) {
|
|
35
|
+
const stash = resolveStash(options.stashDir);
|
|
36
|
+
const proposal = getProposal(stash, options.id);
|
|
37
|
+
return {
|
|
38
|
+
schemaVersion: 1,
|
|
39
|
+
proposal,
|
|
40
|
+
validation: validateProposal(proposal),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export async function akmProposalAccept(options) {
|
|
44
|
+
const stash = resolveStash(options.stashDir);
|
|
45
|
+
const config = options.config ?? loadConfig();
|
|
46
|
+
const result = await promoteProposal(stash, config, options.id, { target: options.target }, options.ctx);
|
|
47
|
+
// Emit `promoted` to the events stream so observers (audit, dashboards,
|
|
48
|
+
// sync) see the accept happen. Only emit on the happy path — promotion
|
|
49
|
+
// throws on validation failure, so reaching this point means the asset
|
|
50
|
+
// is committed.
|
|
51
|
+
appendEvent({
|
|
52
|
+
eventType: "promoted",
|
|
53
|
+
ref: result.ref,
|
|
54
|
+
metadata: {
|
|
55
|
+
proposalId: result.proposal.id,
|
|
56
|
+
source: result.proposal.source,
|
|
57
|
+
...(result.proposal.sourceRun !== undefined ? { sourceRun: result.proposal.sourceRun } : {}),
|
|
58
|
+
assetPath: result.assetPath,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
schemaVersion: 1,
|
|
63
|
+
ok: true,
|
|
64
|
+
id: result.proposal.id,
|
|
65
|
+
ref: result.ref,
|
|
66
|
+
assetPath: result.assetPath,
|
|
67
|
+
proposal: result.proposal,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function akmProposalReject(options) {
|
|
71
|
+
const stash = resolveStash(options.stashDir);
|
|
72
|
+
const existing = getProposal(stash, options.id);
|
|
73
|
+
if (existing.status !== "pending") {
|
|
74
|
+
throw new UsageError(`Proposal ${options.id} is not pending (current status: ${existing.status}). Only pending proposals can be rejected.`, "INVALID_FLAG_VALUE");
|
|
75
|
+
}
|
|
76
|
+
const updated = archiveProposal(stash, options.id, "rejected", options.reason, options.ctx);
|
|
77
|
+
appendEvent({
|
|
78
|
+
eventType: "rejected",
|
|
79
|
+
ref: updated.ref,
|
|
80
|
+
metadata: {
|
|
81
|
+
proposalId: updated.id,
|
|
82
|
+
source: updated.source,
|
|
83
|
+
...(updated.sourceRun !== undefined ? { sourceRun: updated.sourceRun } : {}),
|
|
84
|
+
...(options.reason !== undefined ? { reason: options.reason } : {}),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
schemaVersion: 1,
|
|
89
|
+
ok: true,
|
|
90
|
+
id: updated.id,
|
|
91
|
+
ref: updated.ref,
|
|
92
|
+
...(options.reason !== undefined ? { reason: options.reason } : {}),
|
|
93
|
+
proposal: updated,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function akmProposalDiff(options) {
|
|
97
|
+
const stash = resolveStash(options.stashDir);
|
|
98
|
+
const config = options.config ?? loadConfig();
|
|
99
|
+
const proposal = getProposal(stash, options.id);
|
|
100
|
+
const diff = diffProposal(stash, config, options.id, { target: options.target });
|
|
101
|
+
return {
|
|
102
|
+
schemaVersion: 1,
|
|
103
|
+
id: proposal.id,
|
|
104
|
+
ref: proposal.ref,
|
|
105
|
+
isNew: diff.isNew,
|
|
106
|
+
unified: diff.unified,
|
|
107
|
+
...(diff.targetPath ? { targetPath: diff.targetPath } : {}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export function akmProposalCreate(options) {
|
|
111
|
+
const stash = resolveStash(options.stashDir);
|
|
112
|
+
const proposal = createProposal(stash, {
|
|
113
|
+
ref: options.ref,
|
|
114
|
+
source: options.source,
|
|
115
|
+
...(options.sourceRun !== undefined ? { sourceRun: options.sourceRun } : {}),
|
|
116
|
+
payload: options.payload,
|
|
117
|
+
}, options.ctx);
|
|
118
|
+
return { schemaVersion: 1, ok: true, proposal };
|
|
119
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `akm propose <type> <name> --task ...` — proposal-producing agent
|
|
3
|
+
* command (#226).
|
|
4
|
+
*
|
|
5
|
+
* Mirrors {@link akmReflect} but for fresh authoring. The agent receives a
|
|
6
|
+
* task description plus per-asset-type schema hints and is asked to author
|
|
7
|
+
* a brand-new asset payload. The output lands ONLY in the proposal queue.
|
|
8
|
+
*
|
|
9
|
+
* Failures use the same {@link AgentFailureReason} discriminants as
|
|
10
|
+
* `akm reflect`. `propose_invoked` is emitted at command entry.
|
|
11
|
+
*/
|
|
12
|
+
import { parseAssetRef } from "../core/asset-ref";
|
|
13
|
+
import { TYPE_DIRS } from "../core/asset-spec";
|
|
14
|
+
import { resolveStashDir } from "../core/common";
|
|
15
|
+
import { loadConfig } from "../core/config";
|
|
16
|
+
import { ConfigError, UsageError } from "../core/errors";
|
|
17
|
+
import { appendEvent } from "../core/events";
|
|
18
|
+
import { createProposal } from "../core/proposals";
|
|
19
|
+
import { parseAgentConfig, requireAgentProfile, runAgent, } from "../integrations/agent";
|
|
20
|
+
import { buildProposePrompt, parseAgentProposalPayload } from "../integrations/agent/prompts";
|
|
21
|
+
function loadAgentConfigFromDisk() {
|
|
22
|
+
const config = loadConfig();
|
|
23
|
+
return parseAgentConfig(config.agent);
|
|
24
|
+
}
|
|
25
|
+
function resolveProfile(options) {
|
|
26
|
+
if (options.agentProfile)
|
|
27
|
+
return options.agentProfile;
|
|
28
|
+
const agent = options.agentConfig ?? loadAgentConfigFromDisk();
|
|
29
|
+
return requireAgentProfile(agent, options.profile);
|
|
30
|
+
}
|
|
31
|
+
function failureEnvelope(result, type, name, fallbackReason = "non_zero_exit") {
|
|
32
|
+
const reason = result.reason ?? fallbackReason;
|
|
33
|
+
return {
|
|
34
|
+
schemaVersion: 1,
|
|
35
|
+
ok: false,
|
|
36
|
+
reason,
|
|
37
|
+
error: result.error ?? `agent failure (${reason})`,
|
|
38
|
+
type,
|
|
39
|
+
name,
|
|
40
|
+
exitCode: result.exitCode,
|
|
41
|
+
...(result.stdout ? { stdout: result.stdout } : {}),
|
|
42
|
+
...(result.stderr ? { stderr: result.stderr } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export async function akmPropose(options) {
|
|
46
|
+
if (!options.type?.trim()) {
|
|
47
|
+
throw new UsageError("propose: <type> is required.", "MISSING_REQUIRED_ARGUMENT");
|
|
48
|
+
}
|
|
49
|
+
if (!options.name?.trim()) {
|
|
50
|
+
throw new UsageError("propose: <name> is required.", "MISSING_REQUIRED_ARGUMENT");
|
|
51
|
+
}
|
|
52
|
+
if (!options.task?.trim()) {
|
|
53
|
+
throw new UsageError("propose: --task is required.", "MISSING_REQUIRED_ARGUMENT");
|
|
54
|
+
}
|
|
55
|
+
if (!TYPE_DIRS[options.type]) {
|
|
56
|
+
throw new UsageError(`propose: unknown asset type "${options.type}". Known types: ${Object.keys(TYPE_DIRS).sort().join(", ")}.`, "INVALID_FLAG_VALUE");
|
|
57
|
+
}
|
|
58
|
+
const stash = options.stashDir ?? resolveStashDir();
|
|
59
|
+
// 1. Always emit `propose_invoked` at entry so observers see the attempt.
|
|
60
|
+
appendEvent({
|
|
61
|
+
eventType: "propose_invoked",
|
|
62
|
+
ref: `${options.type}:${options.name}`,
|
|
63
|
+
metadata: {
|
|
64
|
+
type: options.type,
|
|
65
|
+
name: options.name,
|
|
66
|
+
task: options.task,
|
|
67
|
+
...(options.profile ? { profile: options.profile } : {}),
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
// 2. Resolve profile.
|
|
71
|
+
let profile;
|
|
72
|
+
try {
|
|
73
|
+
profile = resolveProfile(options);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (err instanceof ConfigError || err instanceof UsageError)
|
|
77
|
+
throw err;
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
// 3. Build prompt.
|
|
81
|
+
const prompt = buildProposePrompt({
|
|
82
|
+
type: options.type,
|
|
83
|
+
name: options.name,
|
|
84
|
+
task: options.task,
|
|
85
|
+
});
|
|
86
|
+
// 4. Spawn the agent.
|
|
87
|
+
const runOptions = {
|
|
88
|
+
stdio: "captured",
|
|
89
|
+
parseOutput: "text",
|
|
90
|
+
...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
|
|
91
|
+
...(options.runAgentOptions ?? {}),
|
|
92
|
+
};
|
|
93
|
+
const result = await runAgent(profile, prompt, runOptions);
|
|
94
|
+
if (!result.ok) {
|
|
95
|
+
return failureEnvelope(result, options.type, options.name);
|
|
96
|
+
}
|
|
97
|
+
// 5. Parse the structured response.
|
|
98
|
+
let payload;
|
|
99
|
+
try {
|
|
100
|
+
payload = parseAgentProposalPayload(result.stdout);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
return {
|
|
104
|
+
schemaVersion: 1,
|
|
105
|
+
ok: false,
|
|
106
|
+
reason: "parse_error",
|
|
107
|
+
error: err instanceof Error ? err.message : String(err),
|
|
108
|
+
type: options.type,
|
|
109
|
+
name: options.name,
|
|
110
|
+
exitCode: result.exitCode,
|
|
111
|
+
stdout: result.stdout,
|
|
112
|
+
...(result.stderr ? { stderr: result.stderr } : {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// 6. Insert the proposal. Note: we allow the agent's `ref` to normalise the
|
|
116
|
+
// asset name (e.g. path-cleanup), but only after validating that the ref is
|
|
117
|
+
// well-formed and the type still matches the requested type.
|
|
118
|
+
const expectedRef = `${options.type}:${options.name}`;
|
|
119
|
+
let ref = expectedRef;
|
|
120
|
+
if (payload.ref) {
|
|
121
|
+
let parsedRef;
|
|
122
|
+
try {
|
|
123
|
+
parsedRef = parseAssetRef(payload.ref);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
return {
|
|
127
|
+
schemaVersion: 1,
|
|
128
|
+
ok: false,
|
|
129
|
+
reason: "parse_error",
|
|
130
|
+
error: err instanceof Error ? err.message : String(err),
|
|
131
|
+
type: options.type,
|
|
132
|
+
name: options.name,
|
|
133
|
+
exitCode: result.exitCode,
|
|
134
|
+
stdout: result.stdout,
|
|
135
|
+
...(result.stderr ? { stderr: result.stderr } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (parsedRef.type !== options.type) {
|
|
139
|
+
return {
|
|
140
|
+
schemaVersion: 1,
|
|
141
|
+
ok: false,
|
|
142
|
+
reason: "parse_error",
|
|
143
|
+
error: `Agent returned ref type ${parsedRef.type} but expected ${options.type}`,
|
|
144
|
+
type: options.type,
|
|
145
|
+
name: options.name,
|
|
146
|
+
exitCode: result.exitCode,
|
|
147
|
+
stdout: result.stdout,
|
|
148
|
+
...(result.stderr ? { stderr: result.stderr } : {}),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
ref = `${parsedRef.type}:${parsedRef.name}`;
|
|
152
|
+
}
|
|
153
|
+
const createInput = {
|
|
154
|
+
ref,
|
|
155
|
+
source: "propose",
|
|
156
|
+
sourceRun: `propose-${Date.now()}`,
|
|
157
|
+
payload: {
|
|
158
|
+
content: payload.content,
|
|
159
|
+
...(payload.frontmatter ? { frontmatter: payload.frontmatter } : {}),
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
const proposal = createProposal(stash, createInput, options.ctx);
|
|
163
|
+
return {
|
|
164
|
+
schemaVersion: 1,
|
|
165
|
+
ok: true,
|
|
166
|
+
proposal,
|
|
167
|
+
ref: proposal.ref,
|
|
168
|
+
agentProfile: profile.name,
|
|
169
|
+
durationMs: result.durationMs,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `akm reflect [ref]` — proposal-producing agent command (#226).
|
|
3
|
+
*
|
|
4
|
+
* Pipeline:
|
|
5
|
+
*
|
|
6
|
+
* 1. Emit `reflect_invoked` event at command entry (always, even on failure).
|
|
7
|
+
* 2. If `ref` is provided, look the asset up via the FTS index and read its
|
|
8
|
+
* content. Pull recent feedback (`feedback` events for that ref) and
|
|
9
|
+
* lesson-lint findings to surface as schema hints.
|
|
10
|
+
* 3. Build the prompt via {@link buildReflectPrompt}.
|
|
11
|
+
* 4. Spawn the configured agent profile via {@link runAgent}.
|
|
12
|
+
* 5. Parse the agent's stdout into a {@link AgentProposalPayload}.
|
|
13
|
+
* 6. Insert into the proposal queue via {@link createProposal} with
|
|
14
|
+
* `source: "reflect"`.
|
|
15
|
+
*
|
|
16
|
+
* Failures are surfaced as structured envelopes carrying an
|
|
17
|
+
* {@link AgentFailureReason} discriminant. Reflect NEVER calls
|
|
18
|
+
* `writeAssetToSource` directly — the proposal queue is the only path to
|
|
19
|
+
* a committed asset, and the `accept` flow is the bridge.
|
|
20
|
+
*/
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import { parseAssetRef } from "../core/asset-ref";
|
|
23
|
+
import { resolveStashDir } from "../core/common";
|
|
24
|
+
import { loadConfig } from "../core/config";
|
|
25
|
+
import { ConfigError, UsageError } from "../core/errors";
|
|
26
|
+
import { appendEvent, readEvents } from "../core/events";
|
|
27
|
+
import { lintLessonContent } from "../core/lesson-lint";
|
|
28
|
+
import { createProposal } from "../core/proposals";
|
|
29
|
+
import { lookup } from "../indexer/indexer";
|
|
30
|
+
import { parseAgentConfig, requireAgentProfile, runAgent, } from "../integrations/agent";
|
|
31
|
+
import { buildReflectPrompt, parseAgentProposalPayload } from "../integrations/agent/prompts";
|
|
32
|
+
const MAX_FEEDBACK_LINES = 10;
|
|
33
|
+
const MAX_GLOBAL_FEEDBACK_LINES = 20;
|
|
34
|
+
/**
|
|
35
|
+
* Pull recent `feedback` events from events.jsonl. When `ref` is present we
|
|
36
|
+
* scope to that asset; otherwise we surface the most recent feedback across
|
|
37
|
+
* all assets so `akm reflect` can operate in a general "review recent
|
|
38
|
+
* signals" mode. Best-effort — a missing or empty events stream returns `[]`.
|
|
39
|
+
*/
|
|
40
|
+
function readRecentFeedback(ref) {
|
|
41
|
+
try {
|
|
42
|
+
const result = readEvents({ type: "feedback", ...(ref ? { ref } : {}) });
|
|
43
|
+
const lines = [];
|
|
44
|
+
const limit = ref ? MAX_FEEDBACK_LINES : MAX_GLOBAL_FEEDBACK_LINES;
|
|
45
|
+
for (const event of result.events.slice(-limit)) {
|
|
46
|
+
const md = (event.metadata ?? {});
|
|
47
|
+
const signal = typeof md.signal === "string" ? md.signal : "?";
|
|
48
|
+
const note = typeof md.note === "string" ? md.note : typeof md.reason === "string" ? md.reason : "";
|
|
49
|
+
const details = note ? `[${signal}] ${note}` : `[${signal}]`;
|
|
50
|
+
lines.push(!ref && event.ref ? `${event.ref} ${details}` : details);
|
|
51
|
+
}
|
|
52
|
+
return lines;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build schema/lint hints for the prompt. For lesson refs, run the lesson
|
|
60
|
+
* lint over the current content and surface any findings — they are a
|
|
61
|
+
* concrete starting point for the agent's revision.
|
|
62
|
+
*/
|
|
63
|
+
function buildSchemaHints(type, content) {
|
|
64
|
+
if (!content)
|
|
65
|
+
return [];
|
|
66
|
+
if (type !== "lesson")
|
|
67
|
+
return [];
|
|
68
|
+
const report = lintLessonContent(content, "reflect");
|
|
69
|
+
return report.findings.map((f) => `[${f.kind}] ${f.message}`);
|
|
70
|
+
}
|
|
71
|
+
function loadAgentConfigFromDisk() {
|
|
72
|
+
const config = loadConfig();
|
|
73
|
+
return parseAgentConfig(config.agent);
|
|
74
|
+
}
|
|
75
|
+
function resolveProfile(options) {
|
|
76
|
+
if (options.agentProfile)
|
|
77
|
+
return options.agentProfile;
|
|
78
|
+
const agent = options.agentConfig ?? loadAgentConfigFromDisk();
|
|
79
|
+
return requireAgentProfile(agent, options.profile);
|
|
80
|
+
}
|
|
81
|
+
function failureEnvelope(result, ref, fallbackReason = "non_zero_exit") {
|
|
82
|
+
const reason = result.reason ?? fallbackReason;
|
|
83
|
+
return {
|
|
84
|
+
schemaVersion: 1,
|
|
85
|
+
ok: false,
|
|
86
|
+
reason,
|
|
87
|
+
error: result.error ?? `agent failure (${reason})`,
|
|
88
|
+
...(ref ? { ref } : {}),
|
|
89
|
+
exitCode: result.exitCode,
|
|
90
|
+
...(result.stdout ? { stdout: result.stdout } : {}),
|
|
91
|
+
...(result.stderr ? { stderr: result.stderr } : {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export async function akmReflect(options = {}) {
|
|
95
|
+
const stash = options.stashDir ?? resolveStashDir();
|
|
96
|
+
// 1. Always emit `reflect_invoked` at command entry — observers see the
|
|
97
|
+
// attempt regardless of downstream success/failure.
|
|
98
|
+
appendEvent({
|
|
99
|
+
eventType: "reflect_invoked",
|
|
100
|
+
...(options.ref ? { ref: options.ref } : {}),
|
|
101
|
+
metadata: {
|
|
102
|
+
...(options.task ? { task: options.task } : {}),
|
|
103
|
+
...(options.profile ? { profile: options.profile } : {}),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
// 2. Resolve target asset content (if a ref is supplied).
|
|
107
|
+
let assetContent;
|
|
108
|
+
let parsedRef;
|
|
109
|
+
if (options.ref) {
|
|
110
|
+
parsedRef = parseAssetRef(options.ref);
|
|
111
|
+
try {
|
|
112
|
+
const entry = await lookup(parsedRef);
|
|
113
|
+
if (entry?.filePath && fs.existsSync(entry.filePath)) {
|
|
114
|
+
assetContent = fs.readFileSync(entry.filePath, "utf8");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Index miss is non-fatal — the agent can still propose a fresh asset.
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// 3. Resolve agent profile. ConfigError surfaces as a thrown error so the
|
|
122
|
+
// CLI dispatcher renders the standard envelope.
|
|
123
|
+
let profile;
|
|
124
|
+
try {
|
|
125
|
+
profile = resolveProfile(options);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
if (err instanceof ConfigError || err instanceof UsageError)
|
|
129
|
+
throw err;
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
// 4. Build the prompt.
|
|
133
|
+
const feedback = readRecentFeedback(options.ref);
|
|
134
|
+
const schemaHints = buildSchemaHints(parsedRef?.type ?? "", assetContent);
|
|
135
|
+
const prompt = buildReflectPrompt({
|
|
136
|
+
...(options.ref ? { ref: options.ref } : {}),
|
|
137
|
+
...(parsedRef?.type ? { type: parsedRef.type } : {}),
|
|
138
|
+
...(parsedRef?.name ? { name: parsedRef.name } : {}),
|
|
139
|
+
...(assetContent !== undefined ? { assetContent } : {}),
|
|
140
|
+
...(feedback.length > 0 ? { feedback } : {}),
|
|
141
|
+
...(schemaHints.length > 0 ? { schemaHints } : {}),
|
|
142
|
+
...(options.task ? { task: options.task } : {}),
|
|
143
|
+
});
|
|
144
|
+
// 5. Spawn the agent. Force captured stdio + JSON parse so we can extract
|
|
145
|
+
// the structured payload without confusing terminal control codes.
|
|
146
|
+
const runOptions = {
|
|
147
|
+
stdio: "captured",
|
|
148
|
+
parseOutput: "text",
|
|
149
|
+
...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
|
|
150
|
+
...(options.runAgentOptions ?? {}),
|
|
151
|
+
};
|
|
152
|
+
const result = await runAgent(profile, prompt, runOptions);
|
|
153
|
+
if (!result.ok) {
|
|
154
|
+
return failureEnvelope(result, options.ref);
|
|
155
|
+
}
|
|
156
|
+
// 6. Parse stdout into a proposal payload.
|
|
157
|
+
let payload;
|
|
158
|
+
try {
|
|
159
|
+
payload = parseAgentProposalPayload(result.stdout);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
return {
|
|
163
|
+
schemaVersion: 1,
|
|
164
|
+
ok: false,
|
|
165
|
+
reason: "parse_error",
|
|
166
|
+
error: err instanceof Error ? err.message : String(err),
|
|
167
|
+
...(options.ref ? { ref: options.ref } : {}),
|
|
168
|
+
exitCode: result.exitCode,
|
|
169
|
+
stdout: result.stdout,
|
|
170
|
+
...(result.stderr ? { stderr: result.stderr } : {}),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// 7. Create the proposal. The proposal queue is the ONLY thing reflect
|
|
174
|
+
// writes — promotion to a real asset is gated by `akm proposal accept`.
|
|
175
|
+
const createInput = {
|
|
176
|
+
ref: payload.ref,
|
|
177
|
+
source: "reflect",
|
|
178
|
+
sourceRun: `reflect-${Date.now()}`,
|
|
179
|
+
payload: {
|
|
180
|
+
content: payload.content,
|
|
181
|
+
...(payload.frontmatter ? { frontmatter: payload.frontmatter } : {}),
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
const proposal = createProposal(stash, createInput, options.ctx);
|
|
185
|
+
return {
|
|
186
|
+
schemaVersion: 1,
|
|
187
|
+
ok: true,
|
|
188
|
+
proposal,
|
|
189
|
+
ref: proposal.ref,
|
|
190
|
+
agentProfile: profile.name,
|
|
191
|
+
durationMs: result.durationMs,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { toErrorMessage } from "../core/common";
|
|
2
2
|
import { DEFAULT_CONFIG, loadConfig } from "../core/config";
|
|
3
|
+
import { warn } from "../core/warn";
|
|
3
4
|
import { resolveProviderFactory } from "../registry/factory";
|
|
4
5
|
// ── Eagerly import providers to trigger self-registration ───────────────────
|
|
5
6
|
import "../registry/providers/index";
|
|
@@ -90,7 +91,7 @@ export function resolveRegistries(configRegistries) {
|
|
|
90
91
|
if (!url)
|
|
91
92
|
continue;
|
|
92
93
|
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
93
|
-
|
|
94
|
+
warn(`[akm] Ignoring AKM_REGISTRY_URL entry: must start with http:// or https://, got "${url}"`);
|
|
94
95
|
continue;
|
|
95
96
|
}
|
|
96
97
|
entries.push({ url });
|
|
@@ -10,6 +10,7 @@ import { toErrorMessage, tryReadStdinText } from "../core/common";
|
|
|
10
10
|
import { loadConfig } from "../core/config";
|
|
11
11
|
import { UsageError } from "../core/errors";
|
|
12
12
|
import { warn } from "../core/warn";
|
|
13
|
+
import { SCOPE_KEYS } from "../indexer/metadata";
|
|
13
14
|
/**
|
|
14
15
|
* Parse a shorthand duration string to a number of milliseconds.
|
|
15
16
|
* Supports: `30d` (days), `12h` (hours), `6m` (months, approximated as 30d).
|
|
@@ -52,6 +53,17 @@ export function buildMemoryFrontmatter(fields) {
|
|
|
52
53
|
obj.expires = fields.expires;
|
|
53
54
|
if (fields.subjective)
|
|
54
55
|
obj.subjective = true;
|
|
56
|
+
// Scope keys are emitted as flat top-level keys (`scope_user`, …) so the
|
|
57
|
+
// existing one-level frontmatter parser can read them without nesting.
|
|
58
|
+
// A scope object with no populated values is dropped.
|
|
59
|
+
if (fields.scope) {
|
|
60
|
+
for (const key of SCOPE_KEYS) {
|
|
61
|
+
const value = fields.scope[key];
|
|
62
|
+
if (typeof value === "string" && value.trim()) {
|
|
63
|
+
obj[`scope_${key}`] = value.trim();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
55
67
|
// No fields populated → emit a bare delimiter pair so callers don't
|
|
56
68
|
// produce `---\n{}\n---` (the YAML serializer's empty-object form).
|
|
57
69
|
if (Object.keys(obj).length === 0)
|
|
@@ -45,6 +45,8 @@ export async function akmSearch(input) {
|
|
|
45
45
|
// Primary stash directory — used for DB path lookups and as the default
|
|
46
46
|
// stash root. Safe because the empty-sources case is handled above.
|
|
47
47
|
const stashDir = sources[0].path;
|
|
48
|
+
const filters = normalizeScopeFilters(input.filters);
|
|
49
|
+
const includeProposed = input.includeProposed === true;
|
|
48
50
|
const localResult = source === "registry"
|
|
49
51
|
? undefined
|
|
50
52
|
: await searchLocal({
|
|
@@ -54,6 +56,8 @@ export async function akmSearch(input) {
|
|
|
54
56
|
stashDir,
|
|
55
57
|
sources,
|
|
56
58
|
config,
|
|
59
|
+
filters,
|
|
60
|
+
includeProposed,
|
|
57
61
|
});
|
|
58
62
|
const registryResult = source === "stash" ? undefined : await searchRegistry(query, { limit, registries: config.registries });
|
|
59
63
|
if (source === "stash") {
|
|
@@ -76,6 +80,8 @@ export async function akmSearch(input) {
|
|
|
76
80
|
// prefixed), otherwise derive it from source + ref for backward compat.
|
|
77
81
|
const installRef = hit.installRef ??
|
|
78
82
|
(hit.source === "npm" ? `npm:${hit.ref}` : hit.source === "git" ? `git+${hit.ref}` : `github:${hit.ref}`);
|
|
83
|
+
// The legacy registry boolean `curated` was removed in v1 (spec §4.2).
|
|
84
|
+
// Hit-level `warnings` are forwarded when the provider surfaced any.
|
|
79
85
|
return {
|
|
80
86
|
type: "registry",
|
|
81
87
|
name: hit.title,
|
|
@@ -83,8 +89,8 @@ export async function akmSearch(input) {
|
|
|
83
89
|
description: hit.description,
|
|
84
90
|
action: `akm add ${installRef} -> then search again`,
|
|
85
91
|
score: hit.score,
|
|
86
|
-
curated: hit.curated,
|
|
87
92
|
registryName: hit.registryName,
|
|
93
|
+
...(hit.warnings && hit.warnings.length > 0 ? { warnings: hit.warnings } : {}),
|
|
88
94
|
};
|
|
89
95
|
});
|
|
90
96
|
if (source === "registry") {
|
|
@@ -188,6 +194,73 @@ export function parseSearchSource(source) {
|
|
|
188
194
|
return "stash";
|
|
189
195
|
throw new UsageError(`Invalid value for --source: ${String(source)}. Expected one of: stash|registry|both`, "INVALID_SOURCE_VALUE");
|
|
190
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Strip empty / non-string values from a scope filter object. Returns
|
|
199
|
+
* `undefined` when nothing meaningful remains, so callers don't pay for an
|
|
200
|
+
* empty-filter post-walk in `searchLocal`.
|
|
201
|
+
*/
|
|
202
|
+
function normalizeScopeFilters(raw) {
|
|
203
|
+
if (!raw)
|
|
204
|
+
return undefined;
|
|
205
|
+
const out = {};
|
|
206
|
+
for (const key of ["user", "agent", "run", "channel"]) {
|
|
207
|
+
const value = raw[key];
|
|
208
|
+
if (typeof value === "string" && value.trim()) {
|
|
209
|
+
out[key] = value.trim();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Parse repeated `--filter k=v` / `--scope k=v` argv tokens into a
|
|
216
|
+
* `StashEntryScope`. Throws a {@link UsageError} for malformed tokens
|
|
217
|
+
* (missing `=`, unknown key) so callers don't see ambiguous misses.
|
|
218
|
+
*
|
|
219
|
+
* Used by both `akm search --filter` and `akm show --scope`.
|
|
220
|
+
*/
|
|
221
|
+
export function parseScopeFilterFlags(values, flagName = "--filter") {
|
|
222
|
+
if (values.length === 0)
|
|
223
|
+
return undefined;
|
|
224
|
+
const out = {};
|
|
225
|
+
for (const raw of values) {
|
|
226
|
+
const eq = raw.indexOf("=");
|
|
227
|
+
if (eq <= 0) {
|
|
228
|
+
throw new UsageError(`Invalid ${flagName} value "${raw}". Expected key=value (e.g. user=alice).`);
|
|
229
|
+
}
|
|
230
|
+
const key = raw.slice(0, eq).trim();
|
|
231
|
+
const value = raw.slice(eq + 1).trim();
|
|
232
|
+
if (key !== "user" && key !== "agent" && key !== "run" && key !== "channel") {
|
|
233
|
+
throw new UsageError(`Unknown scope key "${key}" in ${flagName}. Valid keys: user, agent, run, channel.`);
|
|
234
|
+
}
|
|
235
|
+
if (!value) {
|
|
236
|
+
throw new UsageError(`${flagName} ${key}=… requires a non-empty value.`);
|
|
237
|
+
}
|
|
238
|
+
out[key] = value;
|
|
239
|
+
}
|
|
240
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Returns true iff `entry.scope` (when present) satisfies every key in
|
|
244
|
+
* `filters`. A missing `entry.scope` (legacy memories) only matches when
|
|
245
|
+
* `filters` is empty / undefined.
|
|
246
|
+
*
|
|
247
|
+
* Filter semantics:
|
|
248
|
+
* - No filter passed → all entries match (legacy behavior preserved).
|
|
249
|
+
* - `filters.user = "alice"` → entry must have `scope.user === "alice"`.
|
|
250
|
+
* - Multiple keys → AND-joined; every supplied key must match.
|
|
251
|
+
*/
|
|
252
|
+
export function entryMatchesScopeFilters(scope, filters) {
|
|
253
|
+
if (!filters)
|
|
254
|
+
return true;
|
|
255
|
+
for (const key of ["user", "agent", "run", "channel"]) {
|
|
256
|
+
const expected = filters[key];
|
|
257
|
+
if (expected === undefined)
|
|
258
|
+
continue;
|
|
259
|
+
if (!scope || scope[key] !== expected)
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
191
264
|
/**
|
|
192
265
|
* Merge stash hits and registry hits via simple concatenation.
|
|
193
266
|
*/
|
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fetchWithRetry, IS_WINDOWS } from "../core/common";
|
|
6
|
+
import { warn } from "../core/warn";
|
|
6
7
|
import { githubHeaders } from "../integrations/github";
|
|
7
8
|
const REPO = "itlackey/akm";
|
|
8
9
|
const DEFAULT_PACKAGE_NAME = "akm-cli";
|
|
@@ -147,7 +148,7 @@ export async function performUpgrade(check, opts) {
|
|
|
147
148
|
const checksumsResponse = await fetchWithRetry(checksumsUrl);
|
|
148
149
|
if (!checksumsResponse.ok) {
|
|
149
150
|
if (skipChecksum) {
|
|
150
|
-
|
|
151
|
+
warn(`WARNING: checksums.txt fetch failed (HTTP ${checksumsResponse.status}). Proceeding without verification because --skip-checksum was provided.`);
|
|
151
152
|
}
|
|
152
153
|
else {
|
|
153
154
|
throw new Error(`Checksum verification failed: could not fetch ${checksumsUrl} (HTTP ${checksumsResponse.status}). ` +
|
|
@@ -166,7 +167,7 @@ export async function performUpgrade(check, opts) {
|
|
|
166
167
|
}
|
|
167
168
|
else {
|
|
168
169
|
if (skipChecksum) {
|
|
169
|
-
|
|
170
|
+
warn(`WARNING: ${binaryName} not found in checksums.txt. Proceeding without verification because --skip-checksum was provided.`);
|
|
170
171
|
}
|
|
171
172
|
else {
|
|
172
173
|
throw new Error(`Checksum verification failed: ${binaryName} not listed in checksums.txt. ` +
|
|
@@ -182,7 +183,7 @@ export async function performUpgrade(check, opts) {
|
|
|
182
183
|
}
|
|
183
184
|
// Network or parse failure
|
|
184
185
|
if (skipChecksum) {
|
|
185
|
-
|
|
186
|
+
warn(`WARNING: Could not fetch or parse checksums: ${err instanceof Error ? err.message : String(err)}. Proceeding because --skip-checksum was provided.`);
|
|
186
187
|
}
|
|
187
188
|
else {
|
|
188
189
|
throw new Error(`Checksum verification failed: ${err instanceof Error ? err.message : String(err)}. ` +
|