akm-cli 0.7.5 → 0.8.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/.github/CHANGELOG.md +1 -1
- package/dist/cli/parse-args.js +43 -0
- package/dist/cli.js +804 -461
- package/dist/commands/agent-dispatch.js +102 -0
- package/dist/commands/agent-support.js +62 -0
- package/dist/commands/config-cli.js +68 -84
- package/dist/commands/consolidate.js +823 -0
- package/dist/commands/distill-promotion-policy.js +658 -0
- package/dist/commands/distill.js +244 -52
- package/dist/commands/eval-cases.js +40 -0
- package/dist/commands/events.js +2 -23
- package/dist/commands/graph.js +222 -0
- package/dist/commands/health.js +376 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +53 -0
- package/dist/commands/help/help-proposals.md +15 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +8 -0
- package/dist/commands/history.js +3 -30
- package/dist/commands/improve.js +1170 -0
- package/dist/commands/info.js +2 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/install-audit.js +5 -1
- package/dist/commands/installed-stashes.js +118 -138
- package/dist/commands/knowledge.js +133 -0
- package/dist/commands/lint/agent-linter.js +46 -0
- package/dist/commands/lint/base-linter.js +251 -0
- package/dist/commands/lint/command-linter.js +46 -0
- package/dist/commands/lint/default-linter.js +13 -0
- package/dist/commands/lint/index.js +107 -0
- package/dist/commands/lint/knowledge-linter.js +13 -0
- package/dist/commands/lint/memory-linter.js +58 -0
- package/dist/commands/lint/registry.js +33 -0
- package/dist/commands/lint/skill-linter.js +42 -0
- package/dist/commands/lint/task-linter.js +47 -0
- package/dist/commands/lint/types.js +1 -0
- package/dist/commands/lint/workflow-linter.js +53 -0
- package/dist/commands/lint.js +1 -0
- package/dist/commands/proposal.js +8 -7
- package/dist/commands/propose.js +78 -28
- package/dist/commands/reflect.js +143 -35
- package/dist/commands/registry-search.js +2 -2
- package/dist/commands/remember.js +54 -0
- package/dist/commands/schema-repair.js +130 -0
- package/dist/commands/search.js +21 -5
- package/dist/commands/show.js +121 -17
- package/dist/commands/source-add.js +10 -10
- package/dist/commands/source-manage.js +11 -19
- package/dist/commands/tasks.js +385 -0
- package/dist/commands/url-checker.js +39 -0
- package/dist/commands/vault.js +2 -23
- package/dist/core/action-contributors.js +25 -0
- package/dist/core/asset-registry.js +4 -16
- package/dist/core/asset-spec.js +10 -0
- package/dist/core/common.js +94 -0
- package/dist/core/concurrent.js +22 -0
- package/dist/core/config.js +222 -128
- package/dist/core/events.js +73 -126
- package/dist/core/frontmatter.js +3 -1
- package/dist/core/markdown.js +17 -0
- package/dist/core/memory-improve.js +678 -0
- package/dist/core/parse.js +155 -0
- package/dist/core/paths.js +101 -3
- package/dist/core/proposal-validators.js +61 -0
- package/dist/core/proposals.js +49 -38
- package/dist/core/state-db.js +775 -0
- package/dist/core/time.js +51 -0
- package/dist/core/warn.js +59 -1
- package/dist/indexer/db-search.js +52 -238
- package/dist/indexer/db.js +377 -1
- package/dist/indexer/ensure-index.js +61 -0
- package/dist/indexer/graph-boost.js +247 -94
- package/dist/indexer/graph-db.js +201 -0
- package/dist/indexer/graph-dedup.js +99 -0
- package/dist/indexer/graph-extraction.js +409 -76
- package/dist/indexer/index-context.js +10 -0
- package/dist/indexer/indexer.js +442 -290
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/match-contributors.js +141 -0
- package/dist/indexer/matchers.js +24 -190
- package/dist/indexer/memory-inference.js +63 -29
- package/dist/indexer/metadata-contributors.js +26 -0
- package/dist/indexer/metadata.js +188 -175
- package/dist/indexer/path-resolver.js +89 -0
- package/dist/indexer/ranking-contributors.js +204 -0
- package/dist/indexer/ranking.js +74 -0
- package/dist/indexer/search-hit-enrichers.js +22 -0
- package/dist/indexer/search-source.js +24 -9
- package/dist/indexer/semantic-status.js +2 -16
- package/dist/indexer/walker.js +25 -0
- package/dist/integrations/agent/config.js +175 -3
- package/dist/integrations/agent/index.js +3 -1
- package/dist/integrations/agent/pipeline.js +39 -0
- package/dist/integrations/agent/profiles.js +67 -5
- package/dist/integrations/agent/prompts.js +77 -72
- package/dist/integrations/agent/runners.js +31 -0
- package/dist/integrations/agent/sdk-runner.js +120 -0
- package/dist/integrations/agent/spawn.js +71 -16
- package/dist/integrations/lockfile.js +10 -18
- package/dist/integrations/session-logs/index.js +65 -0
- package/dist/integrations/session-logs/providers/claude-code.js +56 -0
- package/dist/integrations/session-logs/providers/opencode.js +52 -0
- package/dist/integrations/session-logs/types.js +1 -0
- package/dist/llm/call-ai.js +74 -0
- package/dist/llm/client.js +61 -122
- package/dist/llm/feature-gate.js +27 -16
- package/dist/llm/graph-extract.js +297 -62
- package/dist/llm/memory-infer.js +49 -71
- package/dist/llm/metadata-enhance.js +39 -22
- package/dist/llm/prompts/graph-extract-user-prompt.md +12 -0
- package/dist/output/cli-hints-full.md +277 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +2 -318
- package/dist/output/renderers.js +190 -123
- package/dist/output/shapes.js +33 -0
- package/dist/output/text.js +239 -2
- package/dist/registry/providers/skills-sh.js +61 -49
- package/dist/registry/providers/static-index.js +44 -48
- package/dist/setup/setup.js +510 -11
- package/dist/sources/provider-factory.js +2 -1
- package/dist/sources/providers/git.js +2 -2
- package/dist/sources/website-ingest.js +4 -0
- package/dist/tasks/backends/cron.js +200 -0
- package/dist/tasks/backends/exec-utils.js +25 -0
- package/dist/tasks/backends/index.js +32 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +184 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +212 -0
- package/dist/tasks/parser.js +198 -0
- package/dist/tasks/resolveAkmBin.js +84 -0
- package/dist/tasks/runner.js +432 -0
- package/dist/tasks/schedule.js +208 -0
- package/dist/tasks/schema.js +13 -0
- package/dist/tasks/validator.js +59 -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 +12 -0
- package/dist/wiki/wiki.js +10 -61
- package/dist/workflows/authoring.js +5 -25
- package/dist/workflows/renderer.js +8 -3
- package/dist/workflows/runs.js +59 -91
- package/dist/workflows/validator.js +1 -1
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +3 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.8.0.md +43 -0
- package/package.json +3 -2
- package/dist/templates/wiki-templates.js +0 -100
package/dist/cli.js
CHANGED
|
@@ -4,37 +4,42 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
import { defineCommand, runMain } from "citty";
|
|
7
|
+
import { hasSubcommand, parsePositiveIntFlag } from "./cli/parse-args";
|
|
8
|
+
import { akmAgentDispatch } from "./commands/agent-dispatch";
|
|
7
9
|
import { generateBashCompletions, installBashCompletions } from "./commands/completions";
|
|
8
10
|
import { getConfigValue, listConfig, setConfigValue, unsetConfigValue } from "./commands/config-cli";
|
|
9
11
|
import { akmCurate } from "./commands/curate";
|
|
10
|
-
import { akmDistill } from "./commands/distill";
|
|
11
12
|
import { akmEventsList, akmEventsTail } from "./commands/events";
|
|
13
|
+
import { akmGraphEntities, akmGraphExport, akmGraphRelated, akmGraphRelations, akmGraphSummary, } from "./commands/graph";
|
|
14
|
+
import { akmHealth } from "./commands/health";
|
|
12
15
|
import { akmHistory } from "./commands/history";
|
|
16
|
+
import { akmImprove } from "./commands/improve";
|
|
13
17
|
import { assembleInfo } from "./commands/info";
|
|
14
18
|
import { akmInit } from "./commands/init";
|
|
15
19
|
import { akmListSources, akmRemove, akmUpdate } from "./commands/installed-stashes";
|
|
20
|
+
import { readKnowledgeInput, writeMarkdownAsset } from "./commands/knowledge";
|
|
21
|
+
import { akmLint } from "./commands/lint";
|
|
16
22
|
import { renderMigrationHelp } from "./commands/migration-help";
|
|
17
23
|
import { akmProposalAccept, akmProposalDiff, akmProposalList, akmProposalReject, akmProposalShow, } from "./commands/proposal";
|
|
18
24
|
import { akmPropose } from "./commands/propose";
|
|
19
|
-
import { akmReflect } from "./commands/reflect";
|
|
20
25
|
import { searchRegistry } from "./commands/registry-search";
|
|
21
|
-
import { buildMemoryFrontmatter, parseDuration, readMemoryContent, runAutoHeuristics, runLlmEnrich, } from "./commands/remember";
|
|
22
|
-
import { akmSearch, parseScopeFilterFlags, parseSearchSource } from "./commands/search";
|
|
26
|
+
import { buildMemoryFrontmatter, parseDuration, readMemoryContent, resolveRememberContentArg, runAutoHeuristics, runLlmEnrich, } from "./commands/remember";
|
|
27
|
+
import { akmSearch, parseBeliefFilterMode, parseScopeFilterFlags, parseSearchSource } from "./commands/search";
|
|
23
28
|
import { checkForUpdate, performUpgrade } from "./commands/self-update";
|
|
24
|
-
import { akmShowUnified } from "./commands/show";
|
|
29
|
+
import { akmShowUnified, normalizeShowArgv } from "./commands/show";
|
|
25
30
|
import { akmAdd } from "./commands/source-add";
|
|
26
31
|
import { akmClone } from "./commands/source-clone";
|
|
27
32
|
import { addStash } from "./commands/source-manage";
|
|
33
|
+
import { akmTasksAdd, akmTasksDoctor, akmTasksHistory, akmTasksList, akmTasksRemove, akmTasksRun, akmTasksSetEnabled, akmTasksShow, akmTasksSync, parseTaskRef, } from "./commands/tasks";
|
|
28
34
|
import { parseAssetRef } from "./core/asset-ref";
|
|
29
35
|
import { deriveCanonicalAssetName, resolveAssetPathFromName } from "./core/asset-spec";
|
|
30
|
-
import { isHttpUrl,
|
|
31
|
-
import { DEFAULT_CONFIG,
|
|
36
|
+
import { isHttpUrl, resolveStashDir } from "./core/common";
|
|
37
|
+
import { DEFAULT_CONFIG, loadConfig, loadUserConfig, resolveConfiguredSources, saveConfig } from "./core/config";
|
|
32
38
|
import { ConfigError, NotFoundError, UsageError } from "./core/errors";
|
|
33
39
|
import { appendEvent } from "./core/events";
|
|
34
|
-
import { getCacheDir, getDbPath, getDefaultStashDir } from "./core/paths";
|
|
35
|
-
import { setQuiet, setVerbose, warn } from "./core/warn";
|
|
36
|
-
import {
|
|
37
|
-
import { closeDatabase, findEntryIdByRef, openExistingDatabase } from "./indexer/db";
|
|
40
|
+
import { getCacheDir, getConfigPath, getDbPath, getDefaultStashDir } from "./core/paths";
|
|
41
|
+
import { clearLogFile, info, setLogFile, setQuiet, setVerbose, warn } from "./core/warn";
|
|
42
|
+
import { applyFeedbackToUtilityScore, closeDatabase, findEntryIdByRef, openExistingDatabase } from "./indexer/db";
|
|
38
43
|
import { ensureIndex } from "./indexer/ensure-index";
|
|
39
44
|
import { akmIndex } from "./indexer/indexer";
|
|
40
45
|
import { resolveSourceEntries } from "./indexer/search-source";
|
|
@@ -47,16 +52,22 @@ import { buildRegistryIndex, writeRegistryIndex } from "./registry/build-index";
|
|
|
47
52
|
import { resolveSourcesForOrigin } from "./registry/origin-resolve";
|
|
48
53
|
import { saveGitStash } from "./sources/providers/git";
|
|
49
54
|
import { resolveAssetPath } from "./sources/resolve";
|
|
50
|
-
import { fetchWebsiteMarkdownSnapshot } from "./sources/website-ingest";
|
|
51
55
|
import { pkgVersion } from "./version";
|
|
52
56
|
import { createWorkflowAsset, formatWorkflowErrors, getWorkflowTemplate, validateWorkflowSource, } from "./workflows/authoring";
|
|
53
57
|
import { hasWorkflowSubcommand, parseWorkflowJsonObject, parseWorkflowStepState, WORKFLOW_STEP_STATES, } from "./workflows/cli";
|
|
54
58
|
import { completeWorkflowStep, getNextWorkflowStep, getWorkflowStatus, listWorkflowRuns, resumeWorkflowRun, startWorkflowRun, } from "./workflows/runs";
|
|
55
|
-
const MAX_CAPTURED_ASSET_SLUG_LENGTH = 64;
|
|
56
59
|
const SKILLS_SH_NAME = "skills.sh";
|
|
57
60
|
const SKILLS_SH_URL = "https://skills.sh";
|
|
58
61
|
const SKILLS_SH_PROVIDER = "skills-sh";
|
|
59
62
|
import { stringify as yamlStringify } from "yaml";
|
|
63
|
+
function applyEarlyStderrFlags(argv) {
|
|
64
|
+
if (argv.includes("--quiet") || argv.includes("-q")) {
|
|
65
|
+
setQuiet(true);
|
|
66
|
+
}
|
|
67
|
+
if (argv.includes("--verbose")) {
|
|
68
|
+
setVerbose(true);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
60
71
|
/**
|
|
61
72
|
* Collect all occurrences of a repeatable flag from process.argv.
|
|
62
73
|
* Citty's StringArgDef only exposes the last value when a flag is repeated,
|
|
@@ -80,6 +91,43 @@ function parseAllFlagValues(flag) {
|
|
|
80
91
|
}
|
|
81
92
|
return values;
|
|
82
93
|
}
|
|
94
|
+
function resolveHelpMigrateVersionArg(version) {
|
|
95
|
+
if (version === undefined)
|
|
96
|
+
return undefined;
|
|
97
|
+
const parsedFormat = parseFlagValue(process.argv, "--format");
|
|
98
|
+
if (parsedFormat !== undefined &&
|
|
99
|
+
version === parsedFormat &&
|
|
100
|
+
wasHelpMigrateFlagValueConsumedAsVersion(version, parsedFormat, "--format")) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const parsedDetail = parseFlagValue(process.argv, "--detail");
|
|
104
|
+
if (parsedDetail !== undefined &&
|
|
105
|
+
version === parsedDetail &&
|
|
106
|
+
wasHelpMigrateFlagValueConsumedAsVersion(version, parsedDetail, "--detail")) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
return version;
|
|
110
|
+
}
|
|
111
|
+
function wasHelpMigrateFlagValueConsumedAsVersion(version, flagValue, flagName) {
|
|
112
|
+
const argv = process.argv.slice(2);
|
|
113
|
+
const helpIndex = argv.indexOf("help");
|
|
114
|
+
const tokens = helpIndex >= 0 ? argv.slice(helpIndex + 1) : argv;
|
|
115
|
+
const migrateIndex = tokens.indexOf("migrate");
|
|
116
|
+
const relevant = migrateIndex >= 0 ? tokens.slice(migrateIndex + 1) : tokens;
|
|
117
|
+
let flagIndex = -1;
|
|
118
|
+
for (let i = 0; i < relevant.length; i += 1) {
|
|
119
|
+
const token = relevant[i];
|
|
120
|
+
if (token === flagName || token === `${flagName}=${flagValue}`) {
|
|
121
|
+
flagIndex = i;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (flagIndex === -1)
|
|
126
|
+
return false;
|
|
127
|
+
if (relevant.slice(0, flagIndex).includes(version))
|
|
128
|
+
return false;
|
|
129
|
+
return relevant[flagIndex] === flagName ? relevant[flagIndex + 1] === version : true;
|
|
130
|
+
}
|
|
83
131
|
function output(command, result) {
|
|
84
132
|
const mode = getOutputMode();
|
|
85
133
|
const shaped = shapeForCommand(command, result, mode.detail, mode.forAgent);
|
|
@@ -111,12 +159,57 @@ function output(command, result) {
|
|
|
111
159
|
const setupCommand = defineCommand({
|
|
112
160
|
meta: {
|
|
113
161
|
name: "setup",
|
|
114
|
-
description: "Interactive configuration wizard
|
|
162
|
+
description: "Interactive configuration wizard. Configures embeddings/LLM connections (for indexing/enrichment), agent profiles (CLI agent, embedded SDK, or none), sources, and registries. Shows which features are enabled at the end. Use --config <json> or --yes for non-interactive/scripting mode.",
|
|
115
163
|
},
|
|
116
|
-
|
|
164
|
+
args: {
|
|
165
|
+
config: {
|
|
166
|
+
type: "string",
|
|
167
|
+
description: 'Config JSON to apply non-interactively, e.g. \'{"llm":{"endpoint":"...","model":"..."}}\'',
|
|
168
|
+
},
|
|
169
|
+
yes: {
|
|
170
|
+
type: "boolean",
|
|
171
|
+
default: false,
|
|
172
|
+
description: "Accept all defaults, skip all prompts. Idempotent — safe to run in CI.",
|
|
173
|
+
},
|
|
174
|
+
dir: {
|
|
175
|
+
type: "string",
|
|
176
|
+
description: "Stash directory path (overrides stashDir in config or --config JSON)",
|
|
177
|
+
},
|
|
178
|
+
probe: {
|
|
179
|
+
type: "boolean",
|
|
180
|
+
default: false,
|
|
181
|
+
description: "Probe LLM/embedding endpoints after writing config to verify connectivity",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
async run({ args }) {
|
|
117
185
|
await runWithJsonErrors(async () => {
|
|
118
|
-
const
|
|
119
|
-
|
|
186
|
+
const noInit = getHyphenatedBoolean(args, "no-init");
|
|
187
|
+
if (args.config) {
|
|
188
|
+
// Non-interactive config mode
|
|
189
|
+
const { runSetupFromConfig } = await import("./setup/setup");
|
|
190
|
+
const result = await runSetupFromConfig({
|
|
191
|
+
configJson: args.config,
|
|
192
|
+
dir: args.dir,
|
|
193
|
+
noInit,
|
|
194
|
+
probe: args.probe,
|
|
195
|
+
});
|
|
196
|
+
output("setup", result);
|
|
197
|
+
}
|
|
198
|
+
else if (args.yes) {
|
|
199
|
+
// Defaults mode — no prompts
|
|
200
|
+
const { runSetupWithDefaults } = await import("./setup/setup");
|
|
201
|
+
const result = await runSetupWithDefaults({
|
|
202
|
+
dir: args.dir,
|
|
203
|
+
noInit,
|
|
204
|
+
probe: args.probe,
|
|
205
|
+
});
|
|
206
|
+
output("setup", result);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// Interactive wizard
|
|
210
|
+
const { runSetupWizard } = await import("./setup/setup");
|
|
211
|
+
await runSetupWizard({ dir: args.dir, noInit });
|
|
212
|
+
}
|
|
120
213
|
});
|
|
121
214
|
},
|
|
122
215
|
});
|
|
@@ -142,16 +235,23 @@ const indexCommand = defineCommand({
|
|
|
142
235
|
meta: { name: "index", description: "Build search index (incremental by default; --full forces full reindex)" },
|
|
143
236
|
args: {
|
|
144
237
|
full: { type: "boolean", description: "Force full reindex", default: false },
|
|
145
|
-
enrich: { type: "boolean", description: "Enable LLM inference and enrichment passes", default: false },
|
|
146
238
|
verbose: { type: "boolean", description: "Print phase-by-phase indexing progress to stderr", default: false },
|
|
147
239
|
},
|
|
148
240
|
async run({ args }) {
|
|
149
241
|
await runWithJsonErrors(async () => {
|
|
242
|
+
if (getHyphenatedBoolean(args, "enrich") || parseFlagValue(process.argv, "--enrich") !== undefined) {
|
|
243
|
+
throw new UsageError("`akm index --enrich` has been removed. Plain `akm index` now performs metadata enrichment by default.");
|
|
244
|
+
}
|
|
245
|
+
if (getHyphenatedBoolean(args, "re-enrich") || parseFlagValue(process.argv, "--re-enrich") !== undefined) {
|
|
246
|
+
throw new UsageError("`akm index --re-enrich` has been removed. Re-enrichment of index-time LLM passes is not exposed in this slice.");
|
|
247
|
+
}
|
|
150
248
|
const outputMode = getOutputMode();
|
|
151
249
|
const controller = new AbortController();
|
|
152
250
|
const abort = () => controller.abort(new Error("index interrupted"));
|
|
153
251
|
process.once("SIGINT", abort);
|
|
154
252
|
process.once("SIGTERM", abort);
|
|
253
|
+
const indexLogFile = path.join(getCacheDir(), "logs", "index", `${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
254
|
+
setLogFile(indexLogFile);
|
|
155
255
|
const spin = !args.verbose && outputMode.format === "text" ? p.spinner() : null;
|
|
156
256
|
if (spin) {
|
|
157
257
|
spin.start(`Building search index${args.full ? " (full rebuild)" : ""}...`);
|
|
@@ -160,12 +260,11 @@ const indexCommand = defineCommand({
|
|
|
160
260
|
try {
|
|
161
261
|
const result = await akmIndex({
|
|
162
262
|
full: args.full,
|
|
163
|
-
|
|
164
|
-
onProgress: ({ message, processed, total }) => {
|
|
263
|
+
onProgress: ({ phase, message, processed, total }) => {
|
|
165
264
|
latestMessage = message;
|
|
166
265
|
const progressPrefix = processed !== undefined && total !== undefined ? `[${processed}/${total}] ` : "";
|
|
167
266
|
if (args.verbose) {
|
|
168
|
-
|
|
267
|
+
info(`[index:${phase}] ${progressPrefix}${message}`);
|
|
169
268
|
}
|
|
170
269
|
else if (spin) {
|
|
171
270
|
spin.stop(`${progressPrefix}${message}`);
|
|
@@ -186,6 +285,7 @@ const indexCommand = defineCommand({
|
|
|
186
285
|
throw error;
|
|
187
286
|
}
|
|
188
287
|
finally {
|
|
288
|
+
clearLogFile();
|
|
189
289
|
process.off("SIGINT", abort);
|
|
190
290
|
process.off("SIGTERM", abort);
|
|
191
291
|
}
|
|
@@ -201,6 +301,102 @@ const infoCommand = defineCommand({
|
|
|
201
301
|
});
|
|
202
302
|
},
|
|
203
303
|
});
|
|
304
|
+
const healthCommand = defineCommand({
|
|
305
|
+
meta: { name: "health", description: "Check akm runtime health, artifacts, and improve metrics" },
|
|
306
|
+
args: {
|
|
307
|
+
since: {
|
|
308
|
+
type: "string",
|
|
309
|
+
description: "Rolling window start (ISO timestamp, date, epoch ms, or shorthand like 24h / 7d)",
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
run({ args }) {
|
|
313
|
+
return runWithJsonErrors(() => {
|
|
314
|
+
const result = akmHealth({ since: args.since });
|
|
315
|
+
output("health", result);
|
|
316
|
+
});
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
const graphCommand = defineCommand({
|
|
320
|
+
meta: { name: "graph", description: "Inspect the indexed entity graph stored in SQLite" },
|
|
321
|
+
subCommands: {
|
|
322
|
+
summary: defineCommand({
|
|
323
|
+
meta: { name: "summary", description: "Show entity-graph counts and quality telemetry" },
|
|
324
|
+
args: {
|
|
325
|
+
source: { type: "string", description: "Source name/path (default: primary stash source)" },
|
|
326
|
+
},
|
|
327
|
+
run({ args }) {
|
|
328
|
+
return runWithJsonErrors(() => {
|
|
329
|
+
output("graph-summary", akmGraphSummary({ source: args.source }));
|
|
330
|
+
});
|
|
331
|
+
},
|
|
332
|
+
}),
|
|
333
|
+
entities: defineCommand({
|
|
334
|
+
meta: { name: "entities", description: "List entities with per-file occurrence counts" },
|
|
335
|
+
args: {
|
|
336
|
+
source: { type: "string", description: "Source name/path (default: primary stash source)" },
|
|
337
|
+
limit: { type: "string", description: "Maximum entities to return" },
|
|
338
|
+
},
|
|
339
|
+
run({ args }) {
|
|
340
|
+
return runWithJsonErrors(() => {
|
|
341
|
+
output("graph-entities", akmGraphEntities({ source: args.source, limit: parsePositiveIntFlag(args.limit ?? undefined) }));
|
|
342
|
+
});
|
|
343
|
+
},
|
|
344
|
+
}),
|
|
345
|
+
relations: defineCommand({
|
|
346
|
+
meta: { name: "relations", description: "List relations with occurrence counts" },
|
|
347
|
+
args: {
|
|
348
|
+
source: { type: "string", description: "Source name/path (default: primary stash source)" },
|
|
349
|
+
limit: { type: "string", description: "Maximum relations to return" },
|
|
350
|
+
},
|
|
351
|
+
run({ args }) {
|
|
352
|
+
return runWithJsonErrors(() => {
|
|
353
|
+
output("graph-relations", akmGraphRelations({ source: args.source, limit: parsePositiveIntFlag(args.limit ?? undefined) }));
|
|
354
|
+
});
|
|
355
|
+
},
|
|
356
|
+
}),
|
|
357
|
+
related: defineCommand({
|
|
358
|
+
meta: { name: "related", description: "Show graph-related neighboring assets for a ref" },
|
|
359
|
+
args: {
|
|
360
|
+
ref: { type: "positional", description: "Asset ref", required: true },
|
|
361
|
+
source: { type: "string", description: "Source name/path (default: primary stash source)" },
|
|
362
|
+
limit: { type: "string", description: "Maximum related assets to return" },
|
|
363
|
+
},
|
|
364
|
+
async run({ args }) {
|
|
365
|
+
return runWithJsonErrors(async () => {
|
|
366
|
+
output("graph-related", await akmGraphRelated({
|
|
367
|
+
ref: args.ref ?? "",
|
|
368
|
+
source: args.source,
|
|
369
|
+
limit: parsePositiveIntFlag(args.limit ?? undefined),
|
|
370
|
+
}));
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
}),
|
|
374
|
+
export: defineCommand({
|
|
375
|
+
meta: { name: "export", description: "Export graph artifact as JSON or JSONL" },
|
|
376
|
+
args: {
|
|
377
|
+
source: { type: "string", description: "Source name/path (default: primary stash source)" },
|
|
378
|
+
out: { type: "string", description: "Output path" },
|
|
379
|
+
format: { type: "string", description: "Export format (json|jsonl)", default: "json" },
|
|
380
|
+
},
|
|
381
|
+
run({ args }) {
|
|
382
|
+
return runWithJsonErrors(() => {
|
|
383
|
+
output("graph-export", akmGraphExport({
|
|
384
|
+
source: args.source,
|
|
385
|
+
out: args.out ?? "",
|
|
386
|
+
format: args.format,
|
|
387
|
+
}));
|
|
388
|
+
});
|
|
389
|
+
},
|
|
390
|
+
}),
|
|
391
|
+
},
|
|
392
|
+
run({ args }) {
|
|
393
|
+
return runWithJsonErrors(() => {
|
|
394
|
+
if (hasSubcommand(args, GRAPH_SUBCOMMAND_SET))
|
|
395
|
+
return;
|
|
396
|
+
output("graph-summary", akmGraphSummary());
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
});
|
|
204
400
|
const searchCommand = defineCommand({
|
|
205
401
|
meta: { name: "search", description: "Search the stash" },
|
|
206
402
|
args: {
|
|
@@ -220,29 +416,30 @@ const searchCommand = defineCommand({
|
|
|
220
416
|
description: 'Include entries with quality:"proposed" in the result set. Excluded by default (v1 spec §4.2).',
|
|
221
417
|
default: false,
|
|
222
418
|
},
|
|
419
|
+
belief: {
|
|
420
|
+
type: "string",
|
|
421
|
+
description: "Memory belief filter: all|current|historical. current keeps active memory beliefs; historical keeps contradicted/superseded/archived memory beliefs.",
|
|
422
|
+
default: "all",
|
|
423
|
+
},
|
|
223
424
|
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
224
425
|
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
225
426
|
},
|
|
226
427
|
async run({ args }) {
|
|
227
428
|
await runWithJsonErrors(async () => {
|
|
228
|
-
// An empty query enumerates all indexed assets (list mode).
|
|
229
|
-
// The guard that rejected empty queries was removed; akmSearch handles
|
|
230
|
-
// empty strings end-to-end via getAllEntries (DB path) and the
|
|
231
|
-
// substring-search fallback's query-less branch.
|
|
232
429
|
const query = (args.query ?? "").trim();
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (limitRaw !== undefined && Number.isNaN(limitRaw)) {
|
|
236
|
-
throw new UsageError(`Invalid --limit value: "${args.limit}". Must be a positive integer.`);
|
|
430
|
+
if (!query) {
|
|
431
|
+
throw new UsageError('A search query is required. Usage: akm search "<query>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT", 'Pass a query like `akm search "docker"` or `akm search "code review" --type skill`.');
|
|
237
432
|
}
|
|
238
|
-
const
|
|
433
|
+
const type = args.type;
|
|
434
|
+
const limit = parsePositiveIntFlag(args.limit ?? undefined);
|
|
239
435
|
const source = parseSearchSource(args.source);
|
|
240
436
|
// Repeatable; citty exposes only the last `--filter` value, so read all
|
|
241
437
|
// occurrences directly from argv (same pattern as `--tag`).
|
|
242
438
|
const filterTokens = parseAllFlagValues("--filter");
|
|
243
439
|
const filters = parseScopeFilterFlags(filterTokens, "--filter");
|
|
244
440
|
const includeProposed = args["include-proposed"] === true;
|
|
245
|
-
const
|
|
441
|
+
const belief = parseBeliefFilterMode(typeof args.belief === "string" ? args.belief : undefined);
|
|
442
|
+
const result = await akmSearch({ query, type, limit, source, filters, includeProposed, belief });
|
|
246
443
|
output("search", result);
|
|
247
444
|
});
|
|
248
445
|
},
|
|
@@ -267,11 +464,8 @@ const curateCommand = defineCommand({
|
|
|
267
464
|
throw new UsageError('A curate query is required. Usage: akm curate "<task or prompt>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT", 'Describe the task you want assets for, e.g. `akm curate "deploy to prod"`.');
|
|
268
465
|
}
|
|
269
466
|
const type = args.type;
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
throw new UsageError(`Invalid --limit value: "${args.limit}". Must be a positive integer.`);
|
|
273
|
-
}
|
|
274
|
-
const limit = limitRaw && limitRaw > 0 ? limitRaw : 4;
|
|
467
|
+
const limitParsed = parsePositiveIntFlag(args.limit ?? undefined);
|
|
468
|
+
const limit = limitParsed && limitParsed > 0 ? limitParsed : 4;
|
|
275
469
|
const source = parseSearchSource(args.source ?? "stash");
|
|
276
470
|
const curated = await akmCurate({ query: args.query, type, limit, source });
|
|
277
471
|
output("curate", curated);
|
|
@@ -545,15 +739,17 @@ const showCommand = defineCommand({
|
|
|
545
739
|
},
|
|
546
740
|
async run({ args }) {
|
|
547
741
|
await runWithJsonErrors(async () => {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
throw new UsageError(error.message, "INVALID_FLAG_VALUE", error.hint());
|
|
742
|
+
const subcommand = Array.isArray(args._) ? args._[0] : undefined;
|
|
743
|
+
if (subcommand === "proposal") {
|
|
744
|
+
const proposalId = Array.isArray(args._) ? args._[1] : undefined;
|
|
745
|
+
if (typeof proposalId !== "string" || !proposalId.trim()) {
|
|
746
|
+
throw new UsageError("Usage: akm show proposal <id>", "MISSING_REQUIRED_ARGUMENT");
|
|
554
747
|
}
|
|
555
|
-
|
|
748
|
+
const result = akmProposalShow({ id: proposalId.trim() });
|
|
749
|
+
output("proposal-show", result);
|
|
750
|
+
return;
|
|
556
751
|
}
|
|
752
|
+
parseAssetRef(args.ref);
|
|
557
753
|
// The knowledge-view positional syntax (`akm show knowledge:foo section "Auth"`)
|
|
558
754
|
// is rewritten to `--akmView` / `--akmHeading` / `--akmStart` / `--akmEnd`
|
|
559
755
|
// by `normalizeShowArgv` before citty parses argv. We read those values
|
|
@@ -642,6 +838,14 @@ const configCommand = defineCommand({
|
|
|
642
838
|
});
|
|
643
839
|
},
|
|
644
840
|
}),
|
|
841
|
+
show: defineCommand({
|
|
842
|
+
meta: { name: "show", description: "Alias for `akm config list` — list current configuration" },
|
|
843
|
+
run() {
|
|
844
|
+
return runWithJsonErrors(() => {
|
|
845
|
+
output("config", listConfig(loadConfig()));
|
|
846
|
+
});
|
|
847
|
+
},
|
|
848
|
+
}),
|
|
645
849
|
get: defineCommand({
|
|
646
850
|
meta: { name: "get", description: "Get a configuration value by key" },
|
|
647
851
|
args: {
|
|
@@ -683,7 +887,7 @@ const configCommand = defineCommand({
|
|
|
683
887
|
},
|
|
684
888
|
run({ args }) {
|
|
685
889
|
return runWithJsonErrors(() => {
|
|
686
|
-
if (
|
|
890
|
+
if (hasSubcommand(args, CONFIG_SUBCOMMAND_SET))
|
|
687
891
|
return;
|
|
688
892
|
if (args.list) {
|
|
689
893
|
output("config", listConfig(loadConfig()));
|
|
@@ -903,10 +1107,7 @@ const registryCommand = defineCommand({
|
|
|
903
1107
|
},
|
|
904
1108
|
async run({ args }) {
|
|
905
1109
|
await runWithJsonErrors(async () => {
|
|
906
|
-
const limitRaw =
|
|
907
|
-
if (limitRaw !== undefined && Number.isNaN(limitRaw)) {
|
|
908
|
-
throw new UsageError(`Invalid --limit value: "${args.limit}". Must be a positive integer.`);
|
|
909
|
-
}
|
|
1110
|
+
const limitRaw = parsePositiveIntFlag(args.limit ?? undefined);
|
|
910
1111
|
const result = await searchRegistry(args.query, { limit: limitRaw, includeAssets: args.assets });
|
|
911
1112
|
output("registry-search", result);
|
|
912
1113
|
});
|
|
@@ -971,7 +1172,7 @@ const feedbackCommand = defineCommand({
|
|
|
971
1172
|
description: "Record positive or negative feedback for any indexed stash asset.\n\n" +
|
|
972
1173
|
"Positive feedback boosts an asset's EMA utility score, making it rank higher\n" +
|
|
973
1174
|
"in future searches without requiring a full reindex.\n\n" +
|
|
974
|
-
"Negative feedback records a negative signal in usage_events and events
|
|
1175
|
+
"Negative feedback records a negative signal in usage_events and state.db events.\n" +
|
|
975
1176
|
"It does NOT immediately lower the asset's ranking — the EMA utility score is\n" +
|
|
976
1177
|
"updated the next time `akm index` runs (incremental or full). Run `akm index`\n" +
|
|
977
1178
|
"after recording negative feedback to have it reflected in search results.",
|
|
@@ -988,7 +1189,11 @@ const feedbackCommand = defineCommand({
|
|
|
988
1189
|
"Reindexing is required for the signal to affect search results.",
|
|
989
1190
|
default: false,
|
|
990
1191
|
},
|
|
991
|
-
|
|
1192
|
+
reason: {
|
|
1193
|
+
type: "string",
|
|
1194
|
+
description: "Reason for the feedback (recommended for negative feedback, used by distillation)",
|
|
1195
|
+
},
|
|
1196
|
+
note: { type: "string", description: "Alias for --reason (backward-compatible, prefer --reason)" },
|
|
992
1197
|
tag: {
|
|
993
1198
|
type: "string",
|
|
994
1199
|
description: "Tag to attach to the feedback (repeatable, e.g. --tag slice:train --tag team:platform)",
|
|
@@ -1008,11 +1213,21 @@ const feedbackCommand = defineCommand({
|
|
|
1008
1213
|
throw new UsageError("Specify --positive or --negative.");
|
|
1009
1214
|
}
|
|
1010
1215
|
const signal = args.positive ? "positive" : "negative";
|
|
1216
|
+
const reason = args.reason ?? args.note;
|
|
1217
|
+
if (args.negative === true && !reason?.trim()) {
|
|
1218
|
+
const cfg = loadConfig();
|
|
1219
|
+
if (cfg.feedback?.requireReason === true) {
|
|
1220
|
+
throw new UsageError("Negative feedback requires --reason (feedback.requireReason is enabled).", "MISSING_REQUIRED_ARGUMENT");
|
|
1221
|
+
}
|
|
1222
|
+
else {
|
|
1223
|
+
warn("Warning: negative feedback without --reason provides less distillation signal.");
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1011
1226
|
const rawTags = parseAllFlagValues("--tag");
|
|
1012
1227
|
const validatedTags = validateFeedbackTags(rawTags);
|
|
1013
1228
|
const metadataObj = {
|
|
1014
1229
|
signal,
|
|
1015
|
-
...(
|
|
1230
|
+
...(reason?.trim() ? { reason: reason.trim() } : {}),
|
|
1016
1231
|
...(validatedTags.length > 0 ? { tags: validatedTags } : {}),
|
|
1017
1232
|
};
|
|
1018
1233
|
const metadataStr = Object.keys(metadataObj).length > 1 ? JSON.stringify(metadataObj) : undefined;
|
|
@@ -1040,6 +1255,25 @@ const feedbackCommand = defineCommand({
|
|
|
1040
1255
|
signal,
|
|
1041
1256
|
metadata: metadataStr,
|
|
1042
1257
|
});
|
|
1258
|
+
// Apply feedback-derived utility score adjustment immediately so that
|
|
1259
|
+
// positive/negative signals influence search ranking without requiring
|
|
1260
|
+
// a full reindex. We query the total accumulated feedback counts from
|
|
1261
|
+
// usage_events so the delta reflects the entire signal history.
|
|
1262
|
+
try {
|
|
1263
|
+
const counts = db
|
|
1264
|
+
.prepare(`SELECT
|
|
1265
|
+
SUM(CASE WHEN signal = 'positive' THEN 1 ELSE 0 END) AS pos,
|
|
1266
|
+
SUM(CASE WHEN signal = 'negative' THEN 1 ELSE 0 END) AS neg
|
|
1267
|
+
FROM usage_events
|
|
1268
|
+
WHERE event_type = 'feedback' AND entry_id = ?`)
|
|
1269
|
+
.get(entryId);
|
|
1270
|
+
const pos = counts?.pos ?? 0;
|
|
1271
|
+
const neg = counts?.neg ?? 0;
|
|
1272
|
+
applyFeedbackToUtilityScore(db, entryId, pos, neg);
|
|
1273
|
+
}
|
|
1274
|
+
catch {
|
|
1275
|
+
// best-effort — feedback recording succeeds even if utility update fails
|
|
1276
|
+
}
|
|
1043
1277
|
}
|
|
1044
1278
|
finally {
|
|
1045
1279
|
closeDatabase(db);
|
|
@@ -1049,7 +1283,7 @@ const feedbackCommand = defineCommand({
|
|
|
1049
1283
|
ref,
|
|
1050
1284
|
metadata: metadataObj,
|
|
1051
1285
|
});
|
|
1052
|
-
output("feedback", { ok: true, ref, signal,
|
|
1286
|
+
output("feedback", { ok: true, ref, signal, reason: reason?.trim() ?? null, tags: validatedTags });
|
|
1053
1287
|
});
|
|
1054
1288
|
},
|
|
1055
1289
|
});
|
|
@@ -1059,8 +1293,8 @@ const historyCommand = defineCommand({
|
|
|
1059
1293
|
description: "Show mutation/usage history for a single asset (--ref) or stash-wide.\n\n" +
|
|
1060
1294
|
"Event sources:\n" +
|
|
1061
1295
|
" usage_events (default): search, show, and feedback events from the local index.\n" +
|
|
1062
|
-
"
|
|
1063
|
-
" emitted by `akm
|
|
1296
|
+
" state.db events (--include-proposals): proposal lifecycle events (promoted, rejected)\n" +
|
|
1297
|
+
" emitted by `akm accept` / `akm reject`.\n\n" +
|
|
1064
1298
|
"Results from all active sources are merged and sorted chronologically.",
|
|
1065
1299
|
},
|
|
1066
1300
|
args: {
|
|
@@ -1068,7 +1302,7 @@ const historyCommand = defineCommand({
|
|
|
1068
1302
|
since: { type: "string", description: "ISO timestamp or epoch ms — only events on/after this time" },
|
|
1069
1303
|
"include-proposals": {
|
|
1070
1304
|
type: "boolean",
|
|
1071
|
-
description: "Also include proposal lifecycle events (promoted, rejected) from events.
|
|
1305
|
+
description: "Also include proposal lifecycle events (promoted, rejected) from state.db events. " +
|
|
1072
1306
|
"Default: false (usage_events only).",
|
|
1073
1307
|
default: false,
|
|
1074
1308
|
},
|
|
@@ -1085,93 +1319,6 @@ const historyCommand = defineCommand({
|
|
|
1085
1319
|
});
|
|
1086
1320
|
},
|
|
1087
1321
|
});
|
|
1088
|
-
function normalizeMarkdownAssetName(name, fallback) {
|
|
1089
|
-
const trimmed = (name ?? fallback)
|
|
1090
|
-
.trim()
|
|
1091
|
-
.replace(/\\/g, "/")
|
|
1092
|
-
.replace(/^\/+|\/+$/g, "")
|
|
1093
|
-
.replace(/\.md$/i, "");
|
|
1094
|
-
if (!trimmed)
|
|
1095
|
-
throw new UsageError("Asset name cannot be empty.");
|
|
1096
|
-
const segments = trimmed.split("/");
|
|
1097
|
-
if (segments.some((segment) => !segment || segment === "." || segment === "..")) {
|
|
1098
|
-
throw new UsageError("Asset name must be a relative path without '.' or '..' segments.");
|
|
1099
|
-
}
|
|
1100
|
-
return trimmed;
|
|
1101
|
-
}
|
|
1102
|
-
function slugifyAssetName(value, fallbackPrefix) {
|
|
1103
|
-
const slug = value
|
|
1104
|
-
.toLowerCase()
|
|
1105
|
-
.replace(/^[#>\-\s]+/, "")
|
|
1106
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
1107
|
-
.replace(/^-+|-+$/g, "")
|
|
1108
|
-
.slice(0, MAX_CAPTURED_ASSET_SLUG_LENGTH);
|
|
1109
|
-
return slug || `${fallbackPrefix}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
1110
|
-
}
|
|
1111
|
-
function inferAssetName(content, fallbackPrefix, preferred) {
|
|
1112
|
-
const firstNonEmptyLine = content
|
|
1113
|
-
.split(/\r?\n/)
|
|
1114
|
-
.map((line) => line.trim())
|
|
1115
|
-
.find((line) => line.length > 0);
|
|
1116
|
-
const basis = preferred?.trim() || firstNonEmptyLine || fallbackPrefix;
|
|
1117
|
-
return slugifyAssetName(basis, fallbackPrefix);
|
|
1118
|
-
}
|
|
1119
|
-
function readKnowledgeContent(source) {
|
|
1120
|
-
if (source === "-") {
|
|
1121
|
-
const content = tryReadStdinText();
|
|
1122
|
-
if (!content?.trim()) {
|
|
1123
|
-
throw new UsageError("No stdin content received. Pipe a document into stdin or pass a file path.");
|
|
1124
|
-
}
|
|
1125
|
-
return { content };
|
|
1126
|
-
}
|
|
1127
|
-
const resolvedSource = path.resolve(source);
|
|
1128
|
-
let stat;
|
|
1129
|
-
try {
|
|
1130
|
-
stat = fs.statSync(resolvedSource);
|
|
1131
|
-
}
|
|
1132
|
-
catch {
|
|
1133
|
-
throw new UsageError(`Knowledge source not found: "${source}". Pass a readable file path or "-" for stdin.`);
|
|
1134
|
-
}
|
|
1135
|
-
if (!stat.isFile()) {
|
|
1136
|
-
throw new UsageError(`Knowledge source must be a file: "${source}".`);
|
|
1137
|
-
}
|
|
1138
|
-
return {
|
|
1139
|
-
content: fs.readFileSync(resolvedSource, "utf8"),
|
|
1140
|
-
preferredName: path.basename(resolvedSource, path.extname(resolvedSource)),
|
|
1141
|
-
};
|
|
1142
|
-
}
|
|
1143
|
-
async function readKnowledgeInput(source) {
|
|
1144
|
-
if (!isHttpUrl(source))
|
|
1145
|
-
return readKnowledgeContent(source);
|
|
1146
|
-
const snapshot = await fetchWebsiteMarkdownSnapshot(source);
|
|
1147
|
-
return { content: snapshot.content, preferredName: snapshot.preferredName };
|
|
1148
|
-
}
|
|
1149
|
-
async function writeMarkdownAsset(options) {
|
|
1150
|
-
// Resolve write target via the v1 precedence chain (`--target` →
|
|
1151
|
-
// `defaultWriteTarget` → working stash). Per spec §10 step 5, this is the
|
|
1152
|
-
// single dispatch point — `core/write-source.ts` owns all kind-branching.
|
|
1153
|
-
const cfg = loadConfig();
|
|
1154
|
-
const { source, config } = resolveWriteTarget(cfg, options.target);
|
|
1155
|
-
const typeRoot = path.join(source.path, options.type === "knowledge" ? "knowledge" : "memories");
|
|
1156
|
-
const normalizedName = normalizeMarkdownAssetName(options.name, inferAssetName(options.content, options.fallbackPrefix, options.preferredName));
|
|
1157
|
-
// Pre-flight: existence + force semantics. The helper itself overwrites
|
|
1158
|
-
// unconditionally; the CLI surfaces a friendlier UsageError before any
|
|
1159
|
-
// disk activity when --force is absent.
|
|
1160
|
-
const assetPath = resolveAssetPathFromName(options.type, typeRoot, normalizedName);
|
|
1161
|
-
if (!isWithin(assetPath, typeRoot)) {
|
|
1162
|
-
throw new UsageError(`Resolved ${options.type} path escapes the stash: "${normalizedName}"`);
|
|
1163
|
-
}
|
|
1164
|
-
if (fs.existsSync(assetPath) && !options.force) {
|
|
1165
|
-
throw new UsageError(`${options.type === "knowledge" ? "Knowledge" : "Memory"} "${normalizedName}" already exists. Re-run with --force to overwrite it.`, "RESOURCE_ALREADY_EXISTS");
|
|
1166
|
-
}
|
|
1167
|
-
// Delegate the actual write (and optional git commit/push) to the helper.
|
|
1168
|
-
const result = await writeAssetToSource(source, config, { type: options.type, name: normalizedName }, options.content);
|
|
1169
|
-
return {
|
|
1170
|
-
ref: result.ref,
|
|
1171
|
-
path: result.path,
|
|
1172
|
-
stashDir: source.path,
|
|
1173
|
-
};
|
|
1174
|
-
}
|
|
1175
1322
|
const workflowStartCommand = defineCommand({
|
|
1176
1323
|
meta: {
|
|
1177
1324
|
name: "start",
|
|
@@ -1196,16 +1343,20 @@ const workflowNextCommand = defineCommand({
|
|
|
1196
1343
|
args: {
|
|
1197
1344
|
target: { type: "positional", description: "Workflow run id or workflow ref", required: true },
|
|
1198
1345
|
params: { type: "string", description: "Workflow parameters as a JSON object (only for auto-started runs)" },
|
|
1346
|
+
"dry-run": { type: "boolean", description: "Not supported — rejected with an error", default: false },
|
|
1199
1347
|
},
|
|
1200
1348
|
async run({ args }) {
|
|
1201
1349
|
await runWithJsonErrors(async () => {
|
|
1350
|
+
if (getHyphenatedBoolean(args, "dry-run")) {
|
|
1351
|
+
throw new UsageError("`akm workflow next` does not support --dry-run. Remove the flag to start or resume a run.", "INVALID_FLAG_VALUE");
|
|
1352
|
+
}
|
|
1202
1353
|
const parsedParams = args.params ? parseWorkflowJsonObject(args.params, "--params") : undefined;
|
|
1203
1354
|
// If the target looks like a UUID-style run id (no `:` and matches the
|
|
1204
1355
|
// run-id shape), short-circuit with a structured WORKFLOW_NOT_FOUND
|
|
1205
1356
|
// error before parseAssetRef gets to throw an unhelpful ref-parse error.
|
|
1206
1357
|
if (looksLikeWorkflowRunId(args.target)) {
|
|
1207
1358
|
const { hasWorkflowRun } = await import("./workflows/runs.js");
|
|
1208
|
-
if (!hasWorkflowRun(args.target)) {
|
|
1359
|
+
if (!(await hasWorkflowRun(args.target))) {
|
|
1209
1360
|
throw new NotFoundError(`Workflow run "${args.target}" not found.`, "WORKFLOW_NOT_FOUND", "Run `akm workflow list --active` to see runs.");
|
|
1210
1361
|
}
|
|
1211
1362
|
}
|
|
@@ -1250,7 +1401,7 @@ const workflowCompleteCommand = defineCommand({
|
|
|
1250
1401
|
},
|
|
1251
1402
|
async run({ args }) {
|
|
1252
1403
|
await runWithJsonErrors(async () => {
|
|
1253
|
-
const result = completeWorkflowStep({
|
|
1404
|
+
const result = await completeWorkflowStep({
|
|
1254
1405
|
runId: args.runId,
|
|
1255
1406
|
stepId: args.step,
|
|
1256
1407
|
status: parseWorkflowStepState(args.state),
|
|
@@ -1270,7 +1421,7 @@ const workflowStatusCommand = defineCommand({
|
|
|
1270
1421
|
target: { type: "positional", description: "Workflow run id or workflow ref (workflow:<name>)", required: true },
|
|
1271
1422
|
},
|
|
1272
1423
|
run({ args }) {
|
|
1273
|
-
return runWithJsonErrors(() => {
|
|
1424
|
+
return runWithJsonErrors(async () => {
|
|
1274
1425
|
const target = args.target;
|
|
1275
1426
|
// Check if target looks like a workflow ref
|
|
1276
1427
|
const parsed = (() => {
|
|
@@ -1283,18 +1434,18 @@ const workflowStatusCommand = defineCommand({
|
|
|
1283
1434
|
})();
|
|
1284
1435
|
if (parsed?.type === "workflow") {
|
|
1285
1436
|
const ref = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
|
|
1286
|
-
const { runs } = listWorkflowRuns({ workflowRef: ref });
|
|
1437
|
+
const { runs } = await listWorkflowRuns({ workflowRef: ref });
|
|
1287
1438
|
if (runs.length === 0) {
|
|
1288
1439
|
throw new NotFoundError(`No workflow runs found for ${ref}`, "WORKFLOW_NOT_FOUND");
|
|
1289
1440
|
}
|
|
1290
1441
|
const mostRecent = runs[0];
|
|
1291
1442
|
if (!mostRecent)
|
|
1292
1443
|
throw new NotFoundError(`No workflow runs found for ${ref}`, "WORKFLOW_NOT_FOUND");
|
|
1293
|
-
const result = getWorkflowStatus(mostRecent.id);
|
|
1444
|
+
const result = await getWorkflowStatus(mostRecent.id);
|
|
1294
1445
|
output("workflow-status", result);
|
|
1295
1446
|
}
|
|
1296
1447
|
else {
|
|
1297
|
-
const result = getWorkflowStatus(target);
|
|
1448
|
+
const result = await getWorkflowStatus(target);
|
|
1298
1449
|
output("workflow-status", result);
|
|
1299
1450
|
}
|
|
1300
1451
|
});
|
|
@@ -1310,8 +1461,8 @@ const workflowListCommand = defineCommand({
|
|
|
1310
1461
|
active: { type: "boolean", description: "Only show active runs", default: false },
|
|
1311
1462
|
},
|
|
1312
1463
|
run({ args }) {
|
|
1313
|
-
return runWithJsonErrors(() => {
|
|
1314
|
-
const result = listWorkflowRuns({ workflowRef: args.ref, activeOnly: args.active });
|
|
1464
|
+
return runWithJsonErrors(async () => {
|
|
1465
|
+
const result = await listWorkflowRuns({ workflowRef: args.ref, activeOnly: args.active });
|
|
1315
1466
|
output("workflow-list", result);
|
|
1316
1467
|
});
|
|
1317
1468
|
},
|
|
@@ -1424,8 +1575,8 @@ const workflowResumeCommand = defineCommand({
|
|
|
1424
1575
|
runId: { type: "positional", description: "Workflow run id", required: true },
|
|
1425
1576
|
},
|
|
1426
1577
|
run({ args }) {
|
|
1427
|
-
return runWithJsonErrors(() => {
|
|
1428
|
-
const result = resumeWorkflowRun(args.runId);
|
|
1578
|
+
return runWithJsonErrors(async () => {
|
|
1579
|
+
const result = await resumeWorkflowRun(args.runId);
|
|
1429
1580
|
output("workflow-resume", result);
|
|
1430
1581
|
});
|
|
1431
1582
|
},
|
|
@@ -1447,10 +1598,10 @@ const workflowCommand = defineCommand({
|
|
|
1447
1598
|
validate: workflowValidateCommand,
|
|
1448
1599
|
},
|
|
1449
1600
|
run({ args }) {
|
|
1450
|
-
return runWithJsonErrors(() => {
|
|
1601
|
+
return runWithJsonErrors(async () => {
|
|
1451
1602
|
if (hasWorkflowSubcommand(args))
|
|
1452
1603
|
return;
|
|
1453
|
-
output("workflow-list", listWorkflowRuns({ activeOnly: true }));
|
|
1604
|
+
output("workflow-list", await listWorkflowRuns({ activeOnly: true }));
|
|
1454
1605
|
});
|
|
1455
1606
|
},
|
|
1456
1607
|
});
|
|
@@ -1520,6 +1671,10 @@ const rememberCommand = defineCommand({
|
|
|
1520
1671
|
type: "string",
|
|
1521
1672
|
description: "Scope this memory to a channel name (persisted as `scope_channel` frontmatter)",
|
|
1522
1673
|
},
|
|
1674
|
+
showSimilar: {
|
|
1675
|
+
type: "boolean",
|
|
1676
|
+
description: "Return top-3 similar existing memories in output (opt-in)",
|
|
1677
|
+
},
|
|
1523
1678
|
},
|
|
1524
1679
|
async run({ args }) {
|
|
1525
1680
|
return runWithJsonErrors(async () => {
|
|
@@ -1541,7 +1696,7 @@ const rememberCommand = defineCommand({
|
|
|
1541
1696
|
if (typeof args.channel === "string" && args.channel.trim())
|
|
1542
1697
|
scopeFields.channel = args.channel.trim();
|
|
1543
1698
|
const hasScope = Object.keys(scopeFields).length > 0;
|
|
1544
|
-
const hasTagRequiringArgs = rawTags.length > 0 || !!args.expires || !!args.source || !!args.description
|
|
1699
|
+
const hasTagRequiringArgs = rawTags.length > 0 || !!args.expires || !!args.source || !!args.description;
|
|
1545
1700
|
const hasStructuredArgs = hasTagRequiringArgs || hasScope || args.auto;
|
|
1546
1701
|
if (!hasStructuredArgs) {
|
|
1547
1702
|
const result = await writeMarkdownAsset({
|
|
@@ -1557,7 +1712,13 @@ const rememberCommand = defineCommand({
|
|
|
1557
1712
|
ref: result.ref,
|
|
1558
1713
|
metadata: { path: result.path, force: args.force === true },
|
|
1559
1714
|
});
|
|
1560
|
-
|
|
1715
|
+
if (args.showSimilar) {
|
|
1716
|
+
const similar = await fetchSimilarMemories(body.slice(0, 500), result.ref);
|
|
1717
|
+
output("remember", { ok: true, ...result, similar });
|
|
1718
|
+
}
|
|
1719
|
+
else {
|
|
1720
|
+
output("remember", { ok: true, ...result });
|
|
1721
|
+
}
|
|
1561
1722
|
return;
|
|
1562
1723
|
}
|
|
1563
1724
|
// ── Accumulate metadata from all three modes ──────────────────────────
|
|
@@ -1646,53 +1807,31 @@ const rememberCommand = defineCommand({
|
|
|
1646
1807
|
...(hasScope ? { scope: scopeFields } : {}),
|
|
1647
1808
|
},
|
|
1648
1809
|
});
|
|
1649
|
-
|
|
1810
|
+
if (args.showSimilar) {
|
|
1811
|
+
const similar = await fetchSimilarMemories((body ?? args.content ?? "").slice(0, 500), result.ref);
|
|
1812
|
+
output("remember", { ok: true, ...result, similar });
|
|
1813
|
+
}
|
|
1814
|
+
else {
|
|
1815
|
+
output("remember", { ok: true, ...result });
|
|
1816
|
+
}
|
|
1650
1817
|
});
|
|
1651
1818
|
},
|
|
1652
1819
|
});
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
return
|
|
1820
|
+
/**
|
|
1821
|
+
* Best-effort top-3 similar memory search for `--show-similar`.
|
|
1822
|
+
* Scoped to memory: type; excludes the just-written ref.
|
|
1823
|
+
*/
|
|
1824
|
+
async function fetchSimilarMemories(query, excludeRef) {
|
|
1825
|
+
try {
|
|
1826
|
+
const result = await akmSearch({ query, type: "memory", limit: 4 });
|
|
1827
|
+
return (result.hits ?? [])
|
|
1828
|
+
.filter((h) => "ref" in h && h.ref !== excludeRef)
|
|
1829
|
+
.slice(0, 3)
|
|
1830
|
+
.map((h) => ({ ref: h.ref, ...(h.name ? { title: h.name } : {}) }));
|
|
1661
1831
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
content === parsedDetail &&
|
|
1665
|
-
wasRememberFlagValueConsumedAsContent(content, parsedDetail, "--detail")) {
|
|
1666
|
-
return undefined;
|
|
1832
|
+
catch {
|
|
1833
|
+
return [];
|
|
1667
1834
|
}
|
|
1668
|
-
return content;
|
|
1669
|
-
}
|
|
1670
|
-
function wasRememberFlagValueConsumedAsContent(content, flagValue, flagName) {
|
|
1671
|
-
const argv = process.argv.slice(2);
|
|
1672
|
-
const rememberIndex = argv.indexOf("remember");
|
|
1673
|
-
const tokens = rememberIndex >= 0 ? argv.slice(rememberIndex + 1) : argv;
|
|
1674
|
-
let flagIndex = -1;
|
|
1675
|
-
let flagConsumesNextToken = false;
|
|
1676
|
-
for (let i = 0; i < tokens.length; i += 1) {
|
|
1677
|
-
const token = tokens[i];
|
|
1678
|
-
if (token === flagName) {
|
|
1679
|
-
flagIndex = i;
|
|
1680
|
-
flagConsumesNextToken = true;
|
|
1681
|
-
break;
|
|
1682
|
-
}
|
|
1683
|
-
if (token === `${flagName}=${flagValue}`) {
|
|
1684
|
-
flagIndex = i;
|
|
1685
|
-
break;
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
if (flagIndex === -1)
|
|
1689
|
-
return false;
|
|
1690
|
-
if (tokens.slice(0, flagIndex).includes(content))
|
|
1691
|
-
return false;
|
|
1692
|
-
const firstTokenAfterFlag = flagIndex + (flagConsumesNextToken ? 2 : 1);
|
|
1693
|
-
if (tokens.slice(firstTokenAfterFlag).includes(content))
|
|
1694
|
-
return false;
|
|
1695
|
-
return true;
|
|
1696
1835
|
}
|
|
1697
1836
|
const importKnowledgeCommand = defineCommand({
|
|
1698
1837
|
meta: {
|
|
@@ -1782,10 +1921,11 @@ const helpCommand = defineCommand({
|
|
|
1782
1921
|
},
|
|
1783
1922
|
run({ args }) {
|
|
1784
1923
|
return runWithJsonErrors(() => {
|
|
1785
|
-
|
|
1924
|
+
const version = resolveHelpMigrateVersionArg(typeof args.version === "string" ? args.version : undefined);
|
|
1925
|
+
if (!version?.trim()) {
|
|
1786
1926
|
throw new UsageError("Usage: akm help migrate <version>.", "MISSING_REQUIRED_ARGUMENT", "Pass a version like `0.6.0`, `v0.6.0`, `0.6.0-rc1`, or `latest`.");
|
|
1787
1927
|
}
|
|
1788
|
-
process.stdout.write(renderMigrationHelp(
|
|
1928
|
+
process.stdout.write(renderMigrationHelp(version));
|
|
1789
1929
|
});
|
|
1790
1930
|
},
|
|
1791
1931
|
}),
|
|
@@ -1815,8 +1955,8 @@ const completionsCommand = defineCommand({
|
|
|
1815
1955
|
const script = generateBashCompletions(main);
|
|
1816
1956
|
if (args.install) {
|
|
1817
1957
|
const dest = installBashCompletions(script);
|
|
1818
|
-
|
|
1819
|
-
|
|
1958
|
+
info(`Completions installed to ${dest}`);
|
|
1959
|
+
info(`Restart your shell or run: source ${dest}`);
|
|
1820
1960
|
}
|
|
1821
1961
|
else {
|
|
1822
1962
|
process.stdout.write(script);
|
|
@@ -2122,7 +2262,7 @@ const vaultCommand = defineCommand({
|
|
|
2122
2262
|
},
|
|
2123
2263
|
run({ args }) {
|
|
2124
2264
|
return runWithJsonErrors(async () => {
|
|
2125
|
-
if (
|
|
2265
|
+
if (hasSubcommand(args, VAULT_SUBCOMMAND_SET))
|
|
2126
2266
|
return;
|
|
2127
2267
|
// Default action: list all vaults
|
|
2128
2268
|
const { listKeys } = await import("./commands/vault.js");
|
|
@@ -2286,12 +2426,39 @@ const wikiStashCommand = defineCommand({
|
|
|
2286
2426
|
name: { type: "positional", description: "Wiki name", required: true },
|
|
2287
2427
|
source: { type: "positional", description: "Source file path, URL, or '-' to read from stdin", required: true },
|
|
2288
2428
|
as: { type: "string", description: "Preferred slug base (defaults to source filename or first-line slug)" },
|
|
2429
|
+
target: {
|
|
2430
|
+
type: "string",
|
|
2431
|
+
description: "Name of a writable stash source to write into instead of the default stash. Must match a configured source name (run `akm list` to see sources).",
|
|
2432
|
+
},
|
|
2289
2433
|
},
|
|
2290
2434
|
run({ args }) {
|
|
2291
2435
|
return runWithJsonErrors(async () => {
|
|
2292
2436
|
const { stashRaw } = await import("./wiki/wiki.js");
|
|
2293
|
-
const { content, preferredName } = await
|
|
2294
|
-
|
|
2437
|
+
const { content, preferredName } = await (async () => {
|
|
2438
|
+
if (!isHttpUrl(args.source))
|
|
2439
|
+
return readKnowledgeInput(args.source);
|
|
2440
|
+
const { fetchWebsiteMarkdownSnapshot } = await import("./sources/website-ingest");
|
|
2441
|
+
const snapshot = await fetchWebsiteMarkdownSnapshot(args.source);
|
|
2442
|
+
return { content: snapshot.content, preferredName: args.as ?? snapshot.preferredName };
|
|
2443
|
+
})();
|
|
2444
|
+
let stashDir;
|
|
2445
|
+
if (args.target) {
|
|
2446
|
+
// Resolve the named source to its filesystem path.
|
|
2447
|
+
const cfg = loadConfig();
|
|
2448
|
+
const sources = resolveConfiguredSources(cfg);
|
|
2449
|
+
const match = sources.find((s) => s.name === args.target);
|
|
2450
|
+
if (!match) {
|
|
2451
|
+
throw new UsageError(`--target must reference a configured source name. No source named "${args.target}" found. Run \`akm list\` to see available sources.`, "INVALID_FLAG_VALUE");
|
|
2452
|
+
}
|
|
2453
|
+
const spec = match.source;
|
|
2454
|
+
if (spec.type !== "filesystem" && spec.type !== "local") {
|
|
2455
|
+
throw new ConfigError(`Source "${args.target}" is not a filesystem source and cannot be used as a wiki stash target.`, "INVALID_CONFIG_FILE", `Use a source with type "filesystem" or "local", or omit --target to use the default stash.`);
|
|
2456
|
+
}
|
|
2457
|
+
stashDir = spec.path;
|
|
2458
|
+
}
|
|
2459
|
+
else {
|
|
2460
|
+
stashDir = resolveStashDir();
|
|
2461
|
+
}
|
|
2295
2462
|
const result = stashRaw({
|
|
2296
2463
|
stashDir,
|
|
2297
2464
|
wikiName: args.name,
|
|
@@ -2360,7 +2527,7 @@ const wikiCommand = defineCommand({
|
|
|
2360
2527
|
},
|
|
2361
2528
|
run({ args }) {
|
|
2362
2529
|
return runWithJsonErrors(async () => {
|
|
2363
|
-
if (
|
|
2530
|
+
if (hasSubcommand(args, WIKI_SUBCOMMAND_SET))
|
|
2364
2531
|
return;
|
|
2365
2532
|
// Default action: list wikis
|
|
2366
2533
|
const { listWikis } = await import("./wiki/wiki.js");
|
|
@@ -2369,15 +2536,15 @@ const wikiCommand = defineCommand({
|
|
|
2369
2536
|
},
|
|
2370
2537
|
});
|
|
2371
2538
|
// ── `akm events` ────────────────────────────────────────────────────────────
|
|
2372
|
-
// Append-only events stream surface (#204). `list` reads
|
|
2373
|
-
// with optional --since/--type/--ref filters; `tail` follows the
|
|
2539
|
+
// Append-only events stream surface (#204). `list` reads state.db events
|
|
2540
|
+
// with optional --since/--type/--ref filters; `tail` follows the table via
|
|
2374
2541
|
// a polling loop and prints each event as a single JSONL line.
|
|
2375
2542
|
const eventsListCommand = defineCommand({
|
|
2376
|
-
meta: { name: "list", description: "List events from the append-only
|
|
2543
|
+
meta: { name: "list", description: "List events from the append-only state.db events stream" },
|
|
2377
2544
|
args: {
|
|
2378
2545
|
since: {
|
|
2379
2546
|
type: "string",
|
|
2380
|
-
description: "ISO timestamp / epoch ms, OR `@offset:<
|
|
2547
|
+
description: "ISO timestamp / epoch ms, OR `@offset:<id>` for a durable row-id cursor (resume across processes)",
|
|
2381
2548
|
},
|
|
2382
2549
|
type: { type: "string", description: "Filter by event type (add, remove, remember, feedback, ...)" },
|
|
2383
2550
|
ref: { type: "string", description: "Filter by asset ref (type:name)" },
|
|
@@ -2406,11 +2573,11 @@ const eventsListCommand = defineCommand({
|
|
|
2406
2573
|
},
|
|
2407
2574
|
});
|
|
2408
2575
|
const eventsTailCommand = defineCommand({
|
|
2409
|
-
meta: { name: "tail", description: "Follow the append-only
|
|
2576
|
+
meta: { name: "tail", description: "Follow the append-only state.db events stream (polling)" },
|
|
2410
2577
|
args: {
|
|
2411
2578
|
since: {
|
|
2412
2579
|
type: "string",
|
|
2413
|
-
description: "ISO timestamp / epoch ms, OR `@offset:<
|
|
2580
|
+
description: "ISO timestamp / epoch ms, OR `@offset:<id>` for a durable row-id cursor (resume across processes)",
|
|
2414
2581
|
},
|
|
2415
2582
|
type: { type: "string", description: "Filter by event type" },
|
|
2416
2583
|
ref: { type: "string", description: "Filter by asset ref (type:name)" },
|
|
@@ -2499,7 +2666,7 @@ function parsePositiveInt(raw, flag) {
|
|
|
2499
2666
|
const eventsCommand = defineCommand({
|
|
2500
2667
|
meta: {
|
|
2501
2668
|
name: "events",
|
|
2502
|
-
description: "Read or follow the append-only
|
|
2669
|
+
description: "Read or follow the append-only state.db events stream (mutations, feedback, indexing)",
|
|
2503
2670
|
},
|
|
2504
2671
|
subCommands: {
|
|
2505
2672
|
list: eventsListCommand,
|
|
@@ -2507,16 +2674,12 @@ const eventsCommand = defineCommand({
|
|
|
2507
2674
|
},
|
|
2508
2675
|
});
|
|
2509
2676
|
// ── proposal substrate (#225) ────────────────────────────────────────────────
|
|
2510
|
-
const
|
|
2511
|
-
meta: { name: "
|
|
2677
|
+
const proposalsCommand = defineCommand({
|
|
2678
|
+
meta: { name: "proposals", description: "List proposal queue entries" },
|
|
2512
2679
|
args: {
|
|
2513
2680
|
status: { type: "string", description: "Filter by status (pending|accepted|rejected)" },
|
|
2514
2681
|
ref: { type: "string", description: "Filter by asset ref (type:name)" },
|
|
2515
|
-
"
|
|
2516
|
-
type: "boolean",
|
|
2517
|
-
description: "Include accepted/rejected proposals from the archive",
|
|
2518
|
-
default: false,
|
|
2519
|
-
},
|
|
2682
|
+
type: { type: "string", description: "Filter by asset type" },
|
|
2520
2683
|
},
|
|
2521
2684
|
run({ args }) {
|
|
2522
2685
|
return runWithJsonErrors(() => {
|
|
@@ -2524,28 +2687,20 @@ const proposalListCommand = defineCommand({
|
|
|
2524
2687
|
const result = akmProposalList({
|
|
2525
2688
|
status,
|
|
2526
2689
|
ref: args.ref,
|
|
2527
|
-
includeArchive:
|
|
2690
|
+
includeArchive: status === "accepted" || status === "rejected",
|
|
2528
2691
|
});
|
|
2529
2692
|
output("proposal-list", result);
|
|
2530
2693
|
});
|
|
2531
2694
|
},
|
|
2532
2695
|
});
|
|
2533
|
-
const
|
|
2534
|
-
meta: { name: "
|
|
2696
|
+
const acceptCommand = defineCommand({
|
|
2697
|
+
meta: { name: "accept", description: "Accept a proposal and promote it into the stash" },
|
|
2535
2698
|
args: {
|
|
2536
|
-
id: {
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
output("proposal-show", result);
|
|
2542
|
-
});
|
|
2543
|
-
},
|
|
2544
|
-
});
|
|
2545
|
-
const proposalAcceptCommand = defineCommand({
|
|
2546
|
-
meta: { name: "accept", description: "Validate and promote a proposal to a real asset" },
|
|
2547
|
-
args: {
|
|
2548
|
-
id: { type: "positional", description: "Proposal id (uuid)", required: true },
|
|
2699
|
+
id: {
|
|
2700
|
+
type: "positional",
|
|
2701
|
+
description: "Proposal id (uuid / prefix) or asset ref (e.g. skill:akm-dream)",
|
|
2702
|
+
required: true,
|
|
2703
|
+
},
|
|
2549
2704
|
target: { type: "string", description: "Override the write target by source name" },
|
|
2550
2705
|
},
|
|
2551
2706
|
async run({ args }) {
|
|
@@ -2555,23 +2710,34 @@ const proposalAcceptCommand = defineCommand({
|
|
|
2555
2710
|
});
|
|
2556
2711
|
},
|
|
2557
2712
|
});
|
|
2558
|
-
const
|
|
2559
|
-
meta: { name: "reject", description: "
|
|
2713
|
+
const rejectCommand = defineCommand({
|
|
2714
|
+
meta: { name: "reject", description: "Reject a proposal and record the reason" },
|
|
2560
2715
|
args: {
|
|
2561
|
-
id: {
|
|
2562
|
-
|
|
2716
|
+
id: {
|
|
2717
|
+
type: "positional",
|
|
2718
|
+
description: "Proposal id (uuid / prefix) or asset ref (e.g. skill:akm-dream)",
|
|
2719
|
+
required: true,
|
|
2720
|
+
},
|
|
2721
|
+
reason: { type: "string", description: "Reason for rejection (required)" },
|
|
2563
2722
|
},
|
|
2564
2723
|
run({ args }) {
|
|
2565
2724
|
return runWithJsonErrors(() => {
|
|
2725
|
+
if (!args.reason || !String(args.reason).trim()) {
|
|
2726
|
+
throw new UsageError("Usage: akm reject <id> --reason '<reason>'", "MISSING_REQUIRED_ARGUMENT");
|
|
2727
|
+
}
|
|
2566
2728
|
const result = akmProposalReject({ id: args.id, reason: args.reason });
|
|
2567
2729
|
output("proposal-reject", result);
|
|
2568
2730
|
});
|
|
2569
2731
|
},
|
|
2570
2732
|
});
|
|
2571
|
-
const
|
|
2572
|
-
meta: { name: "diff", description: "Show the diff
|
|
2733
|
+
const diffCommand = defineCommand({
|
|
2734
|
+
meta: { name: "diff", description: "Show the diff for a proposal (accepts full UUID, UUID prefix, or asset ref)" },
|
|
2573
2735
|
args: {
|
|
2574
|
-
id: {
|
|
2736
|
+
id: {
|
|
2737
|
+
type: "positional",
|
|
2738
|
+
description: "Proposal id (uuid / prefix) or asset ref (e.g. skill:akm-dream)",
|
|
2739
|
+
required: true,
|
|
2740
|
+
},
|
|
2575
2741
|
target: { type: "string", description: "Override the write target by source name" },
|
|
2576
2742
|
},
|
|
2577
2743
|
run({ args }) {
|
|
@@ -2581,112 +2747,7 @@ const proposalDiffCommand = defineCommand({
|
|
|
2581
2747
|
});
|
|
2582
2748
|
},
|
|
2583
2749
|
});
|
|
2584
|
-
const proposalCommand = defineCommand({
|
|
2585
|
-
meta: {
|
|
2586
|
-
name: "proposal",
|
|
2587
|
-
description: "Review and promote queued asset proposals (durable storage under .akm/proposals/)",
|
|
2588
|
-
},
|
|
2589
|
-
subCommands: {
|
|
2590
|
-
list: proposalListCommand,
|
|
2591
|
-
show: proposalShowCommand,
|
|
2592
|
-
accept: proposalAcceptCommand,
|
|
2593
|
-
reject: proposalRejectCommand,
|
|
2594
|
-
diff: proposalDiffCommand,
|
|
2595
|
-
},
|
|
2596
|
-
});
|
|
2597
2750
|
// ── distill (#228) ──────────────────────────────────────────────────────────
|
|
2598
|
-
const distillCommand = defineCommand({
|
|
2599
|
-
meta: {
|
|
2600
|
-
name: "distill",
|
|
2601
|
-
description: "Distil feedback for an asset into a queued lesson proposal (gated on llm.features.feedback_distillation)",
|
|
2602
|
-
},
|
|
2603
|
-
args: {
|
|
2604
|
-
ref: { type: "positional", description: "Asset ref (type:name) to distil from", required: true },
|
|
2605
|
-
"source-run": {
|
|
2606
|
-
type: "string",
|
|
2607
|
-
description: "Optional run id propagated onto the queued proposal for traceability",
|
|
2608
|
-
},
|
|
2609
|
-
"exclude-feedback-from": {
|
|
2610
|
-
type: "string",
|
|
2611
|
-
description: "Comma-separated asset refs whose feedback events MUST be filtered out before the LLM input is built. Falls back to AKM_DISTILL_EXCLUDE_FEEDBACK_FROM when omitted.",
|
|
2612
|
-
},
|
|
2613
|
-
"exclude-tags": {
|
|
2614
|
-
type: "string",
|
|
2615
|
-
description: "Exclude feedback events matching these tags (repeatable, e.g. --exclude-tags slice:eval)",
|
|
2616
|
-
},
|
|
2617
|
-
"include-tags": {
|
|
2618
|
-
type: "string",
|
|
2619
|
-
description: "Only include feedback events with ALL these tags (repeatable)",
|
|
2620
|
-
},
|
|
2621
|
-
},
|
|
2622
|
-
async run({ args }) {
|
|
2623
|
-
await runWithJsonErrors(async () => {
|
|
2624
|
-
const excludeFlag = getHyphenatedArg(args, "exclude-feedback-from");
|
|
2625
|
-
const excludeEnv = process.env.AKM_DISTILL_EXCLUDE_FEEDBACK_FROM;
|
|
2626
|
-
// CLI flag takes precedence over the env var when both are present.
|
|
2627
|
-
const excludeRaw = excludeFlag ?? excludeEnv;
|
|
2628
|
-
const excludeFeedbackFromRefs = parseExcludeFeedbackFromRefs(excludeRaw);
|
|
2629
|
-
const excludeTagsRaw = parseAllFlagValues("--exclude-tags");
|
|
2630
|
-
const excludeTagsEnv = process.env.AKM_DISTILL_EXCLUDE_TAGS;
|
|
2631
|
-
const excludeTags = [
|
|
2632
|
-
...new Set([
|
|
2633
|
-
...excludeTagsRaw,
|
|
2634
|
-
...(excludeTagsEnv
|
|
2635
|
-
? excludeTagsEnv
|
|
2636
|
-
.split(",")
|
|
2637
|
-
.map((s) => s.trim())
|
|
2638
|
-
.filter(Boolean)
|
|
2639
|
-
: []),
|
|
2640
|
-
]),
|
|
2641
|
-
];
|
|
2642
|
-
const includeTagsRaw = parseAllFlagValues("--include-tags");
|
|
2643
|
-
const includeTagsEnv = process.env.AKM_DISTILL_INCLUDE_TAGS;
|
|
2644
|
-
const includeTags = [
|
|
2645
|
-
...new Set([
|
|
2646
|
-
...includeTagsRaw,
|
|
2647
|
-
...(includeTagsEnv
|
|
2648
|
-
? includeTagsEnv
|
|
2649
|
-
.split(",")
|
|
2650
|
-
.map((s) => s.trim())
|
|
2651
|
-
.filter(Boolean)
|
|
2652
|
-
: []),
|
|
2653
|
-
]),
|
|
2654
|
-
];
|
|
2655
|
-
const result = await akmDistill({
|
|
2656
|
-
ref: args.ref,
|
|
2657
|
-
sourceRun: getHyphenatedArg(args, "source-run"),
|
|
2658
|
-
...(excludeFeedbackFromRefs.length > 0 ? { excludeFeedbackFromRefs } : {}),
|
|
2659
|
-
...(excludeTags.length > 0 ? { excludeTags } : {}),
|
|
2660
|
-
...(includeTags.length > 0 ? { includeTags } : {}),
|
|
2661
|
-
});
|
|
2662
|
-
output("distill", result);
|
|
2663
|
-
});
|
|
2664
|
-
},
|
|
2665
|
-
});
|
|
2666
|
-
/**
|
|
2667
|
-
* Parse a comma-separated list of asset refs (#267 — `--exclude-feedback-from`
|
|
2668
|
-
* and `AKM_DISTILL_EXCLUDE_FEEDBACK_FROM`). Each entry is validated against
|
|
2669
|
-
* the canonical `[origin//]type:name` grammar via `parseAssetRef`; an
|
|
2670
|
-
* invalid entry surfaces as a UsageError → exit 2.
|
|
2671
|
-
*/
|
|
2672
|
-
function parseExcludeFeedbackFromRefs(raw) {
|
|
2673
|
-
if (raw === undefined || raw.trim() === "")
|
|
2674
|
-
return [];
|
|
2675
|
-
const refs = raw
|
|
2676
|
-
.split(",")
|
|
2677
|
-
.map((part) => part.trim())
|
|
2678
|
-
.filter((part) => part.length > 0);
|
|
2679
|
-
for (const ref of refs) {
|
|
2680
|
-
try {
|
|
2681
|
-
parseAssetRef(ref);
|
|
2682
|
-
}
|
|
2683
|
-
catch (err) {
|
|
2684
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2685
|
-
throw new UsageError(`Invalid --exclude-feedback-from ref "${ref}": ${message}`, "INVALID_FLAG_VALUE", "Each ref must match `[origin//]type:name`, e.g. skill:deploy or team//memory:auth-tips.");
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
2688
|
-
return refs;
|
|
2689
|
-
}
|
|
2690
2751
|
function parseProposalStatus(raw) {
|
|
2691
2752
|
if (raw === undefined)
|
|
2692
2753
|
return undefined;
|
|
@@ -2697,36 +2758,208 @@ function parseProposalStatus(raw) {
|
|
|
2697
2758
|
return trimmed;
|
|
2698
2759
|
throw new UsageError(`Invalid --status value: "${raw}". Expected one of: pending, accepted, rejected.`, "INVALID_FLAG_VALUE");
|
|
2699
2760
|
}
|
|
2700
|
-
|
|
2701
|
-
const reflectCommand = defineCommand({
|
|
2761
|
+
const agentCommand = defineCommand({
|
|
2702
2762
|
meta: {
|
|
2703
|
-
name: "
|
|
2704
|
-
description: "
|
|
2763
|
+
name: "agent",
|
|
2764
|
+
description: "Dispatch an agent by named profile, optionally injecting a prompt from inline text, a stash command: asset, or a stash workflow: asset",
|
|
2705
2765
|
},
|
|
2706
2766
|
args: {
|
|
2707
|
-
|
|
2767
|
+
profile: {
|
|
2708
2768
|
type: "positional",
|
|
2709
|
-
description: "
|
|
2769
|
+
description: "Agent profile name (from config.agent.profiles or a built-in)",
|
|
2710
2770
|
required: false,
|
|
2711
2771
|
},
|
|
2712
|
-
|
|
2713
|
-
|
|
2772
|
+
prompt: { type: "string", description: "Inline prompt text to pass to the agent" },
|
|
2773
|
+
command: { type: "string", description: "Load the body of a command: asset from the index and use as the prompt" },
|
|
2774
|
+
workflow: {
|
|
2775
|
+
type: "string",
|
|
2776
|
+
description: "Load the body of a workflow: asset from the index and use as the prompt",
|
|
2777
|
+
},
|
|
2714
2778
|
"timeout-ms": { type: "string", description: "Override the agent CLI timeout in milliseconds" },
|
|
2715
2779
|
},
|
|
2716
2780
|
async run({ args }) {
|
|
2717
2781
|
await runWithJsonErrors(async () => {
|
|
2782
|
+
if (!args.profile) {
|
|
2783
|
+
throw new UsageError("Usage: akm agent <profile> [--prompt <text>] [--command <ref>] [--workflow <ref>] [args...]", "MISSING_REQUIRED_ARGUMENT", "Provide the agent profile name. Available profiles are listed in config.agent.profiles.");
|
|
2784
|
+
}
|
|
2785
|
+
// Collect extra positional args (forwarded to the agent and used as
|
|
2786
|
+
// template placeholders when a command/workflow ref is specified).
|
|
2787
|
+
const extraArgs = Array.isArray(args._) ? args._.filter((a) => a !== args.profile) : [];
|
|
2718
2788
|
const timeoutRaw = args["timeout-ms"];
|
|
2719
2789
|
const timeoutMs = typeof timeoutRaw === "string" && timeoutRaw.trim() ? Number.parseInt(timeoutRaw, 10) : undefined;
|
|
2720
|
-
const
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2790
|
+
const config = loadConfig();
|
|
2791
|
+
const { parseAgentConfig } = await import("./integrations/agent/config.js");
|
|
2792
|
+
const agentConfig = parseAgentConfig(config.agent);
|
|
2793
|
+
const result = await akmAgentDispatch({
|
|
2794
|
+
profileName: String(args.profile),
|
|
2795
|
+
prompt: typeof args.prompt === "string" ? args.prompt : undefined,
|
|
2796
|
+
commandRef: typeof args.command === "string" && args.command.trim() ? args.command.trim() : undefined,
|
|
2797
|
+
workflowRef: typeof args.workflow === "string" && args.workflow.trim() ? args.workflow.trim() : undefined,
|
|
2798
|
+
args: extraArgs.length > 0 ? extraArgs : undefined,
|
|
2799
|
+
agentConfig,
|
|
2800
|
+
llmConfig: config.llm,
|
|
2724
2801
|
...(timeoutMs !== undefined && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
|
|
2725
2802
|
});
|
|
2726
|
-
output("
|
|
2727
|
-
if (result.ok
|
|
2803
|
+
output("agent-result", result);
|
|
2804
|
+
if (!result.ok) {
|
|
2805
|
+
process.exit(EXIT_GENERAL);
|
|
2806
|
+
}
|
|
2807
|
+
});
|
|
2808
|
+
},
|
|
2809
|
+
});
|
|
2810
|
+
const lintCommand = defineCommand({
|
|
2811
|
+
meta: {
|
|
2812
|
+
name: "lint",
|
|
2813
|
+
description: "Scan stash .md files for structural issues (unquoted colons, missing updated field, orphaned stubs, placeholder stubs, missing name/type, stale paths). Use --fix to auto-fix Tier 1 issues.",
|
|
2814
|
+
},
|
|
2815
|
+
args: {
|
|
2816
|
+
fix: { type: "boolean", description: "Apply auto-fixes in place", default: false },
|
|
2817
|
+
dir: { type: "string", description: "Override stash root directory (default: from config)" },
|
|
2818
|
+
},
|
|
2819
|
+
async run({ args }) {
|
|
2820
|
+
await runWithJsonErrors(async () => {
|
|
2821
|
+
const result = akmLint({
|
|
2822
|
+
fix: args.fix ?? false,
|
|
2823
|
+
dir: typeof args.dir === "string" && args.dir.trim() ? args.dir.trim() : undefined,
|
|
2824
|
+
});
|
|
2825
|
+
output("lint", result);
|
|
2826
|
+
if (!result.ok)
|
|
2728
2827
|
process.exit(EXIT_GENERAL);
|
|
2828
|
+
});
|
|
2829
|
+
},
|
|
2830
|
+
});
|
|
2831
|
+
const improveCommand = defineCommand({
|
|
2832
|
+
meta: {
|
|
2833
|
+
name: "improve",
|
|
2834
|
+
description: "Analyze existing AKM assets and generate improvement proposals; also consolidates memories when llm.features.memory_consolidation is enabled",
|
|
2835
|
+
},
|
|
2836
|
+
args: {
|
|
2837
|
+
scope: {
|
|
2838
|
+
type: "positional",
|
|
2839
|
+
description: "Optional asset type or asset ref to improve",
|
|
2840
|
+
required: false,
|
|
2841
|
+
},
|
|
2842
|
+
task: { type: "string", description: "Add extra guidance for this improvement pass" },
|
|
2843
|
+
"dry-run": { type: "boolean", description: "Show planned actions without writing", default: false },
|
|
2844
|
+
target: { type: "string", description: "Override the write target for accepted proposals" },
|
|
2845
|
+
"auto-accept": {
|
|
2846
|
+
type: "string",
|
|
2847
|
+
description: "Automatically accept low-risk proposals (only 'safe' is supported)",
|
|
2848
|
+
},
|
|
2849
|
+
limit: { type: "string", description: "Maximum number of assets to process (highest utility first)" },
|
|
2850
|
+
"timeout-ms": {
|
|
2851
|
+
type: "string",
|
|
2852
|
+
description: "Wall-clock budget for the entire run in milliseconds (default: 7200000 = 2 hours)",
|
|
2853
|
+
},
|
|
2854
|
+
"ignore-cooldown": {
|
|
2855
|
+
type: "boolean",
|
|
2856
|
+
description: "Ignore all cooldown periods (equivalent to --reflect-cooldown-days 0 --distill-cooldown-days 0 --consolidate-cooldown-days 0)",
|
|
2857
|
+
default: false,
|
|
2858
|
+
},
|
|
2859
|
+
"reflect-cooldown-days": {
|
|
2860
|
+
type: "string",
|
|
2861
|
+
description: "Override reflect cooldown for this run only (default: 7, 0 to disable)",
|
|
2862
|
+
},
|
|
2863
|
+
"distill-cooldown-days": {
|
|
2864
|
+
type: "string",
|
|
2865
|
+
description: "Override distill cooldown for this run only (default: 30, 0 to disable)",
|
|
2866
|
+
},
|
|
2867
|
+
"consolidate-cooldown-days": {
|
|
2868
|
+
type: "string",
|
|
2869
|
+
description: "Override consolidate cooldown for this run only (default: 14, 0 to disable)",
|
|
2870
|
+
},
|
|
2871
|
+
"consolidate-recovery": {
|
|
2872
|
+
type: "string",
|
|
2873
|
+
description: "How to handle stale/incomplete consolidation journals: abort (default) or clean (remove stale journal artifacts)",
|
|
2874
|
+
},
|
|
2875
|
+
"require-feedback-signal": {
|
|
2876
|
+
type: "boolean",
|
|
2877
|
+
description: "Only process assets with recent feedback signals (disables retrieval fallback)",
|
|
2878
|
+
default: false,
|
|
2879
|
+
},
|
|
2880
|
+
"min-retrieval-count": {
|
|
2881
|
+
type: "string",
|
|
2882
|
+
description: "Minimum retrieval count for zero-feedback fallback eligibility (default: 5)",
|
|
2883
|
+
},
|
|
2884
|
+
},
|
|
2885
|
+
async run({ args }) {
|
|
2886
|
+
await runWithJsonErrors(async () => {
|
|
2887
|
+
const autoAcceptRaw = getHyphenatedArg(args, "auto-accept");
|
|
2888
|
+
if (autoAcceptRaw !== undefined && autoAcceptRaw !== "safe") {
|
|
2889
|
+
throw new UsageError("--auto-accept only supports the value 'safe'.", "INVALID_FLAG_VALUE");
|
|
2890
|
+
}
|
|
2891
|
+
const targetArg = typeof args.target === "string" && args.target.trim() ? args.target.trim() : undefined;
|
|
2892
|
+
const taskArg = typeof args.task === "string" && args.task.trim() ? args.task : undefined;
|
|
2893
|
+
const dryRun = getHyphenatedBoolean(args, "dry-run");
|
|
2894
|
+
const autoAccept = autoAcceptRaw === "safe" ? "safe" : undefined;
|
|
2895
|
+
const limitRaw = parsePositiveIntFlag(args.limit ?? undefined);
|
|
2896
|
+
const timeoutRaw = getHyphenatedArg(args, "timeout-ms");
|
|
2897
|
+
const timeoutMs = timeoutRaw !== undefined ? parseInt(timeoutRaw, 10) : undefined;
|
|
2898
|
+
if (timeoutMs !== undefined && (Number.isNaN(timeoutMs) || timeoutMs <= 0)) {
|
|
2899
|
+
throw new UsageError(`Invalid --timeout-ms value: "${timeoutRaw}". Must be a positive integer.`);
|
|
2900
|
+
}
|
|
2901
|
+
const parseNonNegativeCooldownDays = (raw, flagName) => {
|
|
2902
|
+
if (raw === undefined)
|
|
2903
|
+
return undefined;
|
|
2904
|
+
if (!/^\d+$/.test(raw.trim())) {
|
|
2905
|
+
throw new UsageError(`Invalid ${flagName} value: "${raw}". Must be a non-negative integer.`);
|
|
2906
|
+
}
|
|
2907
|
+
return parseInt(raw, 10);
|
|
2908
|
+
};
|
|
2909
|
+
const ignoreCooldown = getHyphenatedBoolean(args, "ignore-cooldown");
|
|
2910
|
+
const reflectCooldownRaw = getHyphenatedArg(args, "reflect-cooldown-days");
|
|
2911
|
+
const reflectCooldownDays = ignoreCooldown
|
|
2912
|
+
? 0
|
|
2913
|
+
: parseNonNegativeCooldownDays(reflectCooldownRaw, "--reflect-cooldown-days");
|
|
2914
|
+
const distillCooldownRaw = getHyphenatedArg(args, "distill-cooldown-days");
|
|
2915
|
+
const distillCooldownDays = ignoreCooldown
|
|
2916
|
+
? 0
|
|
2917
|
+
: parseNonNegativeCooldownDays(distillCooldownRaw, "--distill-cooldown-days");
|
|
2918
|
+
const consolidateCooldownRaw = getHyphenatedArg(args, "consolidate-cooldown-days");
|
|
2919
|
+
const consolidateCooldownDays = ignoreCooldown
|
|
2920
|
+
? 0
|
|
2921
|
+
: parseNonNegativeCooldownDays(consolidateCooldownRaw, "--consolidate-cooldown-days");
|
|
2922
|
+
const consolidateRecoveryRaw = getHyphenatedArg(args, "consolidate-recovery");
|
|
2923
|
+
const consolidateRecovery = consolidateRecoveryRaw === undefined
|
|
2924
|
+
? undefined
|
|
2925
|
+
: consolidateRecoveryRaw.trim().toLowerCase();
|
|
2926
|
+
if (consolidateRecovery !== undefined && consolidateRecovery !== "abort" && consolidateRecovery !== "clean") {
|
|
2927
|
+
throw new UsageError(`Invalid --consolidate-recovery value: "${consolidateRecoveryRaw}". Must be one of: abort, clean.`, "INVALID_FLAG_VALUE");
|
|
2928
|
+
}
|
|
2929
|
+
const minRetrievalCountRaw = getHyphenatedArg(args, "min-retrieval-count");
|
|
2930
|
+
const minRetrievalCount = parseNonNegativeCooldownDays(minRetrievalCountRaw, "--min-retrieval-count");
|
|
2931
|
+
const requireFeedbackSignal = getHyphenatedBoolean(args, "require-feedback-signal");
|
|
2932
|
+
const improveLogFile = path.join(getCacheDir(), "logs", "improve", `${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
2933
|
+
setLogFile(improveLogFile);
|
|
2934
|
+
let improveResult;
|
|
2935
|
+
try {
|
|
2936
|
+
improveResult = await akmImprove({
|
|
2937
|
+
scope: typeof args.scope === "string" && args.scope.trim() ? args.scope : undefined,
|
|
2938
|
+
task: taskArg,
|
|
2939
|
+
dryRun,
|
|
2940
|
+
target: targetArg,
|
|
2941
|
+
autoAccept,
|
|
2942
|
+
...(limitRaw !== undefined ? { limit: limitRaw } : {}),
|
|
2943
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
2944
|
+
...(reflectCooldownDays !== undefined ? { reflectCooldownDays } : {}),
|
|
2945
|
+
...(distillCooldownDays !== undefined ? { distillCooldownDays } : {}),
|
|
2946
|
+
...(consolidateCooldownDays !== undefined ? { consolidateCooldownDays } : {}),
|
|
2947
|
+
...(minRetrievalCount !== undefined ? { minRetrievalCount } : {}),
|
|
2948
|
+
...(requireFeedbackSignal ? { requireFeedbackSignal } : {}),
|
|
2949
|
+
consolidateOptions: {
|
|
2950
|
+
target: targetArg,
|
|
2951
|
+
dryRun,
|
|
2952
|
+
autoAccept,
|
|
2953
|
+
task: taskArg,
|
|
2954
|
+
...(consolidateRecovery !== undefined ? { recoveryMode: consolidateRecovery } : {}),
|
|
2955
|
+
},
|
|
2956
|
+
});
|
|
2957
|
+
}
|
|
2958
|
+
finally {
|
|
2959
|
+
clearLogFile();
|
|
2729
2960
|
}
|
|
2961
|
+
output("improve", improveResult);
|
|
2962
|
+
process.exit(0);
|
|
2730
2963
|
});
|
|
2731
2964
|
},
|
|
2732
2965
|
});
|
|
@@ -2742,6 +2975,7 @@ const proposeCommand = defineCommand({
|
|
|
2742
2975
|
type: { type: "positional", description: "Asset type (skill, command, knowledge, lesson, ...)", required: false },
|
|
2743
2976
|
name: { type: "positional", description: "Asset name (slug or path under the type dir)", required: false },
|
|
2744
2977
|
task: { type: "string", description: "Task description for the agent (what should the asset do?)" },
|
|
2978
|
+
file: { type: "string", description: "Read the task or prompt text from a UTF-8 file" },
|
|
2745
2979
|
profile: { type: "string", description: "Override the agent profile (defaults to agent.default)" },
|
|
2746
2980
|
"timeout-ms": { type: "string", description: "Override the agent CLI timeout in milliseconds" },
|
|
2747
2981
|
},
|
|
@@ -2750,15 +2984,21 @@ const proposeCommand = defineCommand({
|
|
|
2750
2984
|
// citty silently shows help and exits 0 when required positionals are
|
|
2751
2985
|
// omitted. Re-validate explicitly so the exit code is 2 (USAGE) and a
|
|
2752
2986
|
// structured JSON error reaches scripted callers.
|
|
2753
|
-
|
|
2754
|
-
|
|
2987
|
+
const taskFromFlag = typeof args.task === "string" ? args.task : undefined;
|
|
2988
|
+
const fileFromFlag = typeof args.file === "string" ? args.file : undefined;
|
|
2989
|
+
if (!args.type || !args.name || (!taskFromFlag && !fileFromFlag)) {
|
|
2990
|
+
throw new UsageError("Usage: akm propose <type> <name> (--task '<task>' | --file <path>).", "MISSING_REQUIRED_ARGUMENT", "Provide the asset type, name, and exactly one of --task or --file.");
|
|
2991
|
+
}
|
|
2992
|
+
if (taskFromFlag && fileFromFlag) {
|
|
2993
|
+
throw new UsageError("Pass exactly one of --task or --file.", "INVALID_FLAG_VALUE");
|
|
2755
2994
|
}
|
|
2995
|
+
const taskText = fileFromFlag ? fs.readFileSync(path.resolve(fileFromFlag), "utf8") : (taskFromFlag ?? "");
|
|
2756
2996
|
const timeoutRaw = args["timeout-ms"];
|
|
2757
2997
|
const timeoutMs = typeof timeoutRaw === "string" && timeoutRaw.trim() ? Number.parseInt(timeoutRaw, 10) : undefined;
|
|
2758
2998
|
const result = await akmPropose({
|
|
2759
2999
|
type: String(args.type),
|
|
2760
3000
|
name: String(args.name),
|
|
2761
|
-
task:
|
|
3001
|
+
task: taskText,
|
|
2762
3002
|
profile: typeof args.profile === "string" && args.profile.trim() ? args.profile : undefined,
|
|
2763
3003
|
...(timeoutMs !== undefined && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
|
|
2764
3004
|
});
|
|
@@ -2769,6 +3009,192 @@ const proposeCommand = defineCommand({
|
|
|
2769
3009
|
});
|
|
2770
3010
|
},
|
|
2771
3011
|
});
|
|
3012
|
+
const TASKS_SUBCOMMAND_SET = new Set([
|
|
3013
|
+
"add",
|
|
3014
|
+
"list",
|
|
3015
|
+
"show",
|
|
3016
|
+
"remove",
|
|
3017
|
+
"enable",
|
|
3018
|
+
"disable",
|
|
3019
|
+
"run",
|
|
3020
|
+
"history",
|
|
3021
|
+
"sync",
|
|
3022
|
+
"doctor",
|
|
3023
|
+
]);
|
|
3024
|
+
const GRAPH_SUBCOMMAND_SET = new Set(["summary", "entities", "relations", "related", "export"]);
|
|
3025
|
+
const tasksAddCommand = defineCommand({
|
|
3026
|
+
meta: { name: "add", description: "Register a new scheduled task and install it in the OS scheduler" },
|
|
3027
|
+
args: {
|
|
3028
|
+
id: { type: "positional", description: "Task id (used as filename and scheduler entry)", required: true },
|
|
3029
|
+
schedule: { type: "string", description: 'Cron-style schedule, e.g. "0 9 * * *" or "@daily"', required: true },
|
|
3030
|
+
workflow: { type: "string", description: "Workflow ref to invoke (e.g. workflow:my-flow)" },
|
|
3031
|
+
prompt: {
|
|
3032
|
+
type: "string",
|
|
3033
|
+
description: "Prompt for the configured agent harness — inline text, an asset ref like agent:foo, or ./path.md",
|
|
3034
|
+
},
|
|
3035
|
+
profile: { type: "string", description: "Agent profile to use for prompt targets (default: config.agent.default)" },
|
|
3036
|
+
params: { type: "string", description: "Workflow params as a JSON object" },
|
|
3037
|
+
description: { type: "string", description: "Human-readable description" },
|
|
3038
|
+
tags: { type: "string", description: "Comma-separated tags" },
|
|
3039
|
+
disabled: { type: "boolean", description: "Register but leave disabled in the OS scheduler", default: false },
|
|
3040
|
+
force: { type: "boolean", description: "Overwrite an existing task with the same id", default: false },
|
|
3041
|
+
},
|
|
3042
|
+
async run({ args }) {
|
|
3043
|
+
await runWithJsonErrors(async () => {
|
|
3044
|
+
const result = await akmTasksAdd({
|
|
3045
|
+
id: args.id,
|
|
3046
|
+
schedule: args.schedule,
|
|
3047
|
+
workflow: args.workflow,
|
|
3048
|
+
prompt: args.prompt,
|
|
3049
|
+
profile: args.profile,
|
|
3050
|
+
params: args.params,
|
|
3051
|
+
description: args.description,
|
|
3052
|
+
tags: args.tags
|
|
3053
|
+
? args.tags
|
|
3054
|
+
.split(/[\s,]+/)
|
|
3055
|
+
.map((s) => s.trim())
|
|
3056
|
+
.filter(Boolean)
|
|
3057
|
+
: undefined,
|
|
3058
|
+
disabled: args.disabled === true,
|
|
3059
|
+
force: args.force === true,
|
|
3060
|
+
});
|
|
3061
|
+
output("tasks-add", result);
|
|
3062
|
+
});
|
|
3063
|
+
},
|
|
3064
|
+
});
|
|
3065
|
+
const tasksListCommand = defineCommand({
|
|
3066
|
+
meta: { name: "list", description: "List scheduled tasks in the stash" },
|
|
3067
|
+
async run() {
|
|
3068
|
+
await runWithJsonErrors(async () => {
|
|
3069
|
+
const result = await akmTasksList();
|
|
3070
|
+
output("tasks-list", result);
|
|
3071
|
+
});
|
|
3072
|
+
},
|
|
3073
|
+
});
|
|
3074
|
+
const tasksShowCommand = defineCommand({
|
|
3075
|
+
meta: { name: "show", description: "Show a parsed task definition" },
|
|
3076
|
+
args: { id: { type: "positional", description: "Task id or task:<id>", required: true } },
|
|
3077
|
+
async run({ args }) {
|
|
3078
|
+
await runWithJsonErrors(async () => {
|
|
3079
|
+
const { id } = parseTaskRef(args.id);
|
|
3080
|
+
const result = await akmTasksShow(id);
|
|
3081
|
+
output("tasks-show", result);
|
|
3082
|
+
});
|
|
3083
|
+
},
|
|
3084
|
+
});
|
|
3085
|
+
const tasksRemoveCommand = defineCommand({
|
|
3086
|
+
meta: { name: "remove", description: "Delete a task file and uninstall it from the OS scheduler" },
|
|
3087
|
+
args: { id: { type: "positional", description: "Task id", required: true } },
|
|
3088
|
+
async run({ args }) {
|
|
3089
|
+
await runWithJsonErrors(async () => {
|
|
3090
|
+
const { id } = parseTaskRef(args.id);
|
|
3091
|
+
const result = await akmTasksRemove(id);
|
|
3092
|
+
output("tasks-remove", result);
|
|
3093
|
+
});
|
|
3094
|
+
},
|
|
3095
|
+
});
|
|
3096
|
+
const tasksEnableCommand = defineCommand({
|
|
3097
|
+
meta: { name: "enable", description: "Enable a previously-disabled task" },
|
|
3098
|
+
args: { id: { type: "positional", description: "Task id", required: true } },
|
|
3099
|
+
async run({ args }) {
|
|
3100
|
+
await runWithJsonErrors(async () => {
|
|
3101
|
+
const { id } = parseTaskRef(args.id);
|
|
3102
|
+
const result = await akmTasksSetEnabled(id, true);
|
|
3103
|
+
output("tasks-enable", result);
|
|
3104
|
+
});
|
|
3105
|
+
},
|
|
3106
|
+
});
|
|
3107
|
+
const tasksDisableCommand = defineCommand({
|
|
3108
|
+
meta: { name: "disable", description: "Disable a task in the OS scheduler without removing the file" },
|
|
3109
|
+
args: { id: { type: "positional", description: "Task id", required: true } },
|
|
3110
|
+
async run({ args }) {
|
|
3111
|
+
await runWithJsonErrors(async () => {
|
|
3112
|
+
const { id } = parseTaskRef(args.id);
|
|
3113
|
+
const result = await akmTasksSetEnabled(id, false);
|
|
3114
|
+
output("tasks-disable", result);
|
|
3115
|
+
});
|
|
3116
|
+
},
|
|
3117
|
+
});
|
|
3118
|
+
const tasksRunCommand = defineCommand({
|
|
3119
|
+
meta: {
|
|
3120
|
+
name: "run",
|
|
3121
|
+
description: "Execute a task now (this is what cron / launchd / schtasks invoke at the scheduled time)",
|
|
3122
|
+
},
|
|
3123
|
+
args: { id: { type: "positional", description: "Task id", required: true } },
|
|
3124
|
+
async run({ args }) {
|
|
3125
|
+
await runWithJsonErrors(async () => {
|
|
3126
|
+
const { id } = parseTaskRef(args.id);
|
|
3127
|
+
const envelope = await akmTasksRun(id);
|
|
3128
|
+
output("tasks-run", envelope);
|
|
3129
|
+
if (envelope.exitCode !== 0)
|
|
3130
|
+
process.exit(envelope.exitCode);
|
|
3131
|
+
});
|
|
3132
|
+
},
|
|
3133
|
+
});
|
|
3134
|
+
const tasksHistoryCommand = defineCommand({
|
|
3135
|
+
meta: { name: "history", description: "Show recent task run history" },
|
|
3136
|
+
args: {
|
|
3137
|
+
id: { type: "string", description: "Filter to one task id" },
|
|
3138
|
+
limit: { type: "string", description: "Maximum rows to return (default 50)" },
|
|
3139
|
+
},
|
|
3140
|
+
async run({ args }) {
|
|
3141
|
+
await runWithJsonErrors(async () => {
|
|
3142
|
+
const limit = parsePositiveIntFlag(args.limit ?? undefined);
|
|
3143
|
+
const result = await akmTasksHistory({ id: args.id, limit });
|
|
3144
|
+
output("tasks-history", result);
|
|
3145
|
+
});
|
|
3146
|
+
},
|
|
3147
|
+
});
|
|
3148
|
+
const tasksSyncCommand = defineCommand({
|
|
3149
|
+
meta: {
|
|
3150
|
+
name: "sync",
|
|
3151
|
+
description: "Reconcile the on-disk task files with the OS scheduler",
|
|
3152
|
+
},
|
|
3153
|
+
async run() {
|
|
3154
|
+
await runWithJsonErrors(async () => {
|
|
3155
|
+
const result = await akmTasksSync();
|
|
3156
|
+
output("tasks-sync", result);
|
|
3157
|
+
});
|
|
3158
|
+
},
|
|
3159
|
+
});
|
|
3160
|
+
const tasksDoctorCommand = defineCommand({
|
|
3161
|
+
meta: {
|
|
3162
|
+
name: "doctor",
|
|
3163
|
+
description: "Report the active scheduler backend, akm bin path, log dir, and supported schedule subset",
|
|
3164
|
+
},
|
|
3165
|
+
async run() {
|
|
3166
|
+
await runWithJsonErrors(async () => {
|
|
3167
|
+
const result = await akmTasksDoctor();
|
|
3168
|
+
output("tasks-doctor", result);
|
|
3169
|
+
});
|
|
3170
|
+
},
|
|
3171
|
+
});
|
|
3172
|
+
const tasksCommand = defineCommand({
|
|
3173
|
+
meta: {
|
|
3174
|
+
name: "tasks",
|
|
3175
|
+
description: "Schedule workflows or prompts via the OS-native scheduler (cron / launchd / schtasks)",
|
|
3176
|
+
},
|
|
3177
|
+
subCommands: {
|
|
3178
|
+
add: tasksAddCommand,
|
|
3179
|
+
list: tasksListCommand,
|
|
3180
|
+
show: tasksShowCommand,
|
|
3181
|
+
remove: tasksRemoveCommand,
|
|
3182
|
+
enable: tasksEnableCommand,
|
|
3183
|
+
disable: tasksDisableCommand,
|
|
3184
|
+
run: tasksRunCommand,
|
|
3185
|
+
history: tasksHistoryCommand,
|
|
3186
|
+
sync: tasksSyncCommand,
|
|
3187
|
+
doctor: tasksDoctorCommand,
|
|
3188
|
+
},
|
|
3189
|
+
run({ args }) {
|
|
3190
|
+
return runWithJsonErrors(async () => {
|
|
3191
|
+
if (hasSubcommand(args, TASKS_SUBCOMMAND_SET))
|
|
3192
|
+
return;
|
|
3193
|
+
const result = await akmTasksList();
|
|
3194
|
+
output("tasks-list", result);
|
|
3195
|
+
});
|
|
3196
|
+
},
|
|
3197
|
+
});
|
|
2772
3198
|
const main = defineCommand({
|
|
2773
3199
|
meta: {
|
|
2774
3200
|
name: "akm",
|
|
@@ -2789,7 +3215,9 @@ const main = defineCommand({
|
|
|
2789
3215
|
setup: setupCommand,
|
|
2790
3216
|
init: initCommand,
|
|
2791
3217
|
index: indexCommand,
|
|
3218
|
+
health: healthCommand,
|
|
2792
3219
|
info: infoCommand,
|
|
3220
|
+
graph: graphCommand,
|
|
2793
3221
|
add: addCommand,
|
|
2794
3222
|
list: listCommand,
|
|
2795
3223
|
remove: removeCommand,
|
|
@@ -2810,18 +3238,23 @@ const main = defineCommand({
|
|
|
2810
3238
|
feedback: feedbackCommand,
|
|
2811
3239
|
history: historyCommand,
|
|
2812
3240
|
events: eventsCommand,
|
|
2813
|
-
|
|
2814
|
-
|
|
3241
|
+
agent: agentCommand,
|
|
3242
|
+
lint: lintCommand,
|
|
3243
|
+
improve: improveCommand,
|
|
2815
3244
|
propose: proposeCommand,
|
|
2816
|
-
|
|
3245
|
+
proposals: proposalsCommand,
|
|
3246
|
+
accept: acceptCommand,
|
|
3247
|
+
reject: rejectCommand,
|
|
3248
|
+
diff: diffCommand,
|
|
2817
3249
|
help: helpCommand,
|
|
2818
3250
|
hints: hintsCommand,
|
|
2819
3251
|
completions: completionsCommand,
|
|
2820
3252
|
vault: vaultCommand,
|
|
2821
3253
|
wiki: wikiCommand,
|
|
3254
|
+
tasks: tasksCommand,
|
|
2822
3255
|
},
|
|
2823
3256
|
});
|
|
2824
|
-
const CONFIG_SUBCOMMAND_SET = new Set(["path", "list", "get", "set", "unset"]);
|
|
3257
|
+
const CONFIG_SUBCOMMAND_SET = new Set(["path", "list", "show", "get", "set", "unset"]);
|
|
2825
3258
|
const VAULT_SUBCOMMAND_SET = new Set(["list", "path", "run", "create", "set", "unset"]);
|
|
2826
3259
|
const WIKI_SUBCOMMAND_SET = new Set([
|
|
2827
3260
|
"create",
|
|
@@ -2835,7 +3268,6 @@ const WIKI_SUBCOMMAND_SET = new Set([
|
|
|
2835
3268
|
"lint",
|
|
2836
3269
|
"ingest",
|
|
2837
3270
|
]);
|
|
2838
|
-
const SHOW_VIEW_MODES = new Set(["toc", "frontmatter", "full", "section", "lines"]);
|
|
2839
3271
|
// ── Exit codes ──────────────────────────────────────────────────────────────
|
|
2840
3272
|
const EXIT_GENERAL = 1;
|
|
2841
3273
|
const EXIT_USAGE = 2;
|
|
@@ -2849,6 +3281,7 @@ process.argv = normalizeShowArgv(process.argv);
|
|
|
2849
3281
|
// invalid; surface it through the same JSON-error path the rest of the CLI uses
|
|
2850
3282
|
// rather than letting the raw exception escape with a stack trace.
|
|
2851
3283
|
try {
|
|
3284
|
+
applyEarlyStderrFlags(process.argv);
|
|
2852
3285
|
initOutputMode(process.argv, loadConfig().output ?? {});
|
|
2853
3286
|
}
|
|
2854
3287
|
catch (error) {
|
|
@@ -2873,16 +3306,7 @@ function classifyExitCode(error) {
|
|
|
2873
3306
|
}
|
|
2874
3307
|
async function runWithJsonErrors(fn) {
|
|
2875
3308
|
try {
|
|
2876
|
-
|
|
2877
|
-
if (process.argv.includes("--quiet") || process.argv.includes("-q")) {
|
|
2878
|
-
setQuiet(true);
|
|
2879
|
-
}
|
|
2880
|
-
// Apply --verbose flag early so per-spec diagnostics (gated behind
|
|
2881
|
-
// `isVerbose()` in src/core/warn.ts) are restored. The `AKM_VERBOSE`
|
|
2882
|
-
// env var still wins regardless — see warn.ts for the precedence rule.
|
|
2883
|
-
if (process.argv.includes("--verbose")) {
|
|
2884
|
-
setVerbose(true);
|
|
2885
|
-
}
|
|
3309
|
+
applyEarlyStderrFlags(process.argv);
|
|
2886
3310
|
await fn();
|
|
2887
3311
|
}
|
|
2888
3312
|
catch (error) {
|
|
@@ -2909,87 +3333,6 @@ function extractHint(error) {
|
|
|
2909
3333
|
}
|
|
2910
3334
|
return undefined;
|
|
2911
3335
|
}
|
|
2912
|
-
function hasConfigSubcommand(args) {
|
|
2913
|
-
const command = Array.isArray(args._) ? args._[0] : undefined;
|
|
2914
|
-
return typeof command === "string" && CONFIG_SUBCOMMAND_SET.has(command);
|
|
2915
|
-
}
|
|
2916
|
-
function hasVaultSubcommand(args) {
|
|
2917
|
-
const command = Array.isArray(args._) ? args._[0] : undefined;
|
|
2918
|
-
return typeof command === "string" && VAULT_SUBCOMMAND_SET.has(command);
|
|
2919
|
-
}
|
|
2920
|
-
function hasWikiSubcommand(args) {
|
|
2921
|
-
const command = Array.isArray(args._) ? args._[0] : undefined;
|
|
2922
|
-
return typeof command === "string" && WIKI_SUBCOMMAND_SET.has(command);
|
|
2923
|
-
}
|
|
2924
|
-
/**
|
|
2925
|
-
* Normalize argv so positional view-mode arguments after the asset ref
|
|
2926
|
-
* are rewritten into internal flags that citty can parse.
|
|
2927
|
-
*
|
|
2928
|
-
* Converts:
|
|
2929
|
-
* akm show knowledge:guide.md toc → akm show knowledge:guide.md --akmView toc
|
|
2930
|
-
* akm show knowledge:guide.md section Auth → akm show knowledge:guide.md --akmView section --akmHeading Auth
|
|
2931
|
-
* akm show knowledge:guide.md lines 1 50 → akm show knowledge:guide.md --akmView lines --akmStart 1 --akmEnd 50
|
|
2932
|
-
*
|
|
2933
|
-
* Legacy `--view` is intentionally unsupported.
|
|
2934
|
-
* Returns a new array; the input is never modified.
|
|
2935
|
-
*/
|
|
2936
|
-
function normalizeShowArgv(argv) {
|
|
2937
|
-
// argv[0]=bun argv[1]=script argv[2]=subcommand argv[3]=ref argv[4..]=rest
|
|
2938
|
-
if (argv[2] !== "show")
|
|
2939
|
-
return argv;
|
|
2940
|
-
if (argv.includes("--view") || argv.includes("--heading") || argv.includes("--start") || argv.includes("--end")) {
|
|
2941
|
-
throw new UsageError('Legacy show flags are no longer supported. Use positional syntax like `akm show knowledge:guide toc` or `akm show knowledge:guide section "Auth"`.');
|
|
2942
|
-
}
|
|
2943
|
-
// Separate global flags from positional/show-specific args
|
|
2944
|
-
const prefix = argv.slice(0, 3); // [bun, script, show]
|
|
2945
|
-
const rest = argv.slice(3);
|
|
2946
|
-
const globalFlags = [];
|
|
2947
|
-
const showArgs = [];
|
|
2948
|
-
for (let i = 0; i < rest.length; i++) {
|
|
2949
|
-
const arg = rest[i];
|
|
2950
|
-
if (arg === "--quiet" || arg === "-q" || arg === "--for-agent" || arg === "--for-agent=true") {
|
|
2951
|
-
globalFlags.push(arg);
|
|
2952
|
-
continue;
|
|
2953
|
-
}
|
|
2954
|
-
if (arg.startsWith("--format=") || arg.startsWith("--detail=")) {
|
|
2955
|
-
globalFlags.push(arg);
|
|
2956
|
-
continue;
|
|
2957
|
-
}
|
|
2958
|
-
if (arg === "--format" || arg === "--detail") {
|
|
2959
|
-
globalFlags.push(arg);
|
|
2960
|
-
if (rest[i + 1] !== undefined) {
|
|
2961
|
-
globalFlags.push(rest[i + 1]);
|
|
2962
|
-
i++;
|
|
2963
|
-
}
|
|
2964
|
-
continue;
|
|
2965
|
-
}
|
|
2966
|
-
showArgs.push(arg);
|
|
2967
|
-
}
|
|
2968
|
-
// showArgs[0] = ref, showArgs[1] = potential view mode, showArgs[2..] = view params
|
|
2969
|
-
const ref = showArgs[0];
|
|
2970
|
-
const viewMode = showArgs[1];
|
|
2971
|
-
if (!ref || !viewMode || !SHOW_VIEW_MODES.has(viewMode)) {
|
|
2972
|
-
return argv;
|
|
2973
|
-
}
|
|
2974
|
-
const result = [...prefix, ref, "--akmView", viewMode];
|
|
2975
|
-
if (viewMode === "section") {
|
|
2976
|
-
// Next arg is the heading name; pass empty string when missing so the
|
|
2977
|
-
// show handler can produce a clear "section not found" error.
|
|
2978
|
-
const heading = showArgs[2] ?? "";
|
|
2979
|
-
result.push("--akmHeading", heading);
|
|
2980
|
-
}
|
|
2981
|
-
else if (viewMode === "lines") {
|
|
2982
|
-
// Next two args are start and end
|
|
2983
|
-
const start = showArgs[2];
|
|
2984
|
-
const end = showArgs[3];
|
|
2985
|
-
if (start)
|
|
2986
|
-
result.push("--akmStart", start);
|
|
2987
|
-
if (end)
|
|
2988
|
-
result.push("--akmEnd", end);
|
|
2989
|
-
}
|
|
2990
|
-
result.push(...globalFlags);
|
|
2991
|
-
return result;
|
|
2992
|
-
}
|
|
2993
3336
|
// ── Hints (embedded AGENTS.md) ──────────────────────────────────────────────
|
|
2994
3337
|
function loadHints(detail = "normal") {
|
|
2995
3338
|
const filename = detail === "full" ? "AGENTS.full.md" : "AGENTS.md";
|