akm-cli 0.9.0-rc.13 → 0.9.0-rc.15
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 +162 -23
- package/dist/akm-migrate +3 -1
- package/dist/assets/hints/cli-hints-full.md +3 -4
- package/dist/assets/hints/cli-hints-short.md +5 -5
- package/dist/assets/workflows/workflow-template.md +4 -3
- package/dist/cli/invocation.js +3 -2
- package/dist/cli/retired-commands.js +5 -0
- package/dist/cli/unknown-flags.js +226 -0
- package/dist/cli.js +19 -35
- package/dist/commands/agent/agent-dispatch.js +7 -3
- package/dist/commands/agent/contribute-cli.js +15 -3
- package/dist/commands/feedback-cli.js +6 -3
- package/dist/commands/health/checks.js +6 -3
- package/dist/commands/improve/collapse-detector.js +2 -3
- package/dist/commands/improve/reflect.js +5 -3
- package/dist/commands/lint/base-linter.js +4 -16
- package/dist/commands/lint/index.js +13 -13
- package/dist/commands/log.js +6 -1
- package/dist/commands/migration-tool.js +4 -5
- package/dist/commands/observability-cli.js +1 -1
- package/dist/commands/proposal/propose.js +5 -3
- package/dist/commands/proposal/repository.js +5 -5
- package/dist/commands/read/knowledge.js +2 -0
- package/dist/commands/registry-cli.js +5 -3
- package/dist/commands/sources/add-cli.js +6 -6
- package/dist/commands/sources/init.js +1 -1
- package/dist/commands/sources/self-update.js +30 -7
- package/dist/commands/sources/source-add.js +17 -2
- package/dist/commands/tasks/tasks.js +10 -10
- package/dist/commands/workflow-cli.js +133 -259
- package/dist/core/adapter/adapters/akm-lint.js +17 -13
- package/dist/core/adapter/adapters/akm-task-adapter.js +14 -11
- package/dist/core/asset/akm-markdown.js +41 -8
- package/dist/core/asset/frontmatter.js +22 -0
- package/dist/core/asset/resolve-ref.js +23 -3
- package/dist/core/common.js +45 -2
- package/dist/core/config/config-schema.js +8 -0
- package/dist/core/config/schema/experimental.js +0 -17
- package/dist/core/config/schema/sources-bundles.js +11 -0
- package/dist/core/config/schema/workflow.js +3 -3
- package/dist/core/errors.js +5 -1
- package/dist/core/logs-db.js +2 -1
- package/dist/core/parse.js +4 -1
- package/dist/core/state/migrations.js +11 -14
- package/dist/core/state-db.js +4 -6
- package/dist/core/subprocess.js +6 -4
- package/dist/core/type-presentation.js +1 -1
- package/dist/indexer/indexer.js +16 -1
- package/dist/indexer/search/search-source.js +1 -1
- package/dist/indexer/walk/matchers.js +3 -1
- package/dist/integrations/agent/config.js +2 -2
- package/dist/integrations/agent/detect.js +49 -19
- package/dist/integrations/agent/engine-fallback.js +76 -0
- package/dist/integrations/agent/profiles.js +14 -0
- package/dist/integrations/agent/spawn.js +1 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
- package/dist/integrations/lockfile.js +11 -56
- package/dist/output/shapes/passthrough.js +0 -6
- package/dist/output/text/command-format.js +3 -3
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/show-directives.js +4 -6
- package/dist/output/text/workflow-format.js +15 -181
- package/dist/output/text/workflow.js +1 -6
- package/dist/scripts/akm-migrate-node.js +58666 -0
- package/dist/scripts/akm-migrate.js +33976 -11498
- package/dist/setup/detect.js +42 -15
- package/dist/setup/setup.js +1 -1
- package/dist/sources/providers/git-stash.js +4 -2
- package/dist/sources/providers/website.js +5 -0
- package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
- package/dist/sources/snapshot-fetchers/content-extract.js +370 -0
- package/dist/sources/snapshot-fetchers/fetcher-util.js +40 -0
- package/dist/sources/snapshot-fetchers/host-guard.js +199 -0
- package/dist/sources/snapshot-fetchers/registry.js +10 -1
- package/dist/sources/snapshot-fetchers/robots.js +348 -0
- package/dist/sources/snapshot-fetchers/rss.js +279 -0
- package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
- package/dist/sources/snapshot-fetchers/website-ingest.js +488 -257
- package/dist/sources/snapshot-fetchers/x.js +193 -0
- package/dist/storage/engines/sqlite-migrations.js +22 -107
- package/dist/storage/repositories/workflow-runs-repository.js +9 -23
- package/dist/tasks/runner.js +43 -19
- package/dist/tasks/schema.js +24 -1
- package/dist/tasks/validator.js +6 -2
- package/dist/workflows/exec/frozen-judge.js +28 -2
- package/dist/workflows/exec/native-executor.js +44 -28
- package/dist/workflows/exec/param-secrets.js +9 -9
- package/dist/workflows/exec/run-workflow.js +122 -60
- package/dist/workflows/exec/step-work.js +64 -175
- package/dist/workflows/exec/unit-dispatch.js +4 -0
- package/dist/workflows/ir/compile.js +2 -2
- package/dist/workflows/ir/freeze.js +29 -9
- package/dist/workflows/ir/params.js +135 -11
- package/dist/workflows/ir/plan-hash.js +1 -1
- package/dist/workflows/ir/schema.js +6 -2
- package/dist/workflows/renderer.js +2 -2
- package/dist/workflows/runtime/checkin.js +3 -3
- package/dist/workflows/runtime/runs.js +72 -92
- package/dist/workflows/runtime/unit-checkin.js +1 -1
- package/dist/workflows/runtime/unit-phases.js +2 -2
- package/dist/workflows/validate-summary.js +30 -14
- package/docs/migration/release-notes/0.9.0.md +31 -4
- package/docs/migration/v0.8-to-v0.9.md +70 -100
- package/docs/reference/data-and-telemetry.md +5 -5
- package/package.json +7 -3
- package/schemas/akm-config.json +24 -6
- package/schemas/akm-workflow.json +1 -1
- package/dist/workflows/cli.js +0 -33
- package/dist/workflows/exec/brief.js +0 -450
- package/dist/workflows/exec/report.js +0 -1460
- package/dist/workflows/exec/workflow-engine-gate.js +0 -67
package/dist/cli.js
CHANGED
|
@@ -60,6 +60,7 @@ import { defineCommand, parseArgs, renderUsage, runCommand, showUsage } from "ci
|
|
|
60
60
|
import { findCittyTopLevelCommand, findCittyTopLevelCommandIndex, getParsedInvocation, parseAllFlagValues, resolveHelpMigrateVersionArg, setParsedInvocation, } from "./cli/invocation.js";
|
|
61
61
|
import { retiredCommandHint } from "./cli/retired-commands.js";
|
|
62
62
|
import { defineGroupCommand, EXIT_CODES, emitJsonError, GLOBAL_OUTPUT_ARGS, output, runWithJsonErrors, } from "./cli/shared.js";
|
|
63
|
+
import { assertKnownFlags, closestMatch } from "./cli/unknown-flags.js";
|
|
63
64
|
import { agentCommand, lintCommand } from "./commands/agent/contribute-cli.js";
|
|
64
65
|
import { generateBashCompletions, installBashCompletions } from "./commands/completions.js";
|
|
65
66
|
import { configCommand } from "./commands/config-cli.js";
|
|
@@ -236,7 +237,13 @@ const setupCommand = defineCommand({
|
|
|
236
237
|
printSetupTtyHint(result);
|
|
237
238
|
}
|
|
238
239
|
else {
|
|
239
|
-
// Interactive wizard
|
|
240
|
+
// Interactive wizard. Guard the TTY first: the wizard's prompts read
|
|
241
|
+
// from stdin, so a piped/redirected/CI invocation would render the
|
|
242
|
+
// first prompt and then block forever instead of failing — and `akm
|
|
243
|
+
// setup` is the first command users automate.
|
|
244
|
+
if (process.stdin.isTTY !== true) {
|
|
245
|
+
throw new UsageError("Interactive setup requires a TTY. Pass --yes to accept defaults, or --config <json> / --from <file> to configure non-interactively.", "NON_INTERACTIVE_REQUIRES_YES");
|
|
246
|
+
}
|
|
240
247
|
const { runSetupWizard } = await import("./setup/setup.js");
|
|
241
248
|
await runSetupWizard({ dir: args.dir, noInit });
|
|
242
249
|
}
|
|
@@ -818,40 +825,9 @@ function findUnknownCommandAttempt(rawArgs) {
|
|
|
818
825
|
args = args.slice(idx + 1);
|
|
819
826
|
}
|
|
820
827
|
}
|
|
821
|
-
/**
|
|
822
|
-
* Standard edit-distance DP, single-row-at-a-time (sizes here are always
|
|
823
|
-
* short command-name strings). Builds each row left-to-right, appending as
|
|
824
|
-
* it goes, so every index read below is already-populated — the `?? 0`
|
|
825
|
-
* fallbacks only satisfy `noUncheckedIndexedAccess`, they never fire.
|
|
826
|
-
*/
|
|
827
|
-
function levenshteinDistance(a, b) {
|
|
828
|
-
let previousRow = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
829
|
-
for (let i = 1; i <= a.length; i++) {
|
|
830
|
-
const currentRow = [i];
|
|
831
|
-
for (let j = 1; j <= b.length; j++) {
|
|
832
|
-
const substitutionCost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
833
|
-
const deletion = (previousRow[j] ?? 0) + 1;
|
|
834
|
-
const insertion = (currentRow[j - 1] ?? 0) + 1;
|
|
835
|
-
const substitution = (previousRow[j - 1] ?? 0) + substitutionCost;
|
|
836
|
-
currentRow.push(Math.min(deletion, insertion, substitution));
|
|
837
|
-
}
|
|
838
|
-
previousRow = currentRow;
|
|
839
|
-
}
|
|
840
|
-
return previousRow[b.length] ?? 0;
|
|
841
|
-
}
|
|
842
|
-
/** Closest candidate within a length-scaled distance threshold, or undefined when nothing is close enough to be worth suggesting. */
|
|
828
|
+
/** Closest command within a length-scaled distance threshold (shared DP in cli/unknown-flags.ts). */
|
|
843
829
|
function closestCommandMatch(attempted, candidates) {
|
|
844
|
-
|
|
845
|
-
let bestDistance = Number.POSITIVE_INFINITY;
|
|
846
|
-
for (const candidate of candidates) {
|
|
847
|
-
const distance = levenshteinDistance(attempted, candidate);
|
|
848
|
-
if (distance < bestDistance) {
|
|
849
|
-
bestDistance = distance;
|
|
850
|
-
best = candidate;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
const threshold = Math.max(2, Math.ceil(attempted.length / 2));
|
|
854
|
-
return best !== undefined && bestDistance <= threshold ? best : undefined;
|
|
830
|
+
return closestMatch(attempted, candidates, Math.max(2, Math.ceil(attempted.length / 2)));
|
|
855
831
|
}
|
|
856
832
|
const CLI_HELP_POINTER = "Run `akm --help` for usage.";
|
|
857
833
|
/**
|
|
@@ -898,6 +874,10 @@ function toUsageErrorFromCliError(error, rawArgs) {
|
|
|
898
874
|
const message = error.message.replace(/\x1b\[[0-9;]*m/g, "");
|
|
899
875
|
return new UsageError(message, code, hint);
|
|
900
876
|
}
|
|
877
|
+
/** Normalize citty's private CLIError for the real entrypoint and in-process test harness. */
|
|
878
|
+
export function normalizeCittyCliError(error, rawArgs) {
|
|
879
|
+
return isCittyCliError(error) ? toUsageErrorFromCliError(error, rawArgs) : error;
|
|
880
|
+
}
|
|
901
881
|
/**
|
|
902
882
|
* The CLI's real startup sequence, extracted into a function so error paths
|
|
903
883
|
* can `return` early — top-level `return` is a syntax error in an ES module,
|
|
@@ -1016,6 +996,10 @@ async function runCli() {
|
|
|
1016
996
|
console.log(pkgVersion);
|
|
1017
997
|
return;
|
|
1018
998
|
}
|
|
999
|
+
// citty's parseArgs (strict: false) silently ignores undeclared flags, so
|
|
1000
|
+
// a typo used to run the command with defaults and exit 0. Checked here, after --help and
|
|
1001
|
+
// --version, so those keep working on any command.
|
|
1002
|
+
assertKnownFlags(main, rawArgs);
|
|
1019
1003
|
await runCommand(main, { rawArgs });
|
|
1020
1004
|
}
|
|
1021
1005
|
catch (error) {
|
|
@@ -1027,7 +1011,7 @@ async function runCli() {
|
|
|
1027
1011
|
// + raw `console.error(message)` — a one-line diagnosis plus a short
|
|
1028
1012
|
// hint (with a did-you-mean suggestion when applicable), not a ~46KB
|
|
1029
1013
|
// usage dump.
|
|
1030
|
-
emitJsonError(
|
|
1014
|
+
emitJsonError(normalizeCittyCliError(error, rawArgs));
|
|
1031
1015
|
return;
|
|
1032
1016
|
}
|
|
1033
1017
|
// Anything else escaping here is a genuinely unexpected failure outside
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import fs from "node:fs";
|
|
17
17
|
import { parseRefInput } from "../../core/asset/resolve-ref.js";
|
|
18
18
|
import { NotFoundError, UsageError } from "../../core/errors.js";
|
|
19
|
+
import { NO_ENGINE_MESSAGE_SUFFIX, NO_ENGINE_REMEDY, withEngineFallback, } from "../../integrations/agent/engine-fallback.js";
|
|
19
20
|
import { resolveEngine } from "../../integrations/agent/engine-resolution.js";
|
|
20
21
|
import { executeRunner } from "../../integrations/agent/runner-dispatch.js";
|
|
21
22
|
/**
|
|
@@ -60,10 +61,13 @@ async function resolveAssetBody(ref) {
|
|
|
60
61
|
export async function akmAgentDispatch(options) {
|
|
61
62
|
if (!options.agentConfig)
|
|
62
63
|
throw new UsageError("agent requires a valid config with an agent engine.", "MISSING_REQUIRED_ARGUMENT");
|
|
63
|
-
|
|
64
|
+
// Same implicit opencode-sdk fallback the workflow and task surfaces apply,
|
|
65
|
+
// so an engine-less install is usable everywhere or nowhere — not a mix.
|
|
66
|
+
const { config: agentConfig } = withEngineFallback(options.agentConfig);
|
|
67
|
+
const engineName = options.engine ?? agentConfig.defaults?.engine;
|
|
64
68
|
if (!engineName)
|
|
65
|
-
throw new UsageError(
|
|
66
|
-
const runner = resolveEngine(engineName,
|
|
69
|
+
throw new UsageError(`agent ${NO_ENGINE_MESSAGE_SUFFIX} ${NO_ENGINE_REMEDY}`, "MISSING_REQUIRED_ARGUMENT");
|
|
70
|
+
const runner = resolveEngine(engineName, agentConfig);
|
|
67
71
|
if (runner.kind === "llm") {
|
|
68
72
|
throw new UsageError(`Engine "${engineName}" is an LLM engine; akm agent requires an agent engine.`, "INVALID_FLAG_VALUE");
|
|
69
73
|
}
|
|
@@ -25,6 +25,7 @@ import { getStringArg, parsePositiveIntFlag } from "../../cli/parse-args.js";
|
|
|
25
25
|
import { EXIT_CODES, GLOBAL_OUTPUT_ARGS, output, runWithJsonErrors } from "../../cli/shared.js";
|
|
26
26
|
import { loadConfig } from "../../core/config/config.js";
|
|
27
27
|
import { resolveUsageEventSource } from "../../indexer/usage/usage-events.js";
|
|
28
|
+
import { getHyphenatedBoolean } from "../../output/context.js";
|
|
28
29
|
import { akmLint } from "../lint/index.js";
|
|
29
30
|
import { akmAgentDispatch } from "./agent-dispatch.js";
|
|
30
31
|
const EXIT_GENERAL = EXIT_CODES.GENERAL;
|
|
@@ -132,8 +133,19 @@ export const lintCommand = defineCommand({
|
|
|
132
133
|
...GLOBAL_OUTPUT_ARGS,
|
|
133
134
|
fix: {
|
|
134
135
|
type: "boolean",
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
description: "Apply auto-fixes in place",
|
|
137
|
+
default: false,
|
|
138
|
+
},
|
|
139
|
+
// Declared as its own arg rather than `alias: "auto-fix"`: citty's alias
|
|
140
|
+
// handling (node:util parseArgs `short` options) only supports
|
|
141
|
+
// SINGLE-character aliases. A
|
|
142
|
+
// multi-char alias rendered in help as `-auto-fix` (one dash) and parsed
|
|
143
|
+
// as a pile of junk single-char flags, so BOTH advertised spellings —
|
|
144
|
+
// `-auto-fix` and `--auto-fix` — silently ran a plain lint while claiming
|
|
145
|
+
// to fix. Two real boolean args, OR'd at the call site, work and render.
|
|
146
|
+
"auto-fix": {
|
|
147
|
+
type: "boolean",
|
|
148
|
+
description: "Apply auto-fixes in place (same as --fix)",
|
|
137
149
|
default: false,
|
|
138
150
|
},
|
|
139
151
|
dir: { type: "string", description: "Override bundle root directory (default: from config)" },
|
|
@@ -151,7 +163,7 @@ export const lintCommand = defineCommand({
|
|
|
151
163
|
async run({ args }) {
|
|
152
164
|
await runWithJsonErrors(async () => {
|
|
153
165
|
const result = akmLint({
|
|
154
|
-
fix: args.fix
|
|
166
|
+
fix: args.fix === true || getHyphenatedBoolean(args, "auto-fix"),
|
|
155
167
|
dir: getStringArg(args, "dir"),
|
|
156
168
|
typeFilter: getStringArg(args, "type"),
|
|
157
169
|
});
|
|
@@ -9,7 +9,7 @@ import { parseFrontmatter, parseFrontmatterBlock } from "../core/asset/frontmatt
|
|
|
9
9
|
import { conceptIdFromTypeName, parseRefInput } from "../core/asset/resolve-ref.js";
|
|
10
10
|
import { isWithin, writeFileAtomic } from "../core/common.js";
|
|
11
11
|
import { FEEDBACK_FAILURE_MODES, loadConfig } from "../core/config/config.js";
|
|
12
|
-
import { UsageError } from "../core/errors.js";
|
|
12
|
+
import { NotFoundError, UsageError } from "../core/errors.js";
|
|
13
13
|
import { appendEvent } from "../core/events.js";
|
|
14
14
|
import { resolveMutationTarget } from "../core/mutation-target.js";
|
|
15
15
|
import { getDbPath } from "../core/paths.js";
|
|
@@ -311,8 +311,11 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
311
311
|
const lookupRef = makeBundleRef(parsedRef.bundle, parsedRef.conceptId);
|
|
312
312
|
const entryId = findEntryIdByRef(db, lookupRef, requestedSource?.path);
|
|
313
313
|
if (entryId === undefined) {
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
// NotFoundError (exit 1), not UsageError (exit 2): the flags parsed
|
|
315
|
+
// fine, the asset just isn't there. The documented exit-code table
|
|
316
|
+
// reserves 1 for "requested resource missing", and scripts branch on it.
|
|
317
|
+
throw new NotFoundError(`Ref "${ref}" is not in the index. ` +
|
|
318
|
+
"Run 'akm search' to verify the asset exists, then 'akm index' if it was recently added.", "ASSET_NOT_FOUND");
|
|
316
319
|
}
|
|
317
320
|
// Persist the feedback signal into usage_events. Both positive and
|
|
318
321
|
// negative signals apply the EMA utility update immediately — no
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
5
|
import { loadConfig } from "../../core/config/config.js";
|
|
6
|
+
import { withEngineFallback } from "../../integrations/agent/engine-fallback.js";
|
|
7
7
|
import { resolveEngine } from "../../integrations/agent/engine-resolution.js";
|
|
8
8
|
import { resolveModel } from "../../integrations/agent/model-aliases.js";
|
|
9
9
|
import { resolveImprovePlan } from "../improve/improve-strategies.js";
|
|
@@ -28,7 +28,7 @@ function runConfiguredEngineProbe(checkName, engineName, config, deps) {
|
|
|
28
28
|
if (configuredEngine?.kind === "agent" && configuredEngine.platform === "opencode-sdk") {
|
|
29
29
|
let packageAvailable = false;
|
|
30
30
|
try {
|
|
31
|
-
const resolvePackage = deps.resolvePackage ?? ((name) =>
|
|
31
|
+
const resolvePackage = deps.resolvePackage ?? ((name) => import.meta.resolve(name));
|
|
32
32
|
resolvePackage("@opencode-ai/sdk");
|
|
33
33
|
packageAvailable = true;
|
|
34
34
|
}
|
|
@@ -170,7 +170,10 @@ function runConfiguredEngineProbe(checkName, engineName, config, deps) {
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
export function runDefaultEngineProbe(deps = {}) {
|
|
173
|
-
|
|
173
|
+
// Probe the effective view: an install with no `defaults.engine` but a usable
|
|
174
|
+
// opencode binary DOES have a working default, and reporting otherwise would
|
|
175
|
+
// contradict what `workflow run` / `task run` actually do.
|
|
176
|
+
const { config } = withEngineFallback(deps.loadConfig?.() ?? loadConfig(), deps.which);
|
|
174
177
|
return runConfiguredEngineProbe("default-engine", config.defaults?.engine, config, deps);
|
|
175
178
|
}
|
|
176
179
|
export function runDefaultLlmEngineProbe(deps = {}) {
|
|
@@ -15,9 +15,8 @@
|
|
|
15
15
|
* hardcoded 0 from the 0.9.0 confidence-gate deletion onward), so the alert
|
|
16
16
|
* could never fire. See the design doc's status note for the removal
|
|
17
17
|
* rationale. `improve_cycle_metrics.accepted_actions` stays in the schema and
|
|
18
|
-
* is written as 0
|
|
19
|
-
*
|
|
20
|
-
* it would fail those installs closed.
|
|
18
|
+
* is written as 0 because deployed `state.db` files already contain it and
|
|
19
|
+
* removing it would be a destructive schema change.
|
|
21
20
|
*
|
|
22
21
|
* Hard invariants: deterministic only (FTS BM25 + hashing — never an LLM,
|
|
23
22
|
* never an embedding model); bounded storage (< 2 KB per qualifying cycle,
|
|
@@ -37,6 +37,7 @@ import { redactSensitiveText } from "../../core/redaction.js";
|
|
|
37
37
|
import { resolveStandardsContext } from "../../core/standards/resolve-standards-context.js";
|
|
38
38
|
import { lookup } from "../../indexer/indexer.js";
|
|
39
39
|
import { DEFAULT_LLM_TIMEOUT_MS } from "../../integrations/agent/config.js";
|
|
40
|
+
import { NO_ENGINE_MESSAGE_SUFFIX, NO_ENGINE_REMEDY, withEngineFallback, } from "../../integrations/agent/engine-fallback.js";
|
|
40
41
|
import { resolveEngine } from "../../integrations/agent/engine-resolution.js";
|
|
41
42
|
import { buildReflectOutputRepairPrompt, buildReflectPrompt, extractDraftConfidence, parseAgentProposalPayload, } from "../../integrations/agent/prompts.js";
|
|
42
43
|
import { materializeLlmRunnerConnection, resolveImproveProcessRunner, runnerIsLlm, runnerSupportsFileWrite, } from "../../integrations/agent/runner.js";
|
|
@@ -1185,11 +1186,12 @@ function resolveReflectRunner(options) {
|
|
|
1185
1186
|
runnerSpec = processRunner;
|
|
1186
1187
|
}
|
|
1187
1188
|
else {
|
|
1188
|
-
const
|
|
1189
|
+
const { config: engineConfig } = withEngineFallback(config);
|
|
1190
|
+
const defaultEngine = engineConfig.defaults?.engine;
|
|
1189
1191
|
if (!defaultEngine) {
|
|
1190
|
-
throw new ConfigError(
|
|
1192
|
+
throw new ConfigError(`reflect ${NO_ENGINE_MESSAGE_SUFFIX} ${NO_ENGINE_REMEDY}`, "INVALID_CONFIG_FILE");
|
|
1191
1193
|
}
|
|
1192
|
-
runnerSpec = resolveEngine(defaultEngine,
|
|
1194
|
+
runnerSpec = resolveEngine(defaultEngine, engineConfig);
|
|
1193
1195
|
}
|
|
1194
1196
|
if (options.eventSource === "improve" && !runnerIsLlm(runnerSpec)) {
|
|
1195
1197
|
throw new ConfigError(`Unattended improve requires an LLM engine for reflect; engine "${runnerSpec.engine ?? options.engine ?? "unknown"}" is tool-capable.`, "INVALID_CONFIG_FILE", "Set defaults.llmEngine or improve.strategies.<name>.processes.reflect.engine to an LLM engine.");
|
|
@@ -35,15 +35,11 @@ import fs from "node:fs";
|
|
|
35
35
|
import path from "node:path";
|
|
36
36
|
import { assetPathForName, stashDirFor } from "../../core/asset/asset-placement.js";
|
|
37
37
|
import { BUNDLE_REF_RE } from "../../core/asset/asset-ref.js";
|
|
38
|
+
import { spliceFrontmatterLine } from "../../core/asset/frontmatter.js";
|
|
38
39
|
import { typeNameFromConceptId } from "../../core/asset/resolve-ref.js";
|
|
40
|
+
import { localDateStamp } from "../../core/common.js";
|
|
39
41
|
import { findFenceRegions } from "./markdown-insertion.js";
|
|
40
42
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
41
|
-
function formatDate(d) {
|
|
42
|
-
const y = d.getFullYear();
|
|
43
|
-
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
44
|
-
const day = String(d.getDate()).padStart(2, "0");
|
|
45
|
-
return `${y}-${m}-${day}`;
|
|
46
|
-
}
|
|
47
43
|
function checkUnquotedColon(frontmatterText) {
|
|
48
44
|
if (!frontmatterText)
|
|
49
45
|
return null;
|
|
@@ -86,15 +82,7 @@ function checkMissingUpdated(data, frontmatterText) {
|
|
|
86
82
|
return frontmatterText !== null && !("updated" in data);
|
|
87
83
|
}
|
|
88
84
|
function fixMissingUpdated(raw, mtime) {
|
|
89
|
-
|
|
90
|
-
const lines = raw.split(/\r?\n/);
|
|
91
|
-
if (lines[0]?.trim() !== "---")
|
|
92
|
-
return raw;
|
|
93
|
-
const closeIdx = lines.findIndex((l, i) => i > 0 && l.trim() === "---");
|
|
94
|
-
if (closeIdx === -1)
|
|
95
|
-
return raw;
|
|
96
|
-
lines.splice(closeIdx, 0, `updated: ${dateStr}`);
|
|
97
|
-
return lines.join("\n");
|
|
85
|
+
return spliceFrontmatterLine(raw, `updated: ${localDateStamp(mtime)}`) ?? raw;
|
|
98
86
|
}
|
|
99
87
|
// ── stale-path helpers ────────────────────────────────────────────────────────
|
|
100
88
|
function checkStalePath(body) {
|
|
@@ -529,7 +517,7 @@ export function runBaseChecks(ctx) {
|
|
|
529
517
|
issues.push({
|
|
530
518
|
file: ctx.relPath,
|
|
531
519
|
issue: "missing-updated",
|
|
532
|
-
detail: `stamped updated: ${
|
|
520
|
+
detail: `stamped updated: ${localDateStamp(mtime)}`,
|
|
533
521
|
fixed: true,
|
|
534
522
|
});
|
|
535
523
|
}
|
|
@@ -6,7 +6,9 @@ import path from "node:path";
|
|
|
6
6
|
import { parse as parseYaml } from "yaml";
|
|
7
7
|
import { factDiagnostics, matchWorkflowPlaceholder, memoryOrphanStubApplies, nameOrTypeDiagnostics, ORPHANED_STUB_DETAIL, taskDiagnostics, workflowStructureDiagnostics, } from "../../core/adapter/adapters/akm-lint.js";
|
|
8
8
|
import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
|
|
9
|
+
import { stashDirFor } from "../../core/asset/asset-placement.js";
|
|
9
10
|
import { parseFrontmatter, parseFrontmatterBlock } from "../../core/asset/frontmatter.js";
|
|
11
|
+
import { conceptIdForStashFile, displayRefForConceptId } from "../../core/asset/resolve-ref.js";
|
|
10
12
|
import { resolveStashDir } from "../../core/common.js";
|
|
11
13
|
import { loadConfig, primaryBundlePath } from "../../core/config/config.js";
|
|
12
14
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
@@ -321,23 +323,21 @@ export function akmLint(options = {}) {
|
|
|
321
323
|
// keys that are known to enable process-execution hijacking. Warn-only —
|
|
322
324
|
// findings go into `flagged`, never `fixed`.
|
|
323
325
|
const envRoots = [stashRoot, ...extraStashRoots];
|
|
326
|
+
const bundleIdByRoot = new Map(sources.map((source) => [path.resolve(source.path), source.registryId]));
|
|
324
327
|
for (const root of envRoots) {
|
|
325
|
-
|
|
326
|
-
// `
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const dir = path.join(root, scanSubdir);
|
|
328
|
+
const bundleId = bundleIdByRoot.get(path.resolve(root));
|
|
329
|
+
// `env` assets live under `env/`, whole-file `secret` assets under
|
|
330
|
+
// `secrets/`. `displayRefForConceptId` owns the short-default /
|
|
331
|
+
// qualified-secondary `Ref:` spelling `akm show` emits — the old
|
|
332
|
+
// hand-built `env:<base>` colon grammar is rejected by the 0.9.0 ref
|
|
333
|
+
// parser, which dead-ended a user copying the ref off a security finding.
|
|
334
|
+
for (const assetType of ["env", "secret"]) {
|
|
335
|
+
const dir = path.join(root, stashDirFor(assetType));
|
|
334
336
|
if (!fs.existsSync(dir))
|
|
335
337
|
continue;
|
|
336
338
|
for (const envPath of collectEnvFiles(dir)) {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
// basename so it doesn't collide with `default.env` → refPrefix:default.
|
|
340
|
-
const ref = baseName === "" ? `${refPrefix}:.env` : `${refPrefix}:${baseName}`;
|
|
339
|
+
const conceptId = conceptIdForStashFile(assetType, root, envPath);
|
|
340
|
+
const ref = displayRefForConceptId(conceptId, bundleId, cfg.defaultBundle);
|
|
341
341
|
const relPath = path.relative(root, envPath);
|
|
342
342
|
for (const issue of checkEnvForDangerousKeys(envPath, relPath, ref)) {
|
|
343
343
|
flagged.push(issue);
|
package/dist/commands/log.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* other caller and was removed with it.
|
|
17
17
|
*/
|
|
18
18
|
import { makeBundleRef, parseBundleRef } from "../core/asset/asset-ref.js";
|
|
19
|
+
import { loadConfig } from "../core/config/config.js";
|
|
19
20
|
import { UsageError } from "../core/errors.js";
|
|
20
21
|
import { readEvents } from "../core/events.js";
|
|
21
22
|
import { parseSinceToIso } from "../core/time.js";
|
|
@@ -49,7 +50,11 @@ function validateRef(ref) {
|
|
|
49
50
|
throw new UsageError("--ref cannot be empty.", "INVALID_FLAG_VALUE");
|
|
50
51
|
}
|
|
51
52
|
const parsed = parseBundleRef(trimmed);
|
|
52
|
-
|
|
53
|
+
// Deliberately shallow qualification: a bare conceptId is assumed to mean
|
|
54
|
+
// the default bundle, without the full priority-walk `resolveRef` performs —
|
|
55
|
+
// event refs are recorded fully qualified, so the default is the only
|
|
56
|
+
// spelling a short --ref can usefully mean here.
|
|
57
|
+
return makeBundleRef(parsed.bundle ?? loadConfig().defaultBundle, parsed.conceptId);
|
|
53
58
|
}
|
|
54
59
|
function validateRunId(run) {
|
|
55
60
|
if (run === undefined)
|
|
@@ -7,8 +7,8 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { NotFoundError } from "../core/errors.js";
|
|
8
8
|
function migrationEntryPoint() {
|
|
9
9
|
const candidates = [
|
|
10
|
-
fileURLToPath(new URL("
|
|
11
|
-
fileURLToPath(new URL("
|
|
10
|
+
fileURLToPath(new URL(process.versions.bun ? "../scripts/akm-migrate.js" : "../scripts/akm-migrate-node.js", import.meta.url)),
|
|
11
|
+
...(process.versions.bun ? [fileURLToPath(new URL("../../scripts/akm-migrate.ts", import.meta.url))] : []),
|
|
12
12
|
];
|
|
13
13
|
return candidates.find((candidate) => fs.existsSync(candidate));
|
|
14
14
|
}
|
|
@@ -27,14 +27,13 @@ export async function runMigrationTool(args) {
|
|
|
27
27
|
// `scripts/akm-standalone.ts`, which embeds the migrator and dispatches to
|
|
28
28
|
// it when `AKM_MIGRATE_ENTRY=1` — so re-exec ourselves with the marker.
|
|
29
29
|
// (src must not import scripts/: the dist build's tsc has `rootDir: src`.)
|
|
30
|
-
const
|
|
31
|
-
const result = spawnSync(runtime, entry ? [entry, ...args] : [...args], {
|
|
30
|
+
const result = spawnSync(process.execPath, entry ? [entry, ...args] : [...args], {
|
|
32
31
|
encoding: "utf8",
|
|
33
32
|
env: entry ? process.env : { ...process.env, AKM_MIGRATE_ENTRY: "1" },
|
|
34
33
|
maxBuffer: 16 * 1024 * 1024,
|
|
35
34
|
});
|
|
36
35
|
if (result.error) {
|
|
37
|
-
throw new NotFoundError(`Cannot start the standalone akm-migrate tool: ${result.error.message}`, "FILE_NOT_FOUND", "
|
|
36
|
+
throw new NotFoundError(`Cannot start the standalone akm-migrate tool: ${result.error.message}`, "FILE_NOT_FOUND", "Reinstall akm-cli, or use a runtime-free standalone release binary.");
|
|
38
37
|
}
|
|
39
38
|
if (result.stdout)
|
|
40
39
|
process.stdout.write(result.stdout);
|
|
@@ -72,7 +72,7 @@ export const logCommand = defineJsonCommand({
|
|
|
72
72
|
ref: { type: "string", description: "Filter by asset ref ([bundle//]conceptId)" },
|
|
73
73
|
run: {
|
|
74
74
|
type: "string",
|
|
75
|
-
description: "Filter to a workflow run's events (metadata.runId), e.g. the id from `akm workflow
|
|
75
|
+
description: "Filter to a workflow run's events (metadata.runId), e.g. the id from `akm workflow run`",
|
|
76
76
|
},
|
|
77
77
|
"exclude-tags": {
|
|
78
78
|
type: "string",
|
|
@@ -21,6 +21,7 @@ import { appendEvent } from "../../core/events.js";
|
|
|
21
21
|
import { redactSensitiveText } from "../../core/redaction.js";
|
|
22
22
|
import { resolveStandardsContext } from "../../core/standards/resolve-standards-context.js";
|
|
23
23
|
import { deriveEntryProvenance } from "../../indexer/installations.js";
|
|
24
|
+
import { NO_ENGINE_MESSAGE_SUFFIX, NO_ENGINE_REMEDY, withEngineFallback, } from "../../integrations/agent/engine-fallback.js";
|
|
24
25
|
import { resolveEngine } from "../../integrations/agent/engine-resolution.js";
|
|
25
26
|
import { buildProposePrompt, parseAgentProposalPayload } from "../../integrations/agent/prompts.js";
|
|
26
27
|
import { collectDispatchSensitiveValues, executeRunner } from "../../integrations/agent/runner-dispatch.js";
|
|
@@ -83,10 +84,11 @@ export async function akmPropose(options) {
|
|
|
83
84
|
const config = options.agentConfig ?? (await import("../../core/config/config.js")).loadConfig();
|
|
84
85
|
const target = resolveProposalQueueTarget(stash, config);
|
|
85
86
|
emitProposeInvoked(target.source, options);
|
|
86
|
-
const
|
|
87
|
+
const { config: engineConfig } = withEngineFallback(config);
|
|
88
|
+
const engineName = options.engine ?? engineConfig.defaults?.engine;
|
|
87
89
|
if (!engineName)
|
|
88
|
-
throw new ConfigError(
|
|
89
|
-
const runner = resolveEngine(engineName,
|
|
90
|
+
throw new ConfigError(`propose ${NO_ENGINE_MESSAGE_SUFFIX} ${NO_ENGINE_REMEDY}`, "INVALID_CONFIG_FILE");
|
|
91
|
+
const runner = resolveEngine(engineName, engineConfig);
|
|
90
92
|
const profile = runner.kind === "llm" ? undefined : runner.profile;
|
|
91
93
|
// 3. Build prompt.
|
|
92
94
|
// Synthesize a temp draft path so opencode can write the asset content
|
|
@@ -168,7 +168,7 @@ function persistProposalUpdate(db, proposal, stashDir) {
|
|
|
168
168
|
.prepare("SELECT metadata_json FROM proposals WHERE id = ? AND stash_dir = ?")
|
|
169
169
|
.get(proposal.id, stashDir);
|
|
170
170
|
if (!row)
|
|
171
|
-
throw new NotFoundError(`Proposal "${proposal.id}" not found.`, "
|
|
171
|
+
throw new NotFoundError(`Proposal "${proposal.id}" not found.`, "PROPOSAL_NOT_FOUND");
|
|
172
172
|
const metadata = JSON.parse(row.metadata_json);
|
|
173
173
|
for (const field of [
|
|
174
174
|
"sourceRun",
|
|
@@ -572,7 +572,7 @@ export function getProposal(stashDir, id, ctx) {
|
|
|
572
572
|
function requireProposal(db, stashDir, id) {
|
|
573
573
|
const proposal = getStateProposal(db, id, stashDir);
|
|
574
574
|
if (!proposal) {
|
|
575
|
-
throw new NotFoundError(`Proposal "${id}" not found.`, "
|
|
575
|
+
throw new NotFoundError(`Proposal "${id}" not found.`, "PROPOSAL_NOT_FOUND");
|
|
576
576
|
}
|
|
577
577
|
return proposal;
|
|
578
578
|
}
|
|
@@ -588,7 +588,7 @@ function requireProposal(db, stashDir, id) {
|
|
|
588
588
|
function requireProposalLenient(db, stashDir, id) {
|
|
589
589
|
const proposal = getStateProposalLenient(db, id, stashDir);
|
|
590
590
|
if (!proposal) {
|
|
591
|
-
throw new NotFoundError(`Proposal "${id}" not found.`, "
|
|
591
|
+
throw new NotFoundError(`Proposal "${id}" not found.`, "PROPOSAL_NOT_FOUND");
|
|
592
592
|
}
|
|
593
593
|
return proposal;
|
|
594
594
|
}
|
|
@@ -623,7 +623,7 @@ export function resolveProposalId(stashDir, idOrRef, ctx) {
|
|
|
623
623
|
const archived = byRecency(forConcept());
|
|
624
624
|
if (archived)
|
|
625
625
|
return archived;
|
|
626
|
-
throw new NotFoundError(`No proposal found for ref "${idOrRef}".`, "
|
|
626
|
+
throw new NotFoundError(`No proposal found for ref "${idOrRef}".`, "PROPOSAL_NOT_FOUND");
|
|
627
627
|
}
|
|
628
628
|
// 3. UUID prefix (pending queue only).
|
|
629
629
|
const prefixMatches = listStateProposalIdsByPrefix(db, stashDir, idOrRef);
|
|
@@ -632,7 +632,7 @@ export function resolveProposalId(stashDir, idOrRef, ctx) {
|
|
|
632
632
|
if (prefixMatches.length > 1) {
|
|
633
633
|
throw new UsageError(`Ambiguous prefix "${idOrRef}" — matches: ${prefixMatches.join(", ")}`, "INVALID_FLAG_VALUE");
|
|
634
634
|
}
|
|
635
|
-
throw new NotFoundError(`Proposal "${idOrRef}" not found.`, "
|
|
635
|
+
throw new NotFoundError(`Proposal "${idOrRef}" not found.`, "PROPOSAL_NOT_FOUND");
|
|
636
636
|
});
|
|
637
637
|
}
|
|
638
638
|
/**
|
|
@@ -27,6 +27,7 @@ import { commitWriteTargetBoundary, formatRefForMessage, recordWriteTargetPath,
|
|
|
27
27
|
import { indexWrittenAssets } from "../../indexer/index-written-assets.js";
|
|
28
28
|
import { deriveInstallations, slugForPath } from "../../indexer/installations.js";
|
|
29
29
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
30
|
+
import { storeSecretResolver } from "../../sources/snapshot-fetchers/secret-seam.js";
|
|
30
31
|
import { fetchWebsiteMarkdownSnapshot, shouldAllowPrivateWebsiteUrlForTests, } from "../../sources/snapshot-fetchers/website-ingest.js";
|
|
31
32
|
import { writeSupersededEdge } from "../improve/memory/memory-belief.js";
|
|
32
33
|
import { refToRelPath, resolveRefPathInStash } from "../lint/base-linter.js";
|
|
@@ -119,6 +120,7 @@ export async function readKnowledgeInput(source, options) {
|
|
|
119
120
|
return readKnowledgeContent(source);
|
|
120
121
|
const snapshot = await fetchWebsiteMarkdownSnapshot(source, {
|
|
121
122
|
stashDir: options?.stashDir,
|
|
123
|
+
resolveSecret: storeSecretResolver.resolveSecret,
|
|
122
124
|
allowPrivateHosts: options?.allowPrivateHosts ?? shouldAllowPrivateWebsiteUrlForTests(source),
|
|
123
125
|
});
|
|
124
126
|
return { content: snapshot.content, preferredName: snapshot.preferredName };
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { defineGroupCommand, defineJsonCommand, output } from "../cli/shared.js";
|
|
5
5
|
import { getEffectiveRegistries, loadUserConfig, mutateConfig } from "../core/config/config.js";
|
|
6
|
-
import { UsageError } from "../core/errors.js";
|
|
6
|
+
import { NotFoundError, UsageError } from "../core/errors.js";
|
|
7
7
|
import { warn } from "../core/warn.js";
|
|
8
8
|
export const registryCommand = defineGroupCommand({
|
|
9
9
|
meta: { name: "registry", description: "Manage bundle registries" },
|
|
@@ -82,8 +82,10 @@ export const registryCommand = defineGroupCommand({
|
|
|
82
82
|
const registries = [...(config.registries ?? [])];
|
|
83
83
|
const idx = registries.findIndex((r) => r.url === args.target || r.name === args.target);
|
|
84
84
|
if (idx === -1) {
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// Was a success envelope with `removed: false` and exit 0, so
|
|
86
|
+
// `akm registry remove typo && ...` proceeded as if it had removed
|
|
87
|
+
// something. A missing target is exit 1, like every other not-found.
|
|
88
|
+
throw new NotFoundError(`No registry matching "${args.target}" is configured.`, "SOURCE_NOT_FOUND", "Run `akm registry list` to see configured registries.");
|
|
87
89
|
}
|
|
88
90
|
const { confirmDestructive } = await import("../cli/confirm.js");
|
|
89
91
|
const confirmed = await confirmDestructive(`Remove registry "${args.target}"? This cannot be undone.`, {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import * as p from "../../cli/clack.js";
|
|
7
|
+
import { getStringArg, parsePositiveIntFlag } from "../../cli/parse-args.js";
|
|
7
8
|
import { defineJsonCommand, output } from "../../cli/shared.js";
|
|
8
9
|
import { decideDangerousKeyInstall } from "../../core/activation-policy.js";
|
|
9
10
|
import { UsageError } from "../../core/errors.js";
|
|
@@ -14,12 +15,11 @@ import { akmAdd } from "./source-add.js";
|
|
|
14
15
|
import { addStash } from "./source-manage.js";
|
|
15
16
|
// ── Shared website-options helper ──────────
|
|
16
17
|
export function buildWebsiteOptions(args) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return websiteOptions;
|
|
18
|
+
// getStringArg maps absent/blank to undefined; parsePositiveIntFlag maps
|
|
19
|
+
// undefined to undefined — so an unsupplied flag simply omits the key.
|
|
20
|
+
const maxPages = parsePositiveIntFlag(getStringArg(args, "max-pages"), "--max-pages");
|
|
21
|
+
const maxDepth = parsePositiveIntFlag(getStringArg(args, "max-depth"), "--max-depth");
|
|
22
|
+
return { ...(maxPages !== undefined ? { maxPages } : {}), ...(maxDepth !== undefined ? { maxDepth } : {}) };
|
|
23
23
|
}
|
|
24
24
|
// ── HTTP safety check ─────────────────────────────────────────────────────────
|
|
25
25
|
export function shouldWarnOnPlainHttp(ref) {
|
|
@@ -87,7 +87,7 @@ async function akmInitReal(options) {
|
|
|
87
87
|
for (const sub of stashDirNames()) {
|
|
88
88
|
const subDir = path.join(stashDir, sub);
|
|
89
89
|
if (!fs.existsSync(subDir)) {
|
|
90
|
-
fs.mkdirSync(subDir, { recursive: true });
|
|
90
|
+
fs.mkdirSync(subDir, { recursive: true, mode: sub === "env" || sub === "secrets" ? 0o700 : undefined });
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
// Ensure the default stash is a local git repo (no remote required)
|