akm-cli 0.9.0-rc.1 → 0.9.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +223 -9
- package/SECURITY.md +23 -24
- package/dist/assets/help/help-improve.md +10 -10
- package/dist/assets/hints/cli-hints-full.md +44 -10
- package/dist/assets/hints/cli-hints-short.md +6 -2
- package/dist/assets/{profiles → improve-strategies}/default.json +1 -0
- package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +1 -1
- package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +2 -3
- package/dist/assets/{profiles → improve-strategies}/reflect-distill.json +3 -4
- package/dist/assets/stash-skeleton/README.md +28 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
- package/dist/assets/tasks/core/backup.yml +1 -0
- package/dist/assets/tasks/core/extract.yml +1 -0
- package/dist/assets/tasks/core/improve.yml +1 -0
- package/dist/assets/tasks/core/index-refresh.yml +1 -0
- package/dist/assets/tasks/core/sync.yml +1 -0
- package/dist/assets/tasks/core/version-check.yml +1 -0
- package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
- package/dist/assets/templates/html/health.html +5 -1
- package/dist/cli/config-migrate.js +31 -138
- package/dist/cli/config-validate.js +10 -8
- package/dist/cli.js +48 -14
- package/dist/commands/agent/agent-dispatch.js +17 -16
- package/dist/commands/agent/agent-support.js +0 -24
- package/dist/commands/agent/contribute-cli.js +5 -15
- package/dist/commands/backup-cli.js +54 -0
- package/dist/commands/config-cli.js +45 -159
- package/dist/commands/env/secret.js +8 -5
- package/dist/commands/health/checks.js +130 -83
- package/dist/commands/health/html-report.js +4 -0
- package/dist/commands/health/improve-metrics.js +30 -32
- package/dist/commands/health/llm-usage.js +19 -19
- package/dist/commands/health/md-report.js +4 -0
- package/dist/commands/health/metrics.js +2 -1
- package/dist/commands/health/surfaces.js +5 -4
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/consolidate/chunking.js +2 -2
- package/dist/commands/improve/consolidate.js +28 -25
- package/dist/commands/improve/distill/promote-memory.js +5 -12
- package/dist/commands/improve/distill/quality-gate.js +5 -7
- package/dist/commands/improve/distill.js +16 -5
- package/dist/commands/improve/eligibility.js +22 -12
- package/dist/commands/improve/extract-cli.js +47 -19
- package/dist/commands/improve/extract.js +110 -62
- package/dist/commands/improve/improve-cli.js +38 -16
- package/dist/commands/improve/improve-result-file.js +30 -24
- package/dist/commands/improve/improve-strategies.js +137 -0
- package/dist/commands/improve/improve.js +60 -30
- package/dist/commands/improve/locks.js +66 -45
- package/dist/commands/improve/loop-stages.js +75 -33
- package/dist/commands/improve/memory/memory-belief.js +79 -7
- package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
- package/dist/commands/improve/preparation.js +71 -73
- package/dist/commands/improve/procedural.js +3 -2
- package/dist/commands/improve/recombine.js +2 -1
- package/dist/commands/improve/reflect.js +119 -214
- package/dist/commands/improve/shared.js +11 -5
- package/dist/commands/lint/base-linter.js +152 -42
- package/dist/commands/mv-cli.js +809 -0
- package/dist/commands/proposal/proposal-cli.js +18 -8
- package/dist/commands/proposal/propose.js +64 -69
- package/dist/commands/read/knowledge.js +436 -4
- package/dist/commands/read/remember-cli.js +39 -2
- package/dist/commands/read/search-cli.js +6 -1
- package/dist/commands/registry-cli.js +29 -14
- package/dist/commands/remember.js +2 -0
- package/dist/commands/sources/init.js +13 -14
- package/dist/commands/sources/schema-repair.js +2 -4
- package/dist/commands/sources/source-add.js +62 -73
- package/dist/commands/sources/source-manage.js +50 -46
- package/dist/commands/sources/stash-cli.js +41 -4
- package/dist/commands/tasks/default-tasks.js +12 -12
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +113 -18
- package/dist/commands/wiki-cli.js +9 -10
- package/dist/core/asset/frontmatter.js +12 -2
- package/dist/core/common.js +5 -3
- package/dist/core/config/config-io.js +28 -17
- package/dist/core/config/config-schema.js +358 -66
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config-version.js +29 -0
- package/dist/core/config/config-walker.js +98 -27
- package/dist/core/config/config.js +132 -266
- package/dist/core/config/deep-merge.js +41 -0
- package/dist/core/config/engine-semantics.js +32 -0
- package/dist/core/errors.js +2 -2
- package/dist/core/extra-params.js +61 -0
- package/dist/core/file-lock.js +201 -56
- package/dist/core/improve-result.js +178 -0
- package/dist/core/maintenance-barrier.js +119 -0
- package/dist/core/migration-backup.js +416 -0
- package/dist/core/paths.js +3 -0
- package/dist/core/redaction.js +358 -0
- package/dist/core/state/migrations.js +17 -2
- package/dist/core/state-db.js +44 -1
- package/dist/indexer/db/db.js +116 -1
- package/dist/indexer/graph/graph-extraction.js +28 -16
- package/dist/indexer/index-writer-lock.js +31 -24
- package/dist/indexer/index-written-assets.js +15 -6
- package/dist/indexer/indexer.js +47 -2
- package/dist/indexer/passes/memory-inference.js +10 -6
- package/dist/indexer/passes/metadata.js +250 -0
- package/dist/indexer/search/db-search.js +111 -44
- package/dist/indexer/search/fts-query.js +41 -0
- package/dist/indexer/search/ranking-contributors.js +48 -0
- package/dist/indexer/search/ranking.js +36 -23
- package/dist/indexer/search/search-fields.js +11 -1
- package/dist/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -52
- package/dist/integrations/agent/config.js +3 -143
- package/dist/integrations/agent/detect.js +17 -2
- package/dist/integrations/agent/engine-resolution.js +202 -0
- package/dist/integrations/agent/index.js +1 -2
- package/dist/integrations/agent/model-aliases.js +7 -2
- package/dist/integrations/agent/profiles.js +6 -99
- package/dist/integrations/agent/runner-dispatch.js +76 -13
- package/dist/integrations/agent/runner.js +76 -207
- package/dist/integrations/agent/spawn.js +4 -6
- package/dist/integrations/harnesses/aider/agent-builder.js +2 -3
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +2 -3
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +2 -3
- package/dist/integrations/harnesses/claude/index.js +0 -2
- package/dist/integrations/harnesses/codex/agent-builder.js +2 -3
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +2 -3
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +2 -3
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +1 -24
- package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
- package/dist/integrations/harnesses/opencode/index.js +0 -6
- package/dist/integrations/harnesses/opencode-sdk/harness.js +1 -6
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +274 -191
- package/dist/integrations/harnesses/openhands/agent-builder.js +2 -3
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +2 -3
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -32
- package/dist/integrations/lockfile.js +32 -21
- package/dist/llm/client.js +48 -14
- package/dist/llm/feature-gate.js +15 -47
- package/dist/llm/graph-extract.js +1 -1
- package/dist/llm/index-passes.js +8 -42
- package/dist/llm/memory-infer-impl.js +1 -1
- package/dist/llm/usage-persist.js +4 -0
- package/dist/llm/usage-telemetry.js +35 -5
- package/dist/output/shapes/helpers.js +2 -1
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/helpers.js +3 -1
- package/dist/schemas/akm-config.json +11013 -8600
- package/dist/schemas/akm-task.json +87 -0
- package/dist/schemas/akm-workflow.json +57 -13
- package/dist/scripts/migrate-storage.js +8591 -509
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8623 -484
- package/dist/setup/detected-engines.js +142 -0
- package/dist/setup/engine-config.js +89 -0
- package/dist/setup/setup.js +236 -132
- package/dist/setup/steps/connection.js +61 -32
- package/dist/setup/steps/platforms.js +4 -4
- package/dist/setup/steps.js +3 -2
- package/dist/storage/database.js +13 -1
- package/dist/storage/engines/sqlite-migrations.js +1 -0
- package/dist/storage/repositories/improve-runs-repository.js +5 -5
- package/dist/storage/repositories/task-history-repository.js +78 -0
- package/dist/storage/repositories/workflow-runs-repository.js +9 -8
- package/dist/tasks/parser.js +138 -52
- package/dist/tasks/runner.js +71 -75
- package/dist/tasks/schema.js +1 -1
- package/dist/tasks/validator.js +11 -6
- package/dist/wiki/wiki.js +9 -8
- package/dist/workflows/authoring/workflow-program-template.yaml +3 -3
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/db.js +65 -13
- package/dist/workflows/exec/brief.js +18 -24
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +124 -65
- package/dist/workflows/exec/report.js +93 -33
- package/dist/workflows/exec/run-workflow.js +38 -25
- package/dist/workflows/exec/scheduler.js +12 -41
- package/dist/workflows/exec/step-work.js +91 -35
- package/dist/workflows/ir/compile.js +13 -26
- package/dist/workflows/ir/freeze.js +243 -0
- package/dist/workflows/ir/plan-hash.js +40 -5
- package/dist/workflows/ir/schema.js +542 -1
- package/dist/workflows/parser.js +7 -0
- package/dist/workflows/program/parser.js +132 -23
- package/dist/workflows/program/project.js +3 -4
- package/dist/workflows/program/schema.js +2 -2
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/plan-classifier.js +187 -0
- package/dist/workflows/runtime/runs.js +54 -47
- package/dist/workflows/runtime/workflow-asset-loader.js +0 -22
- package/dist/workflows/validator.js +25 -0
- package/docs/data-and-telemetry.md +2 -2
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +5 -4
- package/docs/migration/v0.8-to-v0.9.md +401 -0
- package/package.json +4 -2
- package/schemas/akm-config.json +16638 -0
- package/schemas/akm-task.json +87 -0
- package/schemas/akm-workflow.json +372 -0
- package/dist/commands/improve/improve-profiles.js +0 -168
- package/dist/core/config/config-migration.js +0 -602
- package/dist/core/deep-merge.js +0 -38
- package/dist/llm/call-ai.js +0 -62
- package/dist/setup/legacy-config.js +0 -106
- package/docs/README.md +0 -104
- /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
|
@@ -50,6 +50,8 @@ export function buildMemoryFrontmatter(fields) {
|
|
|
50
50
|
obj.tags = fields.tags;
|
|
51
51
|
if (fields.source?.trim())
|
|
52
52
|
obj.source = fields.source;
|
|
53
|
+
if (fields.xrefs && fields.xrefs.length > 0)
|
|
54
|
+
obj.xrefs = fields.xrefs;
|
|
53
55
|
if (fields.observed_at?.trim())
|
|
54
56
|
obj.observed_at = fields.observed_at;
|
|
55
57
|
if (fields.expires?.trim())
|
|
@@ -11,7 +11,7 @@ import { spawnSync } from "node:child_process";
|
|
|
11
11
|
import fs from "node:fs";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { TYPE_DIRS } from "../../core/asset/asset-spec.js";
|
|
14
|
-
import {
|
|
14
|
+
import { mutateConfig } from "../../core/config/config.js";
|
|
15
15
|
import { ConfigError } from "../../core/errors.js";
|
|
16
16
|
import { assertSafeStashDir, getBinDir, getConfigPath, getDefaultStashDir } from "../../core/paths.js";
|
|
17
17
|
import { ensureRg } from "../../core/ripgrep/install.js";
|
|
@@ -115,21 +115,20 @@ async function akmInitReal(options) {
|
|
|
115
115
|
// Otherwise (--dir + existing default + no --set-default) leave the default
|
|
116
116
|
// pointer alone; the target dir is still scaffolded above.
|
|
117
117
|
const configPath = getConfigPath();
|
|
118
|
-
const existing = loadUserConfig();
|
|
119
|
-
const existingStashDir = existing.stashDir;
|
|
120
|
-
const shouldPersist = !dirExplicitlyProvided || !existingStashDir || setDefault;
|
|
121
118
|
let defaultStashUpdated = false;
|
|
122
119
|
let previousStashDir;
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
120
|
+
if (options?.persistConfig !== false) {
|
|
121
|
+
const result = mutateConfig((latest) => {
|
|
122
|
+
const shouldPersist = !dirExplicitlyProvided || !latest.stashDir || setDefault;
|
|
123
|
+
if (!shouldPersist) {
|
|
124
|
+
previousStashDir = latest.stashDir;
|
|
125
|
+
return latest;
|
|
126
|
+
}
|
|
127
|
+
if (latest.stashDir === stashDir)
|
|
128
|
+
return latest;
|
|
129
|
+
return { ...latest, stashDir };
|
|
130
|
+
});
|
|
131
|
+
defaultStashUpdated = result.written;
|
|
133
132
|
}
|
|
134
133
|
// Ensure ripgrep is available (install to cache/bin if needed)
|
|
135
134
|
let ripgrep;
|
|
@@ -39,8 +39,8 @@ const SCHEMA_REPAIR_WINDOW_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
|
39
39
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
40
40
|
/**
|
|
41
41
|
* Run the schema-repair loop for a batch of validation failures.
|
|
42
|
-
* Returns a list of per-asset outcome records and the set of refs
|
|
43
|
-
*
|
|
42
|
+
* Returns a list of per-asset outcome records and the set of refs whose live
|
|
43
|
+
* files were repaired. Queued proposals never count as live repairs.
|
|
44
44
|
*/
|
|
45
45
|
export async function runSchemaRepairPass(failures, options) {
|
|
46
46
|
const repairs = [];
|
|
@@ -167,8 +167,6 @@ export async function runSchemaRepairPass(failures, options) {
|
|
|
167
167
|
outcome: "queued",
|
|
168
168
|
proposalId: proposalResult.id,
|
|
169
169
|
});
|
|
170
|
-
// Mark as repaired so the caller removes it from the validation-failure set.
|
|
171
|
-
repairedRefs.add(failure.ref);
|
|
172
170
|
}
|
|
173
171
|
catch (e) {
|
|
174
172
|
appendEvent({
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { isHttpUrl, resolveStashDir } from "../../core/common.js";
|
|
7
|
-
import { getSources, loadConfig,
|
|
7
|
+
import { getSources, loadConfig, mutateConfig } from "../../core/config/config.js";
|
|
8
8
|
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
9
9
|
import { akmIndex } from "../../indexer/indexer.js";
|
|
10
10
|
import { upsertLockEntry } from "../../integrations/lockfile.js";
|
|
@@ -67,38 +67,33 @@ export async function registerWikiSource(input) {
|
|
|
67
67
|
async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName) {
|
|
68
68
|
const stashRoot = detectStashRoot(sourcePath);
|
|
69
69
|
const resolvedPath = path.resolve(stashRoot);
|
|
70
|
-
const config = loadUserConfig();
|
|
71
70
|
// Derive the canonical name: explicit --name wins, then wiki name, then readable path.
|
|
72
71
|
const derivedName = explicitName ?? wikiName ?? toReadableId(resolvedPath);
|
|
73
|
-
// Check for duplicates in sources[]
|
|
74
|
-
const sources = [...getSources(config)];
|
|
75
|
-
const existing = sources.find((s) => s.type === "filesystem" && s.path && path.resolve(s.path) === resolvedPath);
|
|
76
72
|
let persistedEntry;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// If --name was explicitly supplied, update the persisted name.
|
|
90
|
-
if (explicitName && existing.name !== explicitName) {
|
|
91
|
-
existing.name = explicitName;
|
|
92
|
-
changed = true;
|
|
73
|
+
mutateConfig((config) => {
|
|
74
|
+
const sources = [...getSources(config)];
|
|
75
|
+
const index = sources.findIndex((source) => source.type === "filesystem" && source.path && path.resolve(source.path) === resolvedPath);
|
|
76
|
+
if (index < 0) {
|
|
77
|
+
persistedEntry = {
|
|
78
|
+
type: "filesystem",
|
|
79
|
+
path: resolvedPath,
|
|
80
|
+
name: derivedName,
|
|
81
|
+
...(wikiName ? { wikiName } : {}),
|
|
82
|
+
};
|
|
83
|
+
sources.push(persistedEntry);
|
|
84
|
+
return { ...config, sources };
|
|
93
85
|
}
|
|
94
|
-
|
|
86
|
+
const existing = { ...sources[index] };
|
|
87
|
+
if (explicitName)
|
|
88
|
+
existing.name = explicitName;
|
|
89
|
+
if (wikiName)
|
|
95
90
|
existing.wikiName = wikiName;
|
|
96
|
-
changed = true;
|
|
97
|
-
}
|
|
98
|
-
if (changed)
|
|
99
|
-
saveConfig({ ...config, sources });
|
|
100
91
|
persistedEntry = existing;
|
|
101
|
-
|
|
92
|
+
if (JSON.stringify(existing) === JSON.stringify(sources[index]))
|
|
93
|
+
return config;
|
|
94
|
+
sources[index] = existing;
|
|
95
|
+
return { ...config, sources };
|
|
96
|
+
});
|
|
102
97
|
const index = await akmIndex({ stashDir });
|
|
103
98
|
const updatedConfig = loadConfig();
|
|
104
99
|
return {
|
|
@@ -108,9 +103,9 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
|
|
|
108
103
|
sourceAdded: {
|
|
109
104
|
type: "filesystem",
|
|
110
105
|
path: resolvedPath,
|
|
111
|
-
name: persistedEntry
|
|
106
|
+
name: persistedEntry?.name ?? toReadableId(resolvedPath),
|
|
112
107
|
stashRoot: resolvedPath,
|
|
113
|
-
...(persistedEntry
|
|
108
|
+
...(persistedEntry?.wikiName ? { wiki: persistedEntry.wikiName } : {}),
|
|
114
109
|
},
|
|
115
110
|
config: {
|
|
116
111
|
sourceCount: getSources(updatedConfig).length,
|
|
@@ -128,33 +123,32 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
|
|
|
128
123
|
async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
|
|
129
124
|
const allowPrivateHosts = shouldAllowPrivateWebsiteUrlForTests(ref);
|
|
130
125
|
const normalizedUrl = validateWebsiteInputUrl(ref, { allowPrivateHosts });
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
else {
|
|
146
|
-
let changed = false;
|
|
147
|
-
if (options && Object.keys(options).length > 0) {
|
|
148
|
-
entry.options = { ...entry.options, ...options };
|
|
149
|
-
changed = true;
|
|
150
|
-
}
|
|
151
|
-
if (wikiName && entry.wikiName !== wikiName) {
|
|
152
|
-
entry.wikiName = wikiName;
|
|
153
|
-
changed = true;
|
|
126
|
+
let entry;
|
|
127
|
+
mutateConfig((config) => {
|
|
128
|
+
const sources = [...getSources(config)];
|
|
129
|
+
const index = sources.findIndex((source) => source.type === "website" && source.url === normalizedUrl);
|
|
130
|
+
if (index < 0) {
|
|
131
|
+
entry = {
|
|
132
|
+
type: "website",
|
|
133
|
+
url: normalizedUrl,
|
|
134
|
+
name: name ?? toWebsiteName(normalizedUrl),
|
|
135
|
+
...(options && Object.keys(options).length > 0 ? { options } : {}),
|
|
136
|
+
...(wikiName ? { wikiName } : {}),
|
|
137
|
+
};
|
|
138
|
+
sources.push(entry);
|
|
139
|
+
return { ...config, sources };
|
|
154
140
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
141
|
+
const existing = { ...sources[index] };
|
|
142
|
+
if (options && Object.keys(options).length > 0)
|
|
143
|
+
existing.options = { ...existing.options, ...options };
|
|
144
|
+
if (wikiName)
|
|
145
|
+
existing.wikiName = wikiName;
|
|
146
|
+
entry = existing;
|
|
147
|
+
if (JSON.stringify(existing) === JSON.stringify(sources[index]))
|
|
148
|
+
return config;
|
|
149
|
+
sources[index] = existing;
|
|
150
|
+
return { ...config, sources };
|
|
151
|
+
});
|
|
158
152
|
const cachePaths = await ensureWebsiteMirror(entry, {
|
|
159
153
|
requireStashDir: true,
|
|
160
154
|
...(allowPrivateHosts ? { allowPrivateHosts: true } : {}),
|
|
@@ -168,9 +162,9 @@ async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
|
|
|
168
162
|
sourceAdded: {
|
|
169
163
|
type: "website",
|
|
170
164
|
url: normalizedUrl,
|
|
171
|
-
name: entry
|
|
165
|
+
name: entry?.name,
|
|
172
166
|
stashRoot: cachePaths.stashDir,
|
|
173
|
-
...(entry
|
|
167
|
+
...(entry?.wikiName ? { wiki: entry.wikiName } : {}),
|
|
174
168
|
},
|
|
175
169
|
config: {
|
|
176
170
|
sourceCount: getSources(updatedConfig).length,
|
|
@@ -258,25 +252,20 @@ async function addRegistryStash(ref, stashDir, writable, wikiName) {
|
|
|
258
252
|
}
|
|
259
253
|
/** Persist or replace an installed stash entry in the user config. */
|
|
260
254
|
export function upsertInstalledRegistryEntry(entry) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const nextConfig = { ...current, installed: nextInstalled };
|
|
266
|
-
saveConfig(nextConfig);
|
|
267
|
-
return nextConfig;
|
|
255
|
+
return mutateConfig((current) => {
|
|
256
|
+
const withoutExisting = (current.installed ?? []).filter((item) => item.id !== entry.id);
|
|
257
|
+
return { ...current, installed: [...withoutExisting, normalizeInstalledEntry(entry)] };
|
|
258
|
+
}).config;
|
|
268
259
|
}
|
|
269
260
|
/** Remove an installed stash entry from the user config. */
|
|
270
261
|
export function removeInstalledRegistryEntry(id) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
installed: nextInstalled.length > 0 ? nextInstalled : undefined
|
|
277
|
-
};
|
|
278
|
-
saveConfig(nextConfig);
|
|
279
|
-
return nextConfig;
|
|
262
|
+
return mutateConfig((current) => {
|
|
263
|
+
const currentInstalled = current.installed ?? [];
|
|
264
|
+
const nextInstalled = currentInstalled.filter((item) => item.id !== id);
|
|
265
|
+
if (nextInstalled.length === currentInstalled.length)
|
|
266
|
+
return current;
|
|
267
|
+
return { ...current, installed: nextInstalled.length > 0 ? nextInstalled : undefined };
|
|
268
|
+
}).config;
|
|
280
269
|
}
|
|
281
270
|
function normalizeInstalledEntry(entry) {
|
|
282
271
|
return {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { isRemoteUrl } from "../../core/common.js";
|
|
6
|
-
import { getSources, loadConfig,
|
|
6
|
+
import { getSources, loadConfig, mutateConfig } from "../../core/config/config.js";
|
|
7
7
|
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
8
8
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
9
9
|
// ── Operations ──────────────────────────────────────────────────────────────
|
|
@@ -22,65 +22,69 @@ export function addStash(opts) {
|
|
|
22
22
|
if (writable === true && providerType && providerType !== "filesystem" && providerType !== "git") {
|
|
23
23
|
throw new ConfigError("writable: true is only supported on filesystem and git sources", "INVALID_CONFIG_FILE");
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
const sources = [...getSources(config)];
|
|
27
|
-
let entry;
|
|
25
|
+
let result;
|
|
28
26
|
if (isRemoteUrl(target)) {
|
|
29
27
|
if (!providerType) {
|
|
30
28
|
throw new UsageError("--provider is required for URL sources (e.g. --provider git --provider website)");
|
|
31
29
|
}
|
|
32
|
-
// Deduplicate by URL
|
|
33
|
-
if (sources.some((s) => s.url === target)) {
|
|
34
|
-
return { sources, added: false, message: "Source URL already configured" };
|
|
35
|
-
}
|
|
36
|
-
entry = { type: providerType, url: target };
|
|
37
|
-
if (name)
|
|
38
|
-
entry.name = name;
|
|
39
|
-
if (writable)
|
|
40
|
-
entry.writable = true;
|
|
41
|
-
if (providerOptions)
|
|
42
|
-
entry.options = providerOptions;
|
|
43
30
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
31
|
+
mutateConfig((config) => {
|
|
32
|
+
const sources = [...getSources(config)];
|
|
33
|
+
let entry;
|
|
34
|
+
if (isRemoteUrl(target)) {
|
|
35
|
+
if (sources.some((source) => source.url === target)) {
|
|
36
|
+
result = { sources, added: false, message: "Source URL already configured" };
|
|
37
|
+
return config;
|
|
38
|
+
}
|
|
39
|
+
entry = { type: providerType, url: target };
|
|
40
|
+
if (name)
|
|
41
|
+
entry.name = name;
|
|
42
|
+
if (writable)
|
|
43
|
+
entry.writable = true;
|
|
44
|
+
if (providerOptions)
|
|
45
|
+
entry.options = providerOptions;
|
|
49
46
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
else {
|
|
48
|
+
const resolvedPath = path.resolve(target);
|
|
49
|
+
if (sources.some((source) => source.path && path.resolve(source.path) === resolvedPath)) {
|
|
50
|
+
result = { sources, added: false, message: "Source path already configured" };
|
|
51
|
+
return config;
|
|
52
|
+
}
|
|
53
|
+
entry = { type: "filesystem", path: resolvedPath };
|
|
54
|
+
if (name)
|
|
55
|
+
entry.name = name;
|
|
56
|
+
}
|
|
57
|
+
sources.push(entry);
|
|
58
|
+
result = { sources, added: true, entry };
|
|
59
|
+
return { ...config, sources };
|
|
60
|
+
});
|
|
61
|
+
return result;
|
|
57
62
|
}
|
|
58
63
|
/**
|
|
59
64
|
* Remove a stash source by URL, path, or name.
|
|
60
65
|
* Match priority: URL > path > name (most specific first).
|
|
61
66
|
*/
|
|
62
67
|
export function removeStash(target) {
|
|
63
|
-
const config = loadUserConfig();
|
|
64
|
-
const sources = [...getSources(config)];
|
|
65
68
|
const isUrlTarget = isRemoteUrl(target);
|
|
66
69
|
const resolvedPath = !isUrlTarget ? path.resolve(target) : undefined;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
idx = sources.findIndex((
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
let result;
|
|
71
|
+
mutateConfig((config) => {
|
|
72
|
+
const sources = [...getSources(config)];
|
|
73
|
+
let idx = isUrlTarget ? sources.findIndex((source) => source.url === target) : -1;
|
|
74
|
+
if (idx === -1 && resolvedPath) {
|
|
75
|
+
idx = sources.findIndex((source) => source.path && path.resolve(source.path) === resolvedPath);
|
|
76
|
+
}
|
|
77
|
+
if (idx === -1)
|
|
78
|
+
idx = sources.findIndex((source) => source.name === target);
|
|
79
|
+
if (idx === -1) {
|
|
80
|
+
result = { sources, removed: false, message: "No matching source found" };
|
|
81
|
+
return config;
|
|
82
|
+
}
|
|
83
|
+
const removed = sources.splice(idx, 1)[0];
|
|
84
|
+
result = { sources, removed: true, entry: removed };
|
|
85
|
+
return { ...config, sources };
|
|
86
|
+
});
|
|
87
|
+
return result;
|
|
84
88
|
}
|
|
85
89
|
/**
|
|
86
90
|
* List all stash sources (local filesystem + configured stashes).
|
|
@@ -27,8 +27,9 @@
|
|
|
27
27
|
import path from "node:path";
|
|
28
28
|
import { defineCommand } from "citty";
|
|
29
29
|
import * as p from "../../cli/clack.js";
|
|
30
|
-
import { defineJsonCommand, output, runWithJsonErrors } from "../../cli/shared.js";
|
|
30
|
+
import { defineJsonCommand, output, parseAllFlagValues, runWithJsonErrors } from "../../cli/shared.js";
|
|
31
31
|
import { assertFlatAssetName } from "../../core/asset/asset-create.js";
|
|
32
|
+
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
32
33
|
import { isHttpUrl } from "../../core/common.js";
|
|
33
34
|
import { loadConfig } from "../../core/config/config.js";
|
|
34
35
|
import { UsageError } from "../../core/errors.js";
|
|
@@ -38,7 +39,7 @@ import { clearLogFile, info, isVerbose, setLogFile } from "../../core/warn.js";
|
|
|
38
39
|
import { resolveWriteTarget } from "../../core/write-source.js";
|
|
39
40
|
import { akmIndex } from "../../indexer/indexer.js";
|
|
40
41
|
import { getHyphenatedBoolean, getOutputMode, parseFlagValue } from "../../output/context.js";
|
|
41
|
-
import { readKnowledgeInput, writeMarkdownAsset } from "../read/knowledge.js";
|
|
42
|
+
import { inferAssetName, mergeXrefsIntoContent, readKnowledgeInput, resolveSupersedesForWrite, resolveXrefsForWrite, writeMarkdownAsset, } from "../read/knowledge.js";
|
|
42
43
|
import { assembleInfo } from "./info.js";
|
|
43
44
|
import { akmInit } from "./init.js";
|
|
44
45
|
export const initCommand = defineJsonCommand({
|
|
@@ -181,21 +182,57 @@ export const importKnowledgeCommand = defineJsonCommand({
|
|
|
181
182
|
type: "string",
|
|
182
183
|
description: "Override the write destination. Accepts a source name from your config; falls back to defaultWriteTarget then the working stash.",
|
|
183
184
|
},
|
|
185
|
+
xref: {
|
|
186
|
+
type: "string",
|
|
187
|
+
description: "Cross-reference ref merged into the document's `xrefs:` frontmatter (repeatable: --xref knowledge:auth-flow). Existing frontmatter is preserved (dedupe-append, never a nested block); a document whose frontmatter is not parseable YAML aborts the import rather than being rewritten lossily. Each ref must resolve in the write target or a configured source; an unresolvable ref aborts the import.",
|
|
188
|
+
},
|
|
189
|
+
supersedes: {
|
|
190
|
+
type: "string",
|
|
191
|
+
description: "Ref of an existing asset this document corrects (repeatable: --supersedes knowledge:legacy-guide). Imports the correction with an xref to the old asset AND demotes the old asset (`beliefState: superseded` + `supersededBy`, a metadata-only edit) so ranking prefers the correction and `--belief current` hides the stale version. An unresolvable or self-referencing ref aborts the import; a ref outside the write target and working stash still imports the correction but skips the demotion (reported as applied: false).",
|
|
192
|
+
},
|
|
184
193
|
},
|
|
185
194
|
async run({ args }) {
|
|
186
195
|
// `--name` is a flat name; subdirectory placement is `--path`'s job.
|
|
187
196
|
assertFlatAssetName(args.name);
|
|
197
|
+
// Collect and validate --xref occurrences (repeatable; citty only exposes
|
|
198
|
+
// the last value, so read argv directly). Validation happens BEFORE any
|
|
199
|
+
// read/write so an unresolvable ref (UsageError → exit 2) leaves the
|
|
200
|
+
// stash untouched.
|
|
201
|
+
const xrefs = resolveXrefsForWrite(parseAllFlagValues("--xref"), args.target);
|
|
202
|
+
// Collect and validate --supersedes occurrences (repeatable). Same
|
|
203
|
+
// before-any-read/write contract: an unresolvable ref exits 2 with nothing
|
|
204
|
+
// imported AND nothing demoted. The superseded refs fold into the imported
|
|
205
|
+
// doc's xrefs automatically (correction provenance); the demotion runs
|
|
206
|
+
// inside writeMarkdownAsset, ordered before the git boundary commit.
|
|
207
|
+
const supersedes = resolveSupersedesForWrite(parseAllFlagValues("--supersedes"), args.target);
|
|
208
|
+
for (const s of supersedes) {
|
|
209
|
+
if (!xrefs.includes(s.ref))
|
|
210
|
+
xrefs.push(s.ref);
|
|
211
|
+
}
|
|
188
212
|
const stashDir = resolveWriteTarget(loadConfig(), args.target).source.path;
|
|
189
213
|
const { content, preferredName } = await readKnowledgeInput(args.source, { stashDir });
|
|
214
|
+
// Imported docs may carry their own frontmatter: merge (dedupe-append)
|
|
215
|
+
// BEFORE the write so write-path indexing sees the final content and no
|
|
216
|
+
// second frontmatter block is ever nested.
|
|
217
|
+
// The slug must come from the document BODY: a merged (or self-carried)
|
|
218
|
+
// frontmatter block puts the `---` fence on the first line, which
|
|
219
|
+
// inferAssetName would slugify to "" and fall back to a random
|
|
220
|
+
// knowledge-<epoch>-<rand> name. A stdin import (no filename-derived
|
|
221
|
+
// preferredName) therefore pre-infers the name from the pre-merge
|
|
222
|
+
// content's PARSED body — not the raw text, whose first line is the fence
|
|
223
|
+
// whenever the piped doc carries its own frontmatter — so --xref/
|
|
224
|
+
// --supersedes never change the slug and a frontmattered doc gets its
|
|
225
|
+
// heading-derived slug on every path.
|
|
190
226
|
const result = await writeMarkdownAsset({
|
|
191
227
|
type: "knowledge",
|
|
192
|
-
content,
|
|
228
|
+
content: mergeXrefsIntoContent(content, xrefs),
|
|
193
229
|
name: args.name ?? (isHttpUrl(args.source) ? preferredName : undefined),
|
|
194
230
|
fallbackPrefix: "knowledge",
|
|
195
|
-
preferredName,
|
|
231
|
+
preferredName: preferredName ?? inferAssetName(parseFrontmatter(content).content, "knowledge"),
|
|
196
232
|
force: args.force,
|
|
197
233
|
target: args.target,
|
|
198
234
|
path: args.path,
|
|
235
|
+
supersedes,
|
|
199
236
|
});
|
|
200
237
|
appendEvent({
|
|
201
238
|
eventType: "import",
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* Ships a well-tuned multi-cadence task set so a typical single-developer
|
|
8
8
|
* install is correct with zero manual config. Registration is **idempotent**:
|
|
9
9
|
* running `akm setup` / `akm tasks init` twice yields the same task set with no
|
|
10
|
-
* duplicates. Each default task is a shell-command task (`akm improve --
|
|
11
|
-
* <name>`), so the
|
|
10
|
+
* duplicates. Each default task is a shell-command task (`akm improve --strategy
|
|
11
|
+
* <name>`), so the strategy is overridable in `config.json` without editing the
|
|
12
12
|
* task definition.
|
|
13
13
|
*
|
|
14
14
|
* The OS-scheduler-touching primitives (`add` / `setEnabled` / `list`) are
|
|
@@ -27,40 +27,40 @@ import { akmTasksAdd, akmTasksList } from "./tasks.js";
|
|
|
27
27
|
export const DEFAULT_IMPROVE_TASKS = [
|
|
28
28
|
{
|
|
29
29
|
id: "akm-improve-frequent",
|
|
30
|
-
|
|
31
|
-
command: "akm improve --
|
|
30
|
+
strategy: "frequent",
|
|
31
|
+
command: "akm improve --strategy frequent --auto-accept safe",
|
|
32
32
|
schedule: "0 * * * *",
|
|
33
33
|
description: "Frequent extract + inference pass (every 60 min)",
|
|
34
34
|
enableMode: "always",
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
id: "akm-improve-consolidate",
|
|
38
|
-
|
|
39
|
-
command: "akm improve --
|
|
38
|
+
strategy: "consolidate",
|
|
39
|
+
command: "akm improve --strategy consolidate --auto-accept safe",
|
|
40
40
|
schedule: "0 */4 * * *",
|
|
41
41
|
description: "Consolidation-only pass (every 4h)",
|
|
42
42
|
enableMode: "always",
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
id: "akm-improve-nightly",
|
|
46
|
-
|
|
47
|
-
command: "akm improve --
|
|
46
|
+
strategy: "thorough",
|
|
47
|
+
command: "akm improve --strategy thorough --auto-accept safe",
|
|
48
48
|
schedule: "0 2 * * *",
|
|
49
49
|
description: "Full nightly quality sweep (daily 2am)",
|
|
50
50
|
enableMode: "server",
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
id: "akm-improve-catchup",
|
|
54
|
-
|
|
55
|
-
command: "akm improve --
|
|
54
|
+
strategy: "catchup",
|
|
55
|
+
command: "akm improve --strategy catchup --auto-accept safe",
|
|
56
56
|
schedule: null,
|
|
57
57
|
description: "Manual recovery — consolidation + triage drain (run on demand)",
|
|
58
58
|
enableMode: "manual",
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
id: "akm-graph-refresh-weekly",
|
|
62
|
-
|
|
63
|
-
command: "akm improve --
|
|
62
|
+
strategy: "graph-refresh",
|
|
63
|
+
command: "akm improve --strategy graph-refresh --auto-accept safe",
|
|
64
64
|
schedule: "0 3 * * 0",
|
|
65
65
|
description: "Full-corpus graph rebuild (weekly Sunday 3am)",
|
|
66
66
|
enableMode: "always",
|
|
@@ -31,7 +31,9 @@ const tasksAddCommand = defineJsonCommand({
|
|
|
31
31
|
type: "string",
|
|
32
32
|
description: 'Shell command to run on the schedule (no AI agent), e.g. "akm improve --auto-accept safe". Split on whitespace; quote the whole flag value.',
|
|
33
33
|
},
|
|
34
|
-
|
|
34
|
+
engine: { type: "string", description: "Engine to use for prompt targets (default: defaults.engine)" },
|
|
35
|
+
model: { type: "string", description: "Model override for prompt targets" },
|
|
36
|
+
"timeout-ms": { type: "string", description: "Positive timeout in milliseconds for prompt or command targets" },
|
|
35
37
|
params: { type: "string", description: "Workflow params as a JSON object" },
|
|
36
38
|
name: { type: "string", description: "Human-readable name for the task" },
|
|
37
39
|
"when-to-use": { type: "string", description: "Guidance on when this task runs or should be used" },
|
|
@@ -47,7 +49,9 @@ const tasksAddCommand = defineJsonCommand({
|
|
|
47
49
|
workflow: args.workflow,
|
|
48
50
|
prompt: args.prompt,
|
|
49
51
|
command: args.command,
|
|
50
|
-
|
|
52
|
+
engine: args.engine,
|
|
53
|
+
model: args.model,
|
|
54
|
+
timeoutMs: args["timeout-ms"] === undefined ? undefined : parsePositiveIntFlag(args["timeout-ms"]),
|
|
51
55
|
params: args.params,
|
|
52
56
|
name: args.name,
|
|
53
57
|
when_to_use: args["when-to-use"],
|
|
@@ -179,7 +183,7 @@ export const tasksCommand = defineGroupCommand({
|
|
|
179
183
|
meta: {
|
|
180
184
|
name: "tasks",
|
|
181
185
|
alias: "task",
|
|
182
|
-
description: "Schedule workflows or
|
|
186
|
+
description: "Schedule version-2 workflows, prompts, or commands via the OS-native scheduler (cron / launchd / schtasks)",
|
|
183
187
|
},
|
|
184
188
|
subCommands: {
|
|
185
189
|
add: tasksAddCommand,
|