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
|
@@ -17,7 +17,6 @@ import { loadConfig } from "../../core/config/config.js";
|
|
|
17
17
|
import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
18
18
|
import { getTaskHistoryDir, getTaskLogDir } from "../../core/paths.js";
|
|
19
19
|
import { commitWriteTargetBoundary, deleteAssetFromSource, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
|
|
20
|
-
import { listAgentProfileNames } from "../../integrations/agent/index.js";
|
|
21
20
|
import { resolveAssetPath } from "../../sources/resolve.js";
|
|
22
21
|
import { backendNameForPlatform, selectBackend } from "../../tasks/backends/index.js";
|
|
23
22
|
import { parseTaskDocument } from "../../tasks/parser.js";
|
|
@@ -25,7 +24,7 @@ import { resolveAkmInvocation } from "../../tasks/resolve-akm-bin.js";
|
|
|
25
24
|
import { exitCodeForStatus, readTaskHistory, runTask } from "../../tasks/runner.js";
|
|
26
25
|
import { parseSchedule, SCHEDULE_SUPPORTED_SUBSET_HINT, translateToCron } from "../../tasks/schedule.js";
|
|
27
26
|
import { validateTaskDocument } from "../../tasks/validator.js";
|
|
28
|
-
import {
|
|
27
|
+
import { resolveImproveStrategy } from "../improve/improve-strategies.js";
|
|
29
28
|
export async function akmTasksAdd(input) {
|
|
30
29
|
const id = normaliseTaskId(input.id);
|
|
31
30
|
const hasCommand = input.command !== undefined &&
|
|
@@ -36,6 +35,12 @@ export async function akmTasksAdd(input) {
|
|
|
36
35
|
if (targetCount !== 1) {
|
|
37
36
|
throw new UsageError("Pass exactly one of --workflow <ref>, --prompt <asset-ref|./file.md|text>, or --command <shell-command>.", "INVALID_FLAG_VALUE");
|
|
38
37
|
}
|
|
38
|
+
if (input.workflow && (input.engine !== undefined || input.model !== undefined || input.timeoutMs !== undefined)) {
|
|
39
|
+
throw new UsageError("Workflow tasks accept only --params; engine, model, and timeout are prompt-task fields.", "INVALID_FLAG_VALUE");
|
|
40
|
+
}
|
|
41
|
+
if (hasCommand && (input.engine !== undefined || input.model !== undefined)) {
|
|
42
|
+
throw new UsageError("Command tasks accept --timeout-ms but not --engine or --model.", "INVALID_FLAG_VALUE");
|
|
43
|
+
}
|
|
39
44
|
// Validate the schedule for the active backend before writing anything.
|
|
40
45
|
const backend = backendNameForPlatform();
|
|
41
46
|
parseSchedule(input.schedule, backend);
|
|
@@ -56,7 +61,9 @@ export async function akmTasksAdd(input) {
|
|
|
56
61
|
workflow: input.workflow,
|
|
57
62
|
prompt: input.prompt,
|
|
58
63
|
command: input.command,
|
|
59
|
-
|
|
64
|
+
engine: input.engine,
|
|
65
|
+
model: input.model,
|
|
66
|
+
timeoutMs: input.timeoutMs,
|
|
60
67
|
params: input.params,
|
|
61
68
|
name: input.name,
|
|
62
69
|
description: input.description,
|
|
@@ -137,11 +144,12 @@ export async function akmTasksList() {
|
|
|
137
144
|
const stashDir = resolveStashDir();
|
|
138
145
|
const typeRoot = path.join(stashDir, "tasks");
|
|
139
146
|
if (!fs.existsSync(typeRoot))
|
|
140
|
-
return { tasks: [] };
|
|
147
|
+
return { tasks: [], stale: [] };
|
|
141
148
|
const entries = fs.readdirSync(typeRoot);
|
|
142
149
|
warnLegacyMdTaskFiles(typeRoot);
|
|
143
150
|
const files = entries.filter((f) => f.endsWith(".yml"));
|
|
144
151
|
const tasks = [];
|
|
152
|
+
const stale = [];
|
|
145
153
|
for (const file of files) {
|
|
146
154
|
const id = file.slice(0, -4);
|
|
147
155
|
const filePath = path.join(typeRoot, file);
|
|
@@ -149,7 +157,9 @@ export async function akmTasksList() {
|
|
|
149
157
|
try {
|
|
150
158
|
task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
|
|
151
159
|
}
|
|
152
|
-
catch {
|
|
160
|
+
catch (err) {
|
|
161
|
+
if (isStaleTaskError(err))
|
|
162
|
+
stale.push(id);
|
|
153
163
|
continue; // skip malformed files; `akm tasks show <id>` will surface the error
|
|
154
164
|
}
|
|
155
165
|
tasks.push({
|
|
@@ -165,7 +175,9 @@ export async function akmTasksList() {
|
|
|
165
175
|
tags: task.tags,
|
|
166
176
|
});
|
|
167
177
|
}
|
|
168
|
-
|
|
178
|
+
if (stale.length > 0)
|
|
179
|
+
warnStaleTaskFiles(stale);
|
|
180
|
+
return { tasks, stale };
|
|
169
181
|
}
|
|
170
182
|
export async function akmTasksShow(id) {
|
|
171
183
|
const normalised = normaliseTaskId(id);
|
|
@@ -234,6 +246,9 @@ export async function akmTasksSetEnabled(id, enabled) {
|
|
|
234
246
|
warnLegacyMdTaskFiles(typeRoot);
|
|
235
247
|
const filePath = await resolveAssetPath(stashDir, "task", normalised);
|
|
236
248
|
const yaml = fs.readFileSync(filePath, "utf8");
|
|
249
|
+
// Parse before writing so stale v1 tasks are diagnosed without changing the
|
|
250
|
+
// source file or its installed scheduler entry.
|
|
251
|
+
parseTaskDocument({ yaml, filePath, id: normalised });
|
|
237
252
|
const updated = setEnabledInYaml(yaml, enabled);
|
|
238
253
|
const ref = taskAssetRef(normalised);
|
|
239
254
|
await writeAssetToSource(target.source, target.config, ref, updated);
|
|
@@ -374,16 +389,15 @@ export async function akmTasksDoctor() {
|
|
|
374
389
|
}
|
|
375
390
|
const backend = backendNameForPlatform();
|
|
376
391
|
const config = loadConfig();
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
const profiles = config.profiles?.agent ? Object.keys(config.profiles.agent) : listAgentProfileNames(config);
|
|
392
|
+
const defaultEngine = config.defaults?.engine;
|
|
393
|
+
const engines = Object.keys(config.engines ?? {});
|
|
380
394
|
// §6.1: surface the effective triage settings for the default improve
|
|
381
|
-
//
|
|
382
|
-
const
|
|
383
|
-
const triage =
|
|
395
|
+
// strategy. The struct is a fixed shape, so this is a deliberate addition.
|
|
396
|
+
const improveStrategyName = typeof config.defaults?.improveStrategy === "string" ? config.defaults.improveStrategy : "default";
|
|
397
|
+
const triage = resolveImproveStrategy(config.defaults?.improveStrategy, config).config.processes?.triage;
|
|
384
398
|
const improveTriage = triage
|
|
385
399
|
? {
|
|
386
|
-
|
|
400
|
+
defaultStrategy: improveStrategyName,
|
|
387
401
|
enabled: triage.enabled === true,
|
|
388
402
|
applyMode: triage.applyMode ?? "queue",
|
|
389
403
|
policy: triage.policy ?? "personal-stash",
|
|
@@ -394,7 +408,9 @@ export async function akmTasksDoctor() {
|
|
|
394
408
|
akm: invocation,
|
|
395
409
|
logDir: getTaskLogDir(),
|
|
396
410
|
historyDir: getTaskHistoryDir(),
|
|
397
|
-
|
|
411
|
+
engine: { defaultEngine, available: engines },
|
|
412
|
+
stale: collectStaleTaskIds(),
|
|
413
|
+
staleGeneratedCommands: collectStaleGeneratedCommands(),
|
|
398
414
|
scheduleSubset: SCHEDULE_SUPPORTED_SUBSET_HINT,
|
|
399
415
|
warnings,
|
|
400
416
|
...(improveTriage ? { improveTriage } : {}),
|
|
@@ -421,7 +437,7 @@ function resolveTaskWriteTarget() {
|
|
|
421
437
|
return resolveWriteTarget(loadConfig());
|
|
422
438
|
}
|
|
423
439
|
function renderTaskYaml(input) {
|
|
424
|
-
const obj = { schedule: input.schedule };
|
|
440
|
+
const obj = { version: 2, schedule: input.schedule, enabled: input.enabled };
|
|
425
441
|
if (input.workflow) {
|
|
426
442
|
obj.workflow = input.workflow;
|
|
427
443
|
if (input.params) {
|
|
@@ -430,16 +446,21 @@ function renderTaskYaml(input) {
|
|
|
430
446
|
}
|
|
431
447
|
else if (input.prompt) {
|
|
432
448
|
obj.prompt = input.prompt;
|
|
433
|
-
if (input.
|
|
434
|
-
obj.
|
|
449
|
+
if (input.engine)
|
|
450
|
+
obj.engine = input.engine;
|
|
451
|
+
if (input.model)
|
|
452
|
+
obj.model = input.model;
|
|
453
|
+
if (input.timeoutMs !== undefined)
|
|
454
|
+
obj.timeoutMs = input.timeoutMs;
|
|
435
455
|
}
|
|
436
456
|
else if (input.command !== undefined) {
|
|
437
457
|
// Emit a string when given a string, an array when given an array. The
|
|
438
458
|
// parser accepts both forms; preserving the caller's shape keeps the YAML
|
|
439
459
|
// ergonomic for humans editing the file later.
|
|
440
460
|
obj.command = input.command;
|
|
461
|
+
if (input.timeoutMs !== undefined)
|
|
462
|
+
obj.timeoutMs = input.timeoutMs;
|
|
441
463
|
}
|
|
442
|
-
obj.enabled = input.enabled;
|
|
443
464
|
if (input.name)
|
|
444
465
|
obj.name = input.name;
|
|
445
466
|
if (input.description)
|
|
@@ -450,6 +471,80 @@ function renderTaskYaml(input) {
|
|
|
450
471
|
obj.tags = input.tags;
|
|
451
472
|
return yamlStringify(obj);
|
|
452
473
|
}
|
|
474
|
+
function isStaleTaskError(err) {
|
|
475
|
+
return err instanceof UsageError && err.code === "TASK_SCHEMA_VERSION_UNSUPPORTED";
|
|
476
|
+
}
|
|
477
|
+
function warnStaleTaskFiles(ids) {
|
|
478
|
+
process.stderr.write(`WARNING: ${ids.length} task file(s) use retired task schema v1 and were not loaded.\n` +
|
|
479
|
+
` Rewrite them with version: 2 and replace profile with engine. See docs/migration/v0.8-to-v0.9.md#task-assets.\n` +
|
|
480
|
+
` Affected: ${ids.map((id) => `tasks/${id}.yml`).join(", ")}\n`);
|
|
481
|
+
}
|
|
482
|
+
function collectStaleTaskIds() {
|
|
483
|
+
const typeRoot = path.join(resolveStashDir(), "tasks");
|
|
484
|
+
if (!fs.existsSync(typeRoot))
|
|
485
|
+
return [];
|
|
486
|
+
const stale = [];
|
|
487
|
+
for (const file of fs.readdirSync(typeRoot)) {
|
|
488
|
+
if (!file.endsWith(".yml"))
|
|
489
|
+
continue;
|
|
490
|
+
const id = file.slice(0, -4);
|
|
491
|
+
try {
|
|
492
|
+
parseTaskDocument({
|
|
493
|
+
yaml: fs.readFileSync(path.join(typeRoot, file), "utf8"),
|
|
494
|
+
filePath: path.join(typeRoot, file),
|
|
495
|
+
id,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
catch (err) {
|
|
499
|
+
if (isStaleTaskError(err))
|
|
500
|
+
stale.push(id);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return stale;
|
|
504
|
+
}
|
|
505
|
+
const STALE_GENERATED_COMMANDS = {
|
|
506
|
+
"akm-improve-frequent": {
|
|
507
|
+
command: "akm improve --profile frequent --auto-accept safe",
|
|
508
|
+
replacement: "akm improve --strategy frequent --auto-accept safe",
|
|
509
|
+
},
|
|
510
|
+
"akm-improve-consolidate": {
|
|
511
|
+
command: "akm improve --profile consolidate --auto-accept safe",
|
|
512
|
+
replacement: "akm improve --strategy consolidate --auto-accept safe",
|
|
513
|
+
},
|
|
514
|
+
"akm-improve-nightly": {
|
|
515
|
+
command: "akm improve --profile thorough --auto-accept safe",
|
|
516
|
+
replacement: "akm improve --strategy thorough --auto-accept safe",
|
|
517
|
+
},
|
|
518
|
+
"akm-improve-catchup": {
|
|
519
|
+
command: "akm improve --profile catchup --auto-accept safe",
|
|
520
|
+
replacement: "akm improve --strategy catchup --auto-accept safe",
|
|
521
|
+
},
|
|
522
|
+
"akm-graph-refresh-weekly": {
|
|
523
|
+
command: "akm improve --profile graph-refresh --auto-accept safe",
|
|
524
|
+
replacement: "akm improve --strategy graph-refresh --auto-accept safe",
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
function collectStaleGeneratedCommands() {
|
|
528
|
+
const typeRoot = path.join(resolveStashDir(), "tasks");
|
|
529
|
+
if (!fs.existsSync(typeRoot))
|
|
530
|
+
return [];
|
|
531
|
+
const stale = [];
|
|
532
|
+
for (const [id, expected] of Object.entries(STALE_GENERATED_COMMANDS)) {
|
|
533
|
+
const filePath = path.join(typeRoot, `${id}.yml`);
|
|
534
|
+
if (!fs.existsSync(filePath))
|
|
535
|
+
continue;
|
|
536
|
+
try {
|
|
537
|
+
const task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
|
|
538
|
+
if (task.target.kind === "command" && task.target.cmd.join(" ") === expected.command) {
|
|
539
|
+
stale.push({ id, replacement: expected.replacement });
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
// Stale-schema reporting owns files that cannot be parsed.
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return stale;
|
|
547
|
+
}
|
|
453
548
|
function parseJsonObjectArg(raw) {
|
|
454
549
|
let parsed;
|
|
455
550
|
try {
|
|
@@ -220,13 +220,13 @@ const wikiLintCommand = defineCommand({
|
|
|
220
220
|
const wikiIngestCommand = defineJsonCommand({
|
|
221
221
|
meta: {
|
|
222
222
|
name: "ingest",
|
|
223
|
-
description: "Dispatch an agent to execute the ingest workflow for this wiki. Uses --
|
|
223
|
+
description: "Dispatch an agent to execute the ingest workflow for this wiki. Uses --engine or config.defaults.engine.",
|
|
224
224
|
},
|
|
225
225
|
args: {
|
|
226
226
|
name: { type: "positional", description: "Wiki name", required: true },
|
|
227
|
-
|
|
227
|
+
engine: {
|
|
228
228
|
type: "string",
|
|
229
|
-
description: "Agent
|
|
229
|
+
description: "Agent engine to use (default: config.defaults.engine).",
|
|
230
230
|
},
|
|
231
231
|
model: {
|
|
232
232
|
type: "string",
|
|
@@ -239,17 +239,15 @@ const wikiIngestCommand = defineJsonCommand({
|
|
|
239
239
|
const stashDir = resolveStashDir();
|
|
240
240
|
const built = buildIngestWorkflow(stashDir, args.name);
|
|
241
241
|
const config = loadConfig();
|
|
242
|
-
const
|
|
243
|
-
if (!
|
|
244
|
-
throw new UsageError("akm wiki ingest requires an agent
|
|
242
|
+
const engine = getStringArg(args, "engine") ?? config.defaults?.engine;
|
|
243
|
+
if (!engine) {
|
|
244
|
+
throw new UsageError("akm wiki ingest requires an agent engine. Pass --engine <name> or set defaults.engine in config.", "MISSING_REQUIRED_ARGUMENT", "Available engines are listed under engines in your config. Run `akm config get engines` to inspect.");
|
|
245
245
|
}
|
|
246
246
|
const timeoutMs = parsePositiveIntFlag(args["timeout-ms"], "--timeout-ms");
|
|
247
247
|
const model = getStringArg(args, "model");
|
|
248
|
-
const { getDefaultLlmConfig } = await import("../core/config/config.js");
|
|
249
248
|
const dispatchResult = await akmAgentDispatch({
|
|
250
|
-
|
|
249
|
+
engine,
|
|
251
250
|
agentConfig: config,
|
|
252
|
-
llmConfig: getDefaultLlmConfig(config),
|
|
253
251
|
prompt: built.workflow,
|
|
254
252
|
dispatch: {
|
|
255
253
|
prompt: built.workflow,
|
|
@@ -258,11 +256,12 @@ const wikiIngestCommand = defineJsonCommand({
|
|
|
258
256
|
...(timeoutMs !== undefined && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
|
|
259
257
|
});
|
|
260
258
|
output("wiki-ingest", {
|
|
259
|
+
schemaVersion: 2,
|
|
261
260
|
wiki: built.wiki,
|
|
262
261
|
path: built.path,
|
|
263
262
|
schemaPath: built.schemaPath,
|
|
264
263
|
dispatched: true,
|
|
265
|
-
|
|
264
|
+
engine,
|
|
266
265
|
agentResult: dispatchResult,
|
|
267
266
|
});
|
|
268
267
|
},
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import fs from "node:fs";
|
|
12
12
|
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
13
|
-
import { assembleAsset } from "./asset-serialize.js";
|
|
13
|
+
import { assembleAsset, serializeFrontmatter } from "./asset-serialize.js";
|
|
14
14
|
/**
|
|
15
15
|
* Parse YAML frontmatter from a Markdown (or similar) string.
|
|
16
16
|
*
|
|
@@ -118,6 +118,13 @@ function parseFrontmatterLenient(frontmatter) {
|
|
|
118
118
|
* or `null` to skip the write entirely (e.g. for idempotent no-ops). The body
|
|
119
119
|
* content is preserved from the parse.
|
|
120
120
|
*
|
|
121
|
+
* A frontmatter mutation is a METADATA edit, not a content edit: when the file
|
|
122
|
+
* already has a frontmatter block, only that block is replaced and the body
|
|
123
|
+
* bytes are kept verbatim (routing through `assembleAsset` would strip the
|
|
124
|
+
* body's leading blank lines and force a trailing newline, silently reshaping
|
|
125
|
+
* assets whose writer used a different separator style). A file gaining its
|
|
126
|
+
* FIRST frontmatter block goes through the canonical `assembleAsset` shape.
|
|
127
|
+
*
|
|
121
128
|
* @returns `true` if a write occurred, `false` if the mutator returned `null`.
|
|
122
129
|
*/
|
|
123
130
|
export function mutateFrontmatter(filePath, mutator) {
|
|
@@ -126,7 +133,10 @@ export function mutateFrontmatter(filePath, mutator) {
|
|
|
126
133
|
const nextFrontmatter = mutator(parsed);
|
|
127
134
|
if (nextFrontmatter === null)
|
|
128
135
|
return false;
|
|
129
|
-
|
|
136
|
+
const next = parsed.frontmatter !== null
|
|
137
|
+
? `---\n${serializeFrontmatter(nextFrontmatter)}\n---\n${parsed.content}`
|
|
138
|
+
: assembleAsset(nextFrontmatter, parsed.content);
|
|
139
|
+
fs.writeFileSync(filePath, next, "utf8");
|
|
130
140
|
return true;
|
|
131
141
|
}
|
|
132
142
|
export function parseFrontmatterBlock(raw) {
|
package/dist/core/common.js
CHANGED
|
@@ -243,11 +243,12 @@ function normalizeFsPathForComparison(value) {
|
|
|
243
243
|
*/
|
|
244
244
|
export async function fetchWithTimeout(url, opts, timeoutMs = 30_000, signal) {
|
|
245
245
|
const controller = new AbortController();
|
|
246
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
246
|
+
const timer = timeoutMs === null ? undefined : setTimeout(() => controller.abort(), timeoutMs);
|
|
247
247
|
const abortExternal = () => controller.abort(signal?.reason);
|
|
248
248
|
if (signal) {
|
|
249
249
|
if (signal.aborted) {
|
|
250
|
-
|
|
250
|
+
if (timer)
|
|
251
|
+
clearTimeout(timer);
|
|
251
252
|
controller.abort(signal.reason);
|
|
252
253
|
}
|
|
253
254
|
else {
|
|
@@ -269,7 +270,8 @@ export async function fetchWithTimeout(url, opts, timeoutMs = 30_000, signal) {
|
|
|
269
270
|
finally {
|
|
270
271
|
if (signal)
|
|
271
272
|
signal.removeEventListener("abort", abortExternal);
|
|
272
|
-
|
|
273
|
+
if (timer)
|
|
274
|
+
clearTimeout(timer);
|
|
273
275
|
}
|
|
274
276
|
}
|
|
275
277
|
/**
|
|
@@ -17,7 +17,7 @@ import path from "node:path";
|
|
|
17
17
|
import { sleepSync } from "../../runtime.js";
|
|
18
18
|
import { writeFileAtomic } from "../common.js";
|
|
19
19
|
import { ConfigError } from "../errors.js";
|
|
20
|
-
import { probeLock, releaseLock, tryAcquireLockSync } from "../file-lock.js";
|
|
20
|
+
import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync } from "../file-lock.js";
|
|
21
21
|
import { getCacheDir, getConfigDir } from "../paths.js";
|
|
22
22
|
/**
|
|
23
23
|
* Read the raw text of a config file. Returns `undefined` when the file does
|
|
@@ -80,7 +80,7 @@ export function writeConfigAtomic(configPath, config) {
|
|
|
80
80
|
}
|
|
81
81
|
/** Maximum number of timestamped config backups to retain (#459). */
|
|
82
82
|
const MAX_CONFIG_BACKUPS = 5;
|
|
83
|
-
export function backupExistingConfig(configPath) {
|
|
83
|
+
export function backupExistingConfig(configPath, now = new Date()) {
|
|
84
84
|
if (!fs.existsSync(configPath))
|
|
85
85
|
return undefined;
|
|
86
86
|
const backupDir = path.join(getCacheDir(), "config-backups");
|
|
@@ -89,10 +89,22 @@ export function backupExistingConfig(configPath) {
|
|
|
89
89
|
// the copy→chmod window. chmod again to tighten a dir from an older version.
|
|
90
90
|
fs.mkdirSync(backupDir, { recursive: true, mode: 0o700 });
|
|
91
91
|
fs.chmodSync(backupDir, 0o700);
|
|
92
|
-
const timestamp =
|
|
93
|
-
|
|
92
|
+
const timestamp = now.toISOString().replace(/[.:]/g, "-");
|
|
93
|
+
let sequence = 0;
|
|
94
|
+
let timestamped;
|
|
95
|
+
while (true) {
|
|
96
|
+
timestamped = path.join(backupDir, `config-${timestamp}${sequence === 0 ? "" : `-${sequence}`}.json`);
|
|
97
|
+
try {
|
|
98
|
+
fs.copyFileSync(configPath, timestamped, fs.constants.COPYFILE_EXCL);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error.code !== "EEXIST")
|
|
103
|
+
throw error;
|
|
104
|
+
sequence++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
94
107
|
const latest = path.join(backupDir, "config.latest.json");
|
|
95
|
-
fs.copyFileSync(configPath, timestamped);
|
|
96
108
|
fs.copyFileSync(configPath, latest);
|
|
97
109
|
// 08-F4: a config backup carries the same sensitive fields as the live config
|
|
98
110
|
// (endpoints, tokens). `copyFileSync` inherits the source's (often 0644) mode,
|
|
@@ -141,7 +153,7 @@ function pruneOldBackups(backupDir) {
|
|
|
141
153
|
* predictable for debugging. Uses $CONFIG (not $DATA) because config.json
|
|
142
154
|
* itself lives in $CONFIG — they should fail together if the dir is read-only.
|
|
143
155
|
*/
|
|
144
|
-
function getConfigLockPath() {
|
|
156
|
+
export function getConfigLockPath() {
|
|
145
157
|
return path.join(getConfigDir(), "config.json.lck");
|
|
146
158
|
}
|
|
147
159
|
const CONFIG_LOCK_MAX_RETRIES = 10;
|
|
@@ -157,8 +169,8 @@ function sleepSyncMs(ms) {
|
|
|
157
169
|
/**
|
|
158
170
|
* Acquire an exclusive sentinel around config writes.
|
|
159
171
|
*
|
|
160
|
-
* Returns a release function.
|
|
161
|
-
*
|
|
172
|
+
* Returns a release function. Acquisition is fail-closed: config mutation may
|
|
173
|
+
* never continue without owning the lock that protects its read/merge/write.
|
|
162
174
|
*/
|
|
163
175
|
export function acquireConfigLock() {
|
|
164
176
|
const lockPath = getConfigLockPath();
|
|
@@ -170,16 +182,16 @@ export function acquireConfigLock() {
|
|
|
170
182
|
}
|
|
171
183
|
for (let attempt = 0; attempt < CONFIG_LOCK_MAX_RETRIES; attempt++) {
|
|
172
184
|
try {
|
|
173
|
-
|
|
174
|
-
|
|
185
|
+
const ownership = tryAcquireLockSync(lockPath, createLockPayload());
|
|
186
|
+
if (ownership) {
|
|
187
|
+
return () => releaseLock(ownership);
|
|
175
188
|
}
|
|
176
189
|
}
|
|
177
|
-
catch {
|
|
178
|
-
|
|
179
|
-
break;
|
|
190
|
+
catch (error) {
|
|
191
|
+
throw new ConfigError(`Unable to acquire config lock at ${lockPath}: ${error instanceof Error ? error.message : String(error)}`, "INVALID_CONFIG_FILE");
|
|
180
192
|
}
|
|
181
|
-
|
|
182
|
-
|
|
193
|
+
const probe = probeLock(lockPath);
|
|
194
|
+
if (probe.state === "stale" && reclaimStaleLock(lockPath, probe)) {
|
|
183
195
|
continue; // Reclaimed — retry immediately.
|
|
184
196
|
}
|
|
185
197
|
if (attempt < CONFIG_LOCK_MAX_RETRIES - 1) {
|
|
@@ -195,8 +207,7 @@ export function acquireConfigLock() {
|
|
|
195
207
|
sleepSyncMs(CONFIG_LOCK_RETRY_DELAY_MS);
|
|
196
208
|
}
|
|
197
209
|
}
|
|
198
|
-
|
|
199
|
-
return () => { };
|
|
210
|
+
throw new ConfigError(`Timed out waiting for config lock at ${lockPath}. Another AKM process may be updating config.`, "INVALID_CONFIG_FILE");
|
|
200
211
|
}
|
|
201
212
|
/**
|
|
202
213
|
* Run `fn` inside the config write lock. Always releases the lock.
|