akm-cli 0.7.5 → 0.8.0-rc.11
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/{.github/CHANGELOG.md → CHANGELOG.md} +192 -2
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +133 -0
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2569 -1449
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +110 -0
- package/dist/commands/agent-support.js +68 -0
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +130 -534
- package/dist/commands/consolidate.js +2122 -0
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +1075 -77
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +5 -23
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +204 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +1302 -0
- package/dist/commands/help/help-accept.md +12 -0
- package/dist/commands/help/help-improve.md +69 -0
- package/dist/commands/help/help-proposals.md +18 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +11 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +217 -0
- package/dist/commands/improve-profiles.js +166 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +2373 -0
- package/dist/commands/info.js +5 -2
- package/dist/commands/init.js +50 -2
- package/dist/commands/installed-stashes.js +102 -139
- package/dist/commands/knowledge.js +136 -0
- package/dist/commands/lint/agent-linter.js +49 -0
- package/dist/commands/lint/base-linter.js +479 -0
- package/dist/commands/lint/command-linter.js +49 -0
- package/dist/commands/lint/default-linter.js +16 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +196 -0
- package/dist/commands/lint/knowledge-linter.js +16 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +61 -0
- package/dist/commands/lint/registry.js +36 -0
- package/dist/commands/lint/skill-linter.js +45 -0
- package/dist/commands/lint/task-linter.js +50 -0
- package/dist/commands/lint/types.js +4 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal.js +67 -12
- package/dist/commands/propose.js +86 -31
- package/dist/commands/reflect.js +1091 -73
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +69 -6
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +148 -25
- package/dist/commands/source-add.js +17 -45
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +14 -19
- package/dist/commands/tasks.js +437 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +17 -2
- package/dist/core/asset-registry.js +12 -17
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +67 -1
- package/dist/core/common.js +182 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +534 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +364 -981
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +91 -138
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -8
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +20 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +806 -0
- package/dist/core/parse.js +158 -0
- package/dist/core/paths.js +280 -14
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +512 -42
- package/dist/core/state-db.js +1068 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +64 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +178 -256
- package/dist/indexer/db.js +975 -103
- package/dist/indexer/ensure-index.js +64 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +376 -101
- package/dist/indexer/graph-db.js +391 -0
- package/dist/indexer/graph-dedup.js +95 -0
- package/dist/indexer/graph-extraction.js +550 -124
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +523 -301
- package/dist/indexer/llm-cache.js +52 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +167 -160
- package/dist/indexer/memory-inference.js +152 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +275 -196
- package/dist/indexer/path-resolver.js +92 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +331 -0
- package/dist/indexer/ranking.js +81 -0
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +111 -0
- package/dist/indexer/search-source.js +44 -10
- package/dist/indexer/semantic-status.js +6 -17
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +28 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +122 -230
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +7 -13
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +70 -5
- package/dist/integrations/agent/prompts.js +214 -80
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +118 -23
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +69 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +282 -0
- package/dist/integrations/session-logs/providers/opencode.js +258 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +77 -124
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +95 -48
- package/dist/llm/graph-extract.js +676 -70
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +77 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/extract-session.md +80 -0
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +292 -0
- package/dist/output/cli-hints-short.md +66 -0
- package/dist/output/cli-hints.js +7 -320
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +300 -257
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +102 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -516
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1039 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +11 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1092
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +71 -50
- package/dist/registry/providers/static-index.js +53 -48
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17750 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +775 -37
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +5 -12
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +138 -21
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +7 -0
- package/dist/tasks/backends/cron.js +203 -0
- package/dist/tasks/backends/exec-utils.js +28 -0
- package/dist/tasks/backends/index.js +24 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +187 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +215 -0
- package/dist/tasks/parser.js +211 -0
- package/dist/tasks/resolveAkmBin.js +87 -0
- package/dist/tasks/runner.js +458 -0
- package/dist/tasks/schedule.js +227 -0
- package/dist/tasks/schema.js +15 -0
- package/dist/tasks/validator.js +62 -0
- package/dist/version.js +3 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +15 -0
- package/dist/wiki/wiki.js +13 -61
- package/dist/workflows/authoring.js +8 -25
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +140 -10
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +11 -3
- package/dist/workflows/runs.js +77 -92
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +10 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +2 -2
- package/docs/migration/release-notes/0.8.0.md +48 -0
- package/docs/migration/v0.7-to-v0.8.md +1307 -0
- package/package.json +30 -12
- package/.github/LICENSE +0 -374
- package/dist/commands/install-audit.js +0 -381
- package/dist/commands/vault.js +0 -328
- package/dist/templates/wiki-templates.js +0 -100
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1
4
|
/**
|
|
2
5
|
* Shared prompt builders for proposal-producing agent commands (#226).
|
|
3
6
|
*
|
|
@@ -23,13 +26,14 @@
|
|
|
23
26
|
* during validation. We carry it through if the agent supplies it.
|
|
24
27
|
*/
|
|
25
28
|
import { TYPE_DIRS } from "../../core/asset-spec";
|
|
29
|
+
import { parseEmbeddedJsonResponse, stripCodeFences, stripThinkBlocks } from "../../core/parse";
|
|
26
30
|
/**
|
|
27
31
|
* Per-asset-type frontmatter / authoring hints surfaced in the prompt so
|
|
28
32
|
* the agent can produce content that passes proposal validation. Kept tiny:
|
|
29
33
|
* full schema docs live in `docs/` — these are nudges, not contracts.
|
|
30
34
|
*/
|
|
31
35
|
const TYPE_HINTS = {
|
|
32
|
-
lesson: "lesson assets MUST start with frontmatter containing `description` and `when_to_use` keys (both non-empty). Body
|
|
36
|
+
lesson: "lesson assets MUST start with frontmatter containing `description` and `when_to_use` keys (both non-empty). Body: 1–3 short paragraphs of practical guidance. A lesson is NOT a restatement of the source asset — it answers: When should I reach for this? What goes wrong without it? What did real use reveal that the asset itself doesn't say?",
|
|
33
37
|
skill: "skill assets are stored as `skills/<name>/SKILL.md`. Frontmatter typically includes `name`, `description`, and `when_to_use`.",
|
|
34
38
|
command: "command assets are markdown with optional frontmatter (`name`, `description`). The body is the prompt template the user invokes.",
|
|
35
39
|
agent: "agent assets are markdown with frontmatter describing the agent role (`name`, `description`, optional `tools`, `model`).",
|
|
@@ -37,7 +41,8 @@ const TYPE_HINTS = {
|
|
|
37
41
|
memory: "memory assets are short factual notes the user wants persisted across sessions. Frontmatter usually includes `description`.",
|
|
38
42
|
workflow: "workflow assets are markdown describing a multi-step process. Include `# <Title>` and ordered `## Step N` sections.",
|
|
39
43
|
script: "script assets are executable text files. Include a shebang and minimal usage comment.",
|
|
40
|
-
|
|
44
|
+
env: "env assets are `.env` files holding a group of related CONFIGURATION for an app/service (KEY=VALUE pairs, `#` comments) — URLs, flags, and any credentials it needs. Values may or may not be sensitive; all are protected (key names discoverable, values stay on disk). Inject with `akm env run env:<name> -- <cmd>` (the safe path — values never reach stdout/your context); do NOT run `akm env export` and read its output, as that prints values. For a single sensitive value used on its own for authentication (token, key, cert) use a `secret` instead. Never echo values back to the user.",
|
|
45
|
+
vault: "vault assets are DEPRECATED (use env). They store environment variables (KEY=VALUE pairs); comments use `#`. Never echo secret values back to the user.",
|
|
41
46
|
wiki: "wiki assets are markdown reference pages with `# Title` and structured headings.",
|
|
42
47
|
};
|
|
43
48
|
function hintForType(type) {
|
|
@@ -54,9 +59,15 @@ function knownTypeList() {
|
|
|
54
59
|
*/
|
|
55
60
|
const RESPONSE_CONTRACT_JSON = [
|
|
56
61
|
"Respond ONLY with a single JSON object. No prose before or after.",
|
|
57
|
-
'Shape: {"ref": "<type>:<name>", "content": "<full file contents>", "frontmatter": {...}}',
|
|
62
|
+
'Shape: {"ref": "<type>:<name>", "content": "<full file contents>", "frontmatter": {...}, "confidence": <number 0..1>}',
|
|
58
63
|
"`content` is the full file body that will be written if accepted.",
|
|
59
64
|
"`frontmatter` is optional — include it if `content` starts with `---` so reviewers can sanity-check the keys.",
|
|
65
|
+
"`confidence` is REQUIRED. Self-rate this proposal on [0, 1] by how certain you are it materially improves the source asset. Calibrate honestly:",
|
|
66
|
+
" • 0.90+ — high certainty: fixes a real defect or adds load-bearing missing content; a reviewer would clearly accept.",
|
|
67
|
+
" • 0.70–0.89 — clear improvement, but a reviewer might reasonably prefer different framing or scope.",
|
|
68
|
+
" • 0.50–0.69 — marginal / judgment call; might help, might not be worth the churn.",
|
|
69
|
+
" • Below 0.50 — you are not confident this improves on the source. Prefer returning the source body roughly unchanged with a low score over inventing changes.",
|
|
70
|
+
"Auto-accept gates on confidence ≥ 0.80 by default. Overclaiming ships low-quality changes; underclaiming leaves good ones stuck in queue. Be honest.",
|
|
60
71
|
].join("\n");
|
|
61
72
|
/**
|
|
62
73
|
* Response contract used when a draft file path is available. Instructs the
|
|
@@ -68,19 +79,55 @@ function fileWriteContract(draftFilePath) {
|
|
|
68
79
|
`Write the complete improved asset content to: ${draftFilePath}`,
|
|
69
80
|
"Use your file-editing tools to create or overwrite that file.",
|
|
70
81
|
"Do NOT output JSON to stdout. Do NOT print the file contents. Just write the file.",
|
|
71
|
-
"When
|
|
82
|
+
"When done, output a single line on stdout: DRAFT_WRITTEN confidence=<0.0-1.0>",
|
|
83
|
+
"`confidence` is REQUIRED and must be your honest self-rated [0, 1] score for this proposal:",
|
|
84
|
+
" • 0.90+ — fixes a real defect or adds load-bearing missing content; reviewer would clearly accept.",
|
|
85
|
+
" • 0.70–0.89 — clear improvement, but a reviewer might prefer different framing.",
|
|
86
|
+
" • 0.50–0.69 — marginal / judgment call.",
|
|
87
|
+
" • Below 0.50 — not confident; prefer not writing changes at all.",
|
|
88
|
+
"Auto-accept gates on confidence ≥ 0.80. Overclaim → low-quality changes land; underclaim → good changes stuck in queue.",
|
|
72
89
|
].join("\n");
|
|
73
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Extract a confidence score from a `DRAFT_WRITTEN confidence=<n>` line emitted
|
|
93
|
+
* by an agent following {@link fileWriteContract}. Tolerates trailing prose,
|
|
94
|
+
* surrounding log lines, and missing/invalid confidence (returns `undefined`
|
|
95
|
+
* so callers can keep the proposal but skip auto-accept).
|
|
96
|
+
*
|
|
97
|
+
* Matched forms (case-insensitive, anywhere in stdout):
|
|
98
|
+
* - `DRAFT_WRITTEN confidence=0.85`
|
|
99
|
+
* - `DRAFT_WRITTEN confidence=0.85 ...trailing`
|
|
100
|
+
* - `DRAFT_WRITTEN` (no confidence — returns `undefined`)
|
|
101
|
+
*/
|
|
102
|
+
export function extractDraftConfidence(stdout) {
|
|
103
|
+
if (!stdout)
|
|
104
|
+
return undefined;
|
|
105
|
+
const match = stdout.match(/\bDRAFT_WRITTEN\b[^\S\r\n]+confidence=([0-9]*\.?[0-9]+)/i);
|
|
106
|
+
if (!match)
|
|
107
|
+
return undefined;
|
|
108
|
+
const value = Number.parseFloat(match[1] ?? "");
|
|
109
|
+
if (!Number.isFinite(value) || value < 0 || value > 1)
|
|
110
|
+
return undefined;
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
74
113
|
/**
|
|
75
114
|
* Build the prompt for `akm reflect [ref]`. Asks the agent to review an
|
|
76
115
|
* existing asset (plus any negative feedback / lint findings) and propose
|
|
77
|
-
* an improved version. Returns a
|
|
78
|
-
*
|
|
116
|
+
* an improved version. Returns a {@link ReflectPromptResult} containing the
|
|
117
|
+
* prompt string and an optional character ceiling for max-tokens enforcement.
|
|
79
118
|
*/
|
|
80
119
|
export function buildReflectPrompt(input) {
|
|
81
120
|
const sections = [];
|
|
82
121
|
if (input.ref && input.type && input.name) {
|
|
83
|
-
|
|
122
|
+
// Change 2 — type-conditioned goal framing
|
|
123
|
+
const isLesson = input.type === "lesson";
|
|
124
|
+
const isSkill = input.type === "skill";
|
|
125
|
+
const goalSentence = isLesson
|
|
126
|
+
? `Your task is to distill what usage signals reveal about this ${input.type} asset — when to reach for it, what goes wrong without it, and what real use has revealed that the asset itself does not say. Do not reproduce the source content; your proposal must add information the source does not contain.`
|
|
127
|
+
: isSkill
|
|
128
|
+
? "Your task is to review this skill asset, identify what the feedback and related distilled lessons show is broken, missing, unclear, or durable enough to promote into long-term documentation, and produce a single improved proposal. If the strongest evidence points to companion reference material rather than the main SKILL.md, you may instead propose a skill-adjacent knowledge doc such as `knowledge:skills/<skill>/references/<topic>`."
|
|
129
|
+
: `Your task is to review this ${input.type} asset, identify what the feedback signals as broken, missing, or unclear, and produce an improved version. Do not reproduce the source content unchanged; your proposal must correct or add something the source lacks.`;
|
|
130
|
+
sections.push(goalSentence);
|
|
84
131
|
sections.push(`Target ref: ${input.ref}`);
|
|
85
132
|
sections.push(`Asset-type guidance: ${hintForType(input.type)}`);
|
|
86
133
|
}
|
|
@@ -92,10 +139,35 @@ export function buildReflectPrompt(input) {
|
|
|
92
139
|
if (input.task?.trim()) {
|
|
93
140
|
sections.push(`Task / focus: ${input.task.trim()}`);
|
|
94
141
|
}
|
|
142
|
+
// Change 3 & 4 — feedback moved before asset content; missing else branch added
|
|
143
|
+
if (input.feedback && input.feedback.length > 0) {
|
|
144
|
+
sections.push("Recent feedback / signals:");
|
|
145
|
+
for (const line of input.feedback)
|
|
146
|
+
sections.push(`- ${line}`);
|
|
147
|
+
}
|
|
148
|
+
else if (!input.ref) {
|
|
149
|
+
sections.push("Recent feedback / signals:");
|
|
150
|
+
sections.push("- (no feedback events recorded)");
|
|
151
|
+
}
|
|
152
|
+
else if (input.type === "skill" && input.relatedLessons && input.relatedLessons.length > 0) {
|
|
153
|
+
sections.push("No direct feedback events were recorded. Limit substantive changes to what is justified by the related distilled lessons below; do not speculate beyond that evidence.");
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// ref is set but no feedback — explicitly constrain scope to schema compliance
|
|
157
|
+
sections.push("No usage feedback recorded. Limit your proposal to schema and structural improvements only: missing required frontmatter fields, unclear `when_to_use`, ambiguous description, or broken formatting. Do not speculate about runtime weaknesses you have not observed.");
|
|
158
|
+
}
|
|
95
159
|
if (input.assetContent?.trim()) {
|
|
96
|
-
|
|
160
|
+
// Cap at 12 000 chars to stay well under OS ARG_MAX when the prompt is
|
|
161
|
+
// passed as a CLI argument to opencode/claude. Large assets (wiki snapshots,
|
|
162
|
+
// long runbooks) would otherwise trigger E2BIG on posix_spawn.
|
|
163
|
+
const REFLECT_CONTENT_CAP = 12_000;
|
|
164
|
+
const body = input.assetContent.trimEnd();
|
|
165
|
+
const truncated = body.length > REFLECT_CONTENT_CAP;
|
|
166
|
+
sections.push(truncated
|
|
167
|
+
? `Current asset content (first ${REFLECT_CONTENT_CAP} chars — full asset is ${body.length} chars):`
|
|
168
|
+
: "Current asset content (verbatim):");
|
|
97
169
|
sections.push("```");
|
|
98
|
-
sections.push(
|
|
170
|
+
sections.push(truncated ? `${body.slice(0, REFLECT_CONTENT_CAP)}\n... [truncated — focus on the visible portion]` : body);
|
|
99
171
|
sections.push("```");
|
|
100
172
|
}
|
|
101
173
|
else if (input.ref) {
|
|
@@ -104,23 +176,103 @@ export function buildReflectPrompt(input) {
|
|
|
104
176
|
else {
|
|
105
177
|
sections.push("(No existing asset content was supplied.)");
|
|
106
178
|
}
|
|
107
|
-
if (input.feedback && input.feedback.length > 0) {
|
|
108
|
-
sections.push("Recent feedback / signals:");
|
|
109
|
-
for (const line of input.feedback)
|
|
110
|
-
sections.push(`- ${line}`);
|
|
111
|
-
}
|
|
112
|
-
else if (!input.ref) {
|
|
113
|
-
sections.push("Recent feedback / signals:");
|
|
114
|
-
sections.push("- (no feedback events recorded)");
|
|
115
|
-
}
|
|
116
179
|
if (input.schemaHints && input.schemaHints.length > 0) {
|
|
117
180
|
sections.push("Schema / lint hints to address:");
|
|
118
181
|
for (const line of input.schemaHints)
|
|
119
182
|
sections.push(`- ${line}`);
|
|
120
183
|
}
|
|
121
|
-
|
|
184
|
+
if (input.relatedLessons && input.relatedLessons.length > 0) {
|
|
185
|
+
sections.push("Related distilled lessons to evaluate for consolidation:");
|
|
186
|
+
for (const lesson of input.relatedLessons) {
|
|
187
|
+
sections.push(`Lesson ref: ${lesson.ref}`);
|
|
188
|
+
sections.push("```");
|
|
189
|
+
sections.push(lesson.content.trimEnd());
|
|
190
|
+
sections.push("```");
|
|
191
|
+
}
|
|
192
|
+
sections.push("Evaluate whether these lessons contain strong evidence of factual, repeatable guidance that should be promoted into long-term skill documentation.");
|
|
193
|
+
sections.push("Promote only guidance that is durable, generally applicable, and supported by repeated evidence. Do not copy anecdotal details, one-off incidents, or duplicate wording verbatim.");
|
|
194
|
+
sections.push("If the guidance belongs in the main skill instructions, update the skill proposal. If it belongs in a companion reference document, return a `knowledge:skills/<skill>/references/<topic>` proposal instead.");
|
|
195
|
+
}
|
|
196
|
+
if (input.rejectedProposals && input.rejectedProposals.length > 0) {
|
|
197
|
+
const lines = ["## Previously Rejected Proposals"];
|
|
198
|
+
lines.push("The following proposals for this ref were already reviewed and rejected. " +
|
|
199
|
+
"Do NOT reproduce the same content or the same structural shape. " +
|
|
200
|
+
"Your new proposal must meaningfully differ from each of these in its approach, framing, or evidence used.");
|
|
201
|
+
for (const rp of input.rejectedProposals) {
|
|
202
|
+
lines.push(`\nRef: ${rp.ref}`);
|
|
203
|
+
lines.push(`Rejection reason: ${rp.reason}`);
|
|
204
|
+
if (rp.contentPreview) {
|
|
205
|
+
lines.push("Rejected content preview:");
|
|
206
|
+
lines.push("```");
|
|
207
|
+
lines.push(rp.contentPreview);
|
|
208
|
+
lines.push("```");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
sections.push(lines.join("\n"));
|
|
212
|
+
}
|
|
213
|
+
if (input.avoidPatterns && input.avoidPatterns.length > 0) {
|
|
214
|
+
sections.push(`## Avoid These Patterns\nPrevious assets in this run produced these errors — do not repeat them:\n${input.avoidPatterns.map((e) => `- ${e}`).join("\n")}`);
|
|
215
|
+
}
|
|
216
|
+
// R-1 / #372: Self-Refine (arXiv:2303.17651) — inject prior draft as critique target.
|
|
217
|
+
// On refinement iterations (iter > 0), the agent is shown its previous proposal
|
|
218
|
+
// and asked to self-critique and improve it rather than starting from scratch.
|
|
219
|
+
if (input.priorDraft?.trim()) {
|
|
220
|
+
sections.push("## Self-Refine: Critique and Improve\n" +
|
|
221
|
+
"The following is your previous draft proposal. " +
|
|
222
|
+
"Identify specific weaknesses: missing evidence, vague wording, incomplete frontmatter, " +
|
|
223
|
+
"or claims that duplicate existing content without adding new signal. " +
|
|
224
|
+
"Then produce an improved version that addresses those weaknesses. " +
|
|
225
|
+
"The revised proposal must be meaningfully better than the draft below — " +
|
|
226
|
+
"do not return the same content unchanged.\n\n" +
|
|
227
|
+
"Previous draft:\n```\n" +
|
|
228
|
+
input.priorDraft.trimEnd() +
|
|
229
|
+
"\n```");
|
|
230
|
+
}
|
|
231
|
+
sections.push("Produce a single proposal that addresses the feedback and respects the asset-type contract. If the proposal's frontmatter is missing `when_to_use`, you MUST generate one — a one-line trigger sentence describing exactly when a user should reach for this asset.");
|
|
232
|
+
// Content-preservation safety rails (#reflect-pipeline-fixes).
|
|
233
|
+
// These rules counter the observed failure modes where reflect rewrites
|
|
234
|
+
// asset content into shorter prose, drops concrete structure, or strips
|
|
235
|
+
// load-bearing frontmatter. Loud and explicit so small models follow.
|
|
236
|
+
//
|
|
237
|
+
// maxOutputChars is hoisted so the return value can include it for callers
|
|
238
|
+
// on the LLM path that want to set a hard max_tokens cap on the request.
|
|
239
|
+
let maxOutputChars;
|
|
240
|
+
if (input.ref && input.assetContent?.trim()) {
|
|
241
|
+
// Strip frontmatter to get source body length — mirrors checkReflectSize which
|
|
242
|
+
// compares body-only lengths. Inline regex avoids importing parseFrontmatter.
|
|
243
|
+
const rawContent = input.assetContent.trimEnd();
|
|
244
|
+
const fmBodyMatch = rawContent.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
245
|
+
const sourceBodyLen = (fmBodyMatch ? fmBodyMatch[1] : rawContent).trim().length;
|
|
246
|
+
// Compute concrete char bounds matching checkReflectSize constants:
|
|
247
|
+
// REFLECT_SIZE_GUARD_MIN_BYTES=200, REFLECT_SHRINK_RATIO_MIN=0.5,
|
|
248
|
+
// REFLECT_ABSOLUTE_FLOOR_BYTES=150, REFLECT_EXPAND_RATIO_MAX=2.5,
|
|
249
|
+
// REFLECT_ABSOLUTE_CEILING_BYTES=2500, REFLECT_ABSOLUTE_MAX_BYTES=25000.
|
|
250
|
+
// Embed concrete counts only when the gate will actually fire (source >= 200 chars).
|
|
251
|
+
const showCharBounds = sourceBodyLen >= 200;
|
|
252
|
+
const minChars = Math.max(Math.round(0.5 * sourceBodyLen), 150);
|
|
253
|
+
const maxChars = Math.min(Math.max(Math.round(2.5 * sourceBodyLen), 2500), 25000);
|
|
254
|
+
if (showCharBounds)
|
|
255
|
+
maxOutputChars = maxChars;
|
|
256
|
+
sections.push([
|
|
257
|
+
"## Content preservation rules (MUST follow)",
|
|
258
|
+
"1. PRESERVE ALL concrete content: code blocks, fenced snippets, CLI commands, numbered/bulleted checklists, tables, YAML/JSON examples, file paths, configuration keys, environment variable names, and CSS/HTML selectors. These are load-bearing — do NOT replace them with prose summaries.",
|
|
259
|
+
"2. PRESERVE the source asset's frontmatter. The post-processor reassembles the final asset from the original frontmatter plus your body. Do NOT emit `---` frontmatter delimiters at the top of `content` — start `content` with the markdown body (e.g. `# Heading` or the first paragraph). If you include frontmatter anyway, identity fields (`name`, `ref`, `id`, `slug`, `type`) will be reset to the original values.",
|
|
260
|
+
showCharBounds
|
|
261
|
+
? `3. DO NOT shrink the asset. Your body must be at least ${minChars} characters (source body is ${sourceBodyLen} chars; floor is 50%). If you genuinely need to remove a major section, explain why in a comment line at the top of the body (e.g. \`<!-- removed obsolete section X because ... -->\`).`
|
|
262
|
+
: "3. DO NOT shrink the asset dramatically. The improved body must be at least 50% of the source body length. If you genuinely need to remove a major section, explain why in a comment line at the top of the body (e.g. `<!-- removed obsolete section X because ... -->`).",
|
|
263
|
+
showCharBounds
|
|
264
|
+
? `4. DO NOT pad the asset with speculative material. Your body must be at most ${maxChars} characters (source body is ${sourceBodyLen} chars; ceiling is 250%). Do not add invented sections, hypothetical examples, or padding prose.`
|
|
265
|
+
: "4. DO NOT pad the asset with speculative material. The improved body must be at most 250% of the source body length unless the feedback explicitly requests added sections.",
|
|
266
|
+
"5. Improve clarity of surrounding prose, fix structural issues, add missing required frontmatter fields. Do NOT rewrite a runbook into an essay.",
|
|
267
|
+
].join("\n"));
|
|
268
|
+
}
|
|
269
|
+
if (!input.draftFilePath && input.ref) {
|
|
270
|
+
// Reinforce that the `ref` field is mandatory and must exactly match the target.
|
|
271
|
+
// Small models frequently omit `ref` from the response JSON, causing parse errors.
|
|
272
|
+
sections.push(`IMPORTANT: The JSON "ref" field is REQUIRED. It MUST be exactly: "${input.ref}"`);
|
|
273
|
+
}
|
|
122
274
|
sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
|
|
123
|
-
return sections.join("\n\n");
|
|
275
|
+
return { prompt: sections.join("\n\n"), ...(maxOutputChars !== undefined ? { maxOutputChars } : {}) };
|
|
124
276
|
}
|
|
125
277
|
/**
|
|
126
278
|
* Build the prompt for `akm propose <type> <name> --task ...`. Asks the
|
|
@@ -141,6 +293,33 @@ export function buildProposePrompt(input) {
|
|
|
141
293
|
sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
|
|
142
294
|
return sections.join("\n\n");
|
|
143
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Build the prompt for the schema repair pass in `akm improve`. Asks the
|
|
298
|
+
* agent to add the minimal required frontmatter to an asset that failed
|
|
299
|
+
* validation — without rewriting the body.
|
|
300
|
+
*/
|
|
301
|
+
export function buildSchemaRepairPrompt(input) {
|
|
302
|
+
const sections = [];
|
|
303
|
+
sections.push(`This ${input.type} asset failed schema validation with the error: "${input.reason}". ` +
|
|
304
|
+
`Your task is to fix the schema issue by adding or correcting the missing/invalid field(s) ` +
|
|
305
|
+
`while preserving all existing content.`);
|
|
306
|
+
sections.push(`Target ref: ${input.ref}`);
|
|
307
|
+
sections.push(`Schema requirements for ${input.type} assets: ${hintForType(input.type)}`);
|
|
308
|
+
const CONTENT_CAP = 3000;
|
|
309
|
+
const body = input.assetContent.trimEnd();
|
|
310
|
+
const truncated = body.length > CONTENT_CAP;
|
|
311
|
+
sections.push("Current asset content (first 3000 chars — sufficient to generate missing frontmatter):");
|
|
312
|
+
sections.push("```");
|
|
313
|
+
sections.push(truncated ? `${body.slice(0, CONTENT_CAP)}\n... [truncated]` : body);
|
|
314
|
+
sections.push("```");
|
|
315
|
+
sections.push("Produce the minimal fix: add ONLY the missing required frontmatter field(s). " +
|
|
316
|
+
"Do not rewrite the body unless it is empty. " +
|
|
317
|
+
"If `description` is missing, generate a concise one-sentence description from the content. " +
|
|
318
|
+
"If `when_to_use` is missing, generate a one-line trigger sentence. " +
|
|
319
|
+
"Preserve all existing frontmatter keys and the full body verbatim.");
|
|
320
|
+
sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
|
|
321
|
+
return sections.join("\n\n");
|
|
322
|
+
}
|
|
144
323
|
/**
|
|
145
324
|
* Parse agent stdout into a proposal payload. The agent contract requires a
|
|
146
325
|
* single JSON object; anything else is reported as a parse error so callers
|
|
@@ -151,7 +330,8 @@ export function buildProposePrompt(input) {
|
|
|
151
330
|
* 2. Prose preamble / postamble around the JSON object (handled by `extractEmbeddedJson`).
|
|
152
331
|
*/
|
|
153
332
|
export function parseAgentProposalPayload(stdout) {
|
|
154
|
-
|
|
333
|
+
// Strip <think> blocks and fences, then attempt full parse with embedded fallback.
|
|
334
|
+
const trimmed = stripCodeFences(stripThinkBlocks(stdout)).trim();
|
|
155
335
|
if (!trimmed)
|
|
156
336
|
throw new Error("agent produced empty output");
|
|
157
337
|
let parsed;
|
|
@@ -162,7 +342,7 @@ export function parseAgentProposalPayload(stdout) {
|
|
|
162
342
|
// Agent output contains prose before/after the JSON object (e.g. a local
|
|
163
343
|
// LLM that narrates before responding). Try extracting the first balanced
|
|
164
344
|
// top-level `{…}` from the text rather than failing immediately.
|
|
165
|
-
const embedded =
|
|
345
|
+
const embedded = parseEmbeddedJsonResponse(trimmed);
|
|
166
346
|
if (!embedded)
|
|
167
347
|
throw directErr;
|
|
168
348
|
parsed = embedded;
|
|
@@ -180,68 +360,22 @@ export function parseAgentProposalPayload(stdout) {
|
|
|
180
360
|
if (parsed.frontmatter && typeof parsed.frontmatter === "object" && !Array.isArray(parsed.frontmatter)) {
|
|
181
361
|
out.frontmatter = parsed.frontmatter;
|
|
182
362
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
function extractEmbeddedJson(text) {
|
|
192
|
-
for (let start = 0; start < text.length; start++) {
|
|
193
|
-
if (text[start] !== "{")
|
|
194
|
-
continue;
|
|
195
|
-
let depth = 0;
|
|
196
|
-
let inString = false;
|
|
197
|
-
let escaped = false;
|
|
198
|
-
for (let i = start; i < text.length; i++) {
|
|
199
|
-
const ch = text[i];
|
|
200
|
-
if (inString) {
|
|
201
|
-
if (escaped) {
|
|
202
|
-
escaped = false;
|
|
203
|
-
}
|
|
204
|
-
else if (ch === "\\") {
|
|
205
|
-
escaped = true;
|
|
206
|
-
}
|
|
207
|
-
else if (ch === '"') {
|
|
208
|
-
inString = false;
|
|
209
|
-
}
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (ch === '"') {
|
|
213
|
-
inString = true;
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (ch === "{")
|
|
217
|
-
depth++;
|
|
218
|
-
if (ch === "}") {
|
|
219
|
-
depth--;
|
|
220
|
-
if (depth === 0) {
|
|
221
|
-
try {
|
|
222
|
-
return JSON.parse(text.slice(start, i + 1));
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
363
|
+
// Phase 6A: extract optional `confidence` (number in [0, 1]). Clamp gently
|
|
364
|
+
// rather than reject — a model that returns 1.0 or 0 with extra precision
|
|
365
|
+
// (e.g. 1.0000001) should still surface a usable score. Anything that isn't
|
|
366
|
+
// a finite number is dropped so downstream `createProposal` can rely on the
|
|
367
|
+
// shape invariant.
|
|
368
|
+
if (typeof parsed.confidence === "number" && Number.isFinite(parsed.confidence)) {
|
|
369
|
+
const clamped = Math.max(0, Math.min(1, parsed.confidence));
|
|
370
|
+
out.confidence = clamped;
|
|
230
371
|
}
|
|
231
|
-
return
|
|
372
|
+
return out;
|
|
232
373
|
}
|
|
233
374
|
/**
|
|
234
375
|
* Strip `\`\`\`json … \`\`\`` fences and `<think>…</think>` reasoning blocks
|
|
235
|
-
* from agent output.
|
|
236
|
-
*
|
|
376
|
+
* from agent output. Thin wrapper around `core/parse` helpers, kept exported
|
|
377
|
+
* for backward compatibility (re-exported from `integrations/agent/index.ts`).
|
|
237
378
|
*/
|
|
238
379
|
export function stripJsonFences(text) {
|
|
239
|
-
|
|
240
|
-
.trim()
|
|
241
|
-
.replace(/<think>[\s\S]*?<\/think>/gi, "")
|
|
242
|
-
.trim();
|
|
243
|
-
const fenced = stripped.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/);
|
|
244
|
-
if (fenced)
|
|
245
|
-
return fenced[1] ?? stripped;
|
|
246
|
-
return stripped;
|
|
380
|
+
return stripCodeFences(stripThinkBlocks(text));
|
|
247
381
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { ConfigError } from "../../core/errors";
|
|
5
|
+
function resolveEffectiveMode(entry, profileName, config) {
|
|
6
|
+
if (entry.mode)
|
|
7
|
+
return entry.mode;
|
|
8
|
+
// Infer from profile pool when profile is specified
|
|
9
|
+
if (profileName) {
|
|
10
|
+
if (config.profiles?.llm?.[profileName])
|
|
11
|
+
return "llm";
|
|
12
|
+
const agentProfile = config.profiles?.agent?.[profileName];
|
|
13
|
+
if (agentProfile) {
|
|
14
|
+
return agentProfile.platform === "opencode-sdk" ? "sdk" : "agent";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Fall back to defaults
|
|
18
|
+
if (config.defaults?.llm)
|
|
19
|
+
return "llm";
|
|
20
|
+
if (config.defaults?.agent)
|
|
21
|
+
return "agent";
|
|
22
|
+
return "llm";
|
|
23
|
+
}
|
|
24
|
+
function resolveProfileName(entry, mode, config) {
|
|
25
|
+
if (entry.profile)
|
|
26
|
+
return entry.profile;
|
|
27
|
+
if (mode === "llm") {
|
|
28
|
+
const defaultName = config.defaults?.llm;
|
|
29
|
+
if (defaultName)
|
|
30
|
+
return defaultName;
|
|
31
|
+
throw new ConfigError(`No LLM profile configured. Set defaults.llm in config or specify profile in the process entry.`, "LLM_NOT_CONFIGURED", "Run `akm setup` or define a profile under `profiles.llm` and set `defaults.llm`.");
|
|
32
|
+
}
|
|
33
|
+
const defaultName = config.defaults?.agent;
|
|
34
|
+
if (defaultName)
|
|
35
|
+
return defaultName;
|
|
36
|
+
throw new ConfigError(`No agent profile configured. Set defaults.agent in config or specify profile in the process entry.`, "INVALID_CONFIG_FILE", "Run `akm setup` to configure an agent profile, or add one under `profiles.agent`.");
|
|
37
|
+
}
|
|
38
|
+
function buildLlmRunnerSpec(profileName, timeoutMs, config) {
|
|
39
|
+
const profile = config.profiles?.llm?.[profileName];
|
|
40
|
+
if (!profile) {
|
|
41
|
+
throw new ConfigError(`LLM profile "${profileName}" not found in profiles.llm.`, "LLM_NOT_CONFIGURED", `Available profiles: ${Object.keys(config.profiles?.llm ?? {}).join(", ") || "none"}. Run \`akm setup\` to configure.`);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
kind: "llm",
|
|
45
|
+
connection: profile,
|
|
46
|
+
...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function buildAgentRunnerSpec(kind, profileName, timeoutMs, config) {
|
|
50
|
+
const profileConfig = config.profiles?.agent?.[profileName];
|
|
51
|
+
if (!profileConfig) {
|
|
52
|
+
throw new ConfigError(`Agent profile "${profileName}" not found in profiles.agent.`, "INVALID_CONFIG_FILE", `Available profiles: ${Object.keys(config.profiles?.agent ?? {}).join(", ") || "none"}. Run \`akm setup\` to configure.`);
|
|
53
|
+
}
|
|
54
|
+
// Validate mode/platform consistency
|
|
55
|
+
if (kind === "sdk" && profileConfig.platform !== "opencode-sdk") {
|
|
56
|
+
throw new ConfigError(`Mode "sdk" requires platform "opencode-sdk", but profiles.agent["${profileName}"].platform is "${profileConfig.platform}".`, "INVALID_CONFIG_FILE");
|
|
57
|
+
}
|
|
58
|
+
if (kind === "agent" && profileConfig.platform === "opencode-sdk") {
|
|
59
|
+
throw new ConfigError(`Mode "agent" requires platform "opencode" or "claude", but profiles.agent["${profileName}"].platform is "opencode-sdk".`, "INVALID_CONFIG_FILE");
|
|
60
|
+
}
|
|
61
|
+
const agentProfile = {
|
|
62
|
+
name: profileName,
|
|
63
|
+
bin: profileConfig.bin ?? profileName,
|
|
64
|
+
args: profileConfig.args ?? [],
|
|
65
|
+
stdio: "captured",
|
|
66
|
+
envPassthrough: [],
|
|
67
|
+
parseOutput: "text",
|
|
68
|
+
...(profileConfig.model ? { model: profileConfig.model } : {}),
|
|
69
|
+
...(profileConfig.workspace ? { workspace: profileConfig.workspace } : {}),
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
kind,
|
|
73
|
+
profile: agentProfile,
|
|
74
|
+
...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Resolve the runner used for "validation" passes on the `improve` section
|
|
79
|
+
* (Advantage D3 / Phase 4B — third model tier).
|
|
80
|
+
*
|
|
81
|
+
* Look-up order:
|
|
82
|
+
* 1. `profiles.improve.default.processes.validation` (preferred — lets users
|
|
83
|
+
* wire a lower-cost classifier model for staleness detection, confidence
|
|
84
|
+
* scoring, and lesson classification).
|
|
85
|
+
* 2. `defaults.llm` as a final fallback so callers always get a usable
|
|
86
|
+
* runner when any LLM is configured.
|
|
87
|
+
*
|
|
88
|
+
* Returns `null` when neither is configured (callers may then skip the
|
|
89
|
+
* validation pass rather than throwing).
|
|
90
|
+
*/
|
|
91
|
+
export function resolveValidationRunner(config) {
|
|
92
|
+
const validation = config.profiles?.improve?.default?.processes?.validation;
|
|
93
|
+
if (validation && validation.enabled !== false && (validation.profile || validation.mode)) {
|
|
94
|
+
try {
|
|
95
|
+
const spec = resolveImproveProcessRunnerFromProfile(validation, config);
|
|
96
|
+
if (spec)
|
|
97
|
+
return spec;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Fall through to defaults.llm below.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const defaultLlm = config.defaults?.llm;
|
|
104
|
+
if (defaultLlm) {
|
|
105
|
+
try {
|
|
106
|
+
return buildLlmRunnerSpec(defaultLlm, undefined, config);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
export function resolveRunner(mode, profileName, config) {
|
|
115
|
+
if (mode === "llm")
|
|
116
|
+
return buildLlmRunnerSpec(profileName, undefined, config);
|
|
117
|
+
return buildAgentRunnerSpec(mode, profileName, undefined, config);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolve a RunnerSpec from an improve-profile process entry. Returns `null`
|
|
121
|
+
* when the entry is absent or provides no overrides — callers should fall
|
|
122
|
+
* back to the default per-process runner resolution path.
|
|
123
|
+
*/
|
|
124
|
+
export function resolveImproveProcessRunnerFromProfile(processConfig, config) {
|
|
125
|
+
if (!processConfig)
|
|
126
|
+
return null;
|
|
127
|
+
const { mode: explicitMode, profile, timeoutMs } = processConfig;
|
|
128
|
+
if (!explicitMode && !profile)
|
|
129
|
+
return null;
|
|
130
|
+
const mode = explicitMode ?? resolveEffectiveMode(processConfig, profile, config);
|
|
131
|
+
const profileName = profile ?? resolveProfileName(processConfig, mode, config);
|
|
132
|
+
if (mode === "llm") {
|
|
133
|
+
return buildLlmRunnerSpec(profileName, timeoutMs, config);
|
|
134
|
+
}
|
|
135
|
+
if (mode === "agent" || mode === "sdk") {
|
|
136
|
+
return buildAgentRunnerSpec(mode, profileName, timeoutMs, config);
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Convenience accessor for callers that previously read
|
|
142
|
+
* `getProcessOptions("index", "staleness_detection", config).thresholdDays`.
|
|
143
|
+
* After the 0.8.0 migration, those values live on first-class config keys —
|
|
144
|
+
* see `config.index?.stalenessDetection?.thresholdDays` etc.
|
|
145
|
+
*/
|
|
146
|
+
export function getStalenessDetectionThresholdDays(config) {
|
|
147
|
+
return config.index?.stalenessDetection?.thresholdDays;
|
|
148
|
+
}
|
|
149
|
+
// Re-export `isProcessEnabled` from feature-gate.ts so callers that previously
|
|
150
|
+
// imported it from runner.ts continue to work.
|
|
151
|
+
export { isProcessEnabled } from "../../llm/feature-gate";
|