akm-cli 0.9.0-rc.2 → 0.9.0-rc.4
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 +40 -12
- package/README.md +7 -8
- package/SECURITY.md +5 -3
- package/dist/akm +44 -33
- package/dist/akm-migrate-storage +44 -33
- package/dist/assets/backends/schtasks-template.xml +2 -1
- package/dist/assets/improve-strategies/catchup.json +3 -1
- package/dist/assets/improve-strategies/consolidate.json +3 -1
- package/dist/assets/improve-strategies/frequent.json +3 -1
- package/dist/assets/improve-strategies/graph-refresh.json +3 -1
- package/dist/assets/improve-strategies/memory-focus.json +4 -1
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -0
- package/dist/assets/improve-strategies/quick.json +3 -1
- package/dist/assets/improve-strategies/recombine-only.json +2 -0
- package/dist/assets/improve-strategies/reflect-distill.json +1 -0
- package/dist/assets/improve-strategies/synthesize.json +2 -0
- package/dist/assets/tasks/core/backup.yml +2 -2
- package/dist/cli/config-migrate.js +554 -26
- package/dist/cli.js +28 -11
- package/dist/commands/backup-cli.js +6 -4
- package/dist/commands/config-cli.js +10 -3
- package/dist/commands/feedback-cli.js +24 -7
- package/dist/commands/health/checks.js +94 -15
- package/dist/commands/health/html-report.js +23 -2
- package/dist/commands/health/improve-metrics.js +45 -6
- package/dist/commands/health/md-report.js +10 -1
- package/dist/commands/health/surfaces.js +2 -1
- package/dist/commands/health/windows.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/anti-collapse.js +3 -3
- package/dist/commands/improve/collapse-detector.js +5 -5
- package/dist/commands/improve/consolidate.js +123 -66
- package/dist/commands/improve/distill/promote-memory.js +8 -6
- package/dist/commands/improve/distill/quality-gate.js +1 -1
- package/dist/commands/improve/distill-guards.js +1 -1
- package/dist/commands/improve/distill-promotion-policy.js +32 -26
- package/dist/commands/improve/distill.js +52 -36
- package/dist/commands/improve/eligibility.js +9 -8
- package/dist/commands/improve/extract-prompt.js +2 -2
- package/dist/commands/improve/extract.js +43 -11
- package/dist/commands/improve/improve-auto-accept.js +14 -28
- package/dist/commands/improve/improve-cli.js +4 -2
- package/dist/commands/improve/improve-strategies.js +2 -1
- package/dist/commands/improve/improve.js +49 -8
- package/dist/commands/improve/loop-stages.js +11 -6
- package/dist/commands/improve/preparation.js +51 -17
- package/dist/commands/improve/procedural.js +10 -6
- package/dist/commands/improve/recombine.js +31 -7
- package/dist/commands/improve/reflect.js +24 -14
- package/dist/commands/improve/salience.js +5 -1
- package/dist/commands/improve/source-identity.js +43 -0
- package/dist/commands/migrate-cli.js +36 -0
- package/dist/commands/mv-cli.js +647 -258
- package/dist/commands/proposal/drain.js +16 -4
- package/dist/commands/proposal/proposal-cli.js +3 -2
- package/dist/commands/proposal/proposal.js +16 -55
- package/dist/commands/proposal/repository.js +664 -58
- package/dist/commands/read/curate.js +4 -2
- package/dist/commands/read/knowledge.js +4 -1
- package/dist/commands/read/search.js +6 -2
- package/dist/commands/read/show.js +8 -7
- package/dist/commands/sources/self-update.js +156 -112
- package/dist/commands/sources/sources-cli.js +7 -2
- package/dist/commands/tasks/default-tasks.js +5 -5
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +261 -68
- package/dist/core/common.js +36 -3
- package/dist/core/config/config-io.js +2 -2
- package/dist/core/config/config-schema.js +15 -3
- package/dist/core/config/config.js +14 -13
- package/dist/core/file-lock.js +2 -2
- package/dist/core/improve-result.js +87 -7
- package/dist/core/migration-backup.js +816 -171
- package/dist/core/migration-operation.js +44 -0
- package/dist/core/state/migrations.js +4 -7
- package/dist/core/state-db.js +23 -7
- package/dist/core/write-source.js +86 -18
- package/dist/indexer/db/db.js +109 -53
- package/dist/indexer/index-writer-lock.js +38 -37
- package/dist/indexer/index-written-assets.js +73 -56
- package/dist/indexer/indexer.js +5 -1
- package/dist/indexer/search/search-source.js +2 -2
- package/dist/indexer/usage/usage-events.js +8 -2
- package/dist/integrations/agent/engine-resolution.js +14 -7
- package/dist/scripts/migrate-storage.js +887 -992
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +494 -574
- package/dist/setup/detected-engines.js +7 -13
- package/dist/setup/engine-config.js +14 -4
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +1 -1
- package/dist/setup/steps/tasks.js +1 -1
- package/dist/sources/providers/git-stash.js +58 -21
- package/dist/sources/providers/git.js +1 -1
- package/dist/storage/engines/sqlite-migrations.js +138 -3
- package/dist/storage/repositories/events-repository.js +24 -0
- package/dist/storage/repositories/proposals-repository.js +14 -0
- package/dist/storage/repositories/task-history-repository.js +30 -3
- package/dist/tasks/backends/cron.js +71 -38
- package/dist/tasks/backends/exec-utils.js +55 -3
- package/dist/tasks/backends/launchd.js +241 -50
- package/dist/tasks/backends/schtasks.js +434 -59
- package/dist/tasks/command-executable.js +93 -0
- package/dist/tasks/embedded.js +2 -0
- package/dist/tasks/parser.js +142 -6
- package/dist/tasks/resolve-akm-bin.js +19 -4
- package/dist/tasks/runner.js +258 -93
- package/dist/tasks/schedule.js +108 -19
- package/dist/tasks/scheduler-invocation.js +86 -0
- package/dist/tasks/task-id.js +37 -0
- package/dist/workflows/db.js +24 -13
- package/dist/workflows/validator.js +24 -2
- package/docs/README.md +103 -0
- package/docs/migration/release-notes/0.9.0.md +28 -9
- package/docs/migration/v0.8-to-v0.9.md +186 -26
- package/package.json +2 -3
package/dist/tasks/schedule.js
CHANGED
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
* • a launchd plist `<StartCalendarInterval>` / `<StartInterval>` (macOS),
|
|
16
16
|
* • Task Scheduler XML triggers (Windows).
|
|
17
17
|
*
|
|
18
|
-
* The shared subset is `*`, single integers, `*\/N`, plus the `@hourly /
|
|
18
|
+
* The shared subset is `*`, single integers, `*\/N`, `A-B/N`, plus the `@hourly /
|
|
19
19
|
* @daily / @weekly / @monthly` aliases. Patterns outside that — multi-value
|
|
20
|
-
* lists, ranges,
|
|
20
|
+
* lists, plain ranges, day-of-month AND
|
|
21
21
|
* day-of-week combinations — are rejected with a {@link UsageError}.
|
|
22
22
|
*
|
|
23
23
|
* Cron is the most permissive of the three backends; some patterns it
|
|
@@ -41,9 +41,9 @@ const FIELD_LIMITS = {
|
|
|
41
41
|
month: { min: 1, max: 12 },
|
|
42
42
|
dow: { min: 0, max: 6 },
|
|
43
43
|
};
|
|
44
|
-
const SUPPORTED_HINT = "Supported subset: `*`, single integers (`5`),
|
|
44
|
+
const SUPPORTED_HINT = "Supported subset: `*`, single integers (`5`), steps (`*/N`, `A-B/N`), and comma lists (`7,37`). " +
|
|
45
45
|
"Aliases: `@hourly`, `@daily`, `@weekly`, `@monthly`. " +
|
|
46
|
-
"
|
|
46
|
+
"Plain ranges and named days/months are not supported.";
|
|
47
47
|
export function parseSchedule(input, backend) {
|
|
48
48
|
const cron = expandAlias(input);
|
|
49
49
|
const fields = parseCronFields(cron, input);
|
|
@@ -99,6 +99,16 @@ function parseField(raw, name, limit, original) {
|
|
|
99
99
|
}
|
|
100
100
|
return { kind: "step", step };
|
|
101
101
|
}
|
|
102
|
+
const rangeStepMatch = raw.match(/^(\d+)-(\d+)\/(\d+)$/);
|
|
103
|
+
if (rangeStepMatch) {
|
|
104
|
+
const start = Number(rangeStepMatch[1]);
|
|
105
|
+
const end = Number(rangeStepMatch[2]);
|
|
106
|
+
const step = Number(rangeStepMatch[3]);
|
|
107
|
+
if (start < limit.min || end > limit.max || start > end || step <= 0) {
|
|
108
|
+
throw new UsageError(`Invalid ${name} range-step "${raw}" in schedule "${original}" (allowed ${limit.min}-${limit.max}).`, "INVALID_FLAG_VALUE");
|
|
109
|
+
}
|
|
110
|
+
return { kind: "rangeStep", start, end, step };
|
|
111
|
+
}
|
|
102
112
|
if (/^\d+$/.test(raw)) {
|
|
103
113
|
const value = Number(raw);
|
|
104
114
|
if (value < limit.min || value > limit.max) {
|
|
@@ -128,22 +138,28 @@ export function translateToCron(spec) {
|
|
|
128
138
|
}
|
|
129
139
|
export function translateToLaunchd(spec) {
|
|
130
140
|
const f = spec.fields;
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
rejectDomDowCombination(spec, "macOS launchd");
|
|
142
|
+
// Expand minute steps into clock-minute anchors. StartInterval would drift
|
|
143
|
+
// from cron whenever the agent is loaded away from a matching boundary.
|
|
144
|
+
if ((f.minute.kind === "step" || f.minute.kind === "rangeStep") &&
|
|
133
145
|
f.hour.kind === "star" &&
|
|
134
146
|
f.dom.kind === "star" &&
|
|
135
147
|
f.month.kind === "star" &&
|
|
136
148
|
f.dow.kind === "star") {
|
|
137
|
-
return {
|
|
149
|
+
return { calendars: expandFieldValues(f.minute, FIELD_LIMITS.minute).map((Minute) => ({ Minute })) };
|
|
138
150
|
}
|
|
139
|
-
//
|
|
151
|
+
// Likewise, anchor hour steps to the same hours cron selects each day.
|
|
140
152
|
if (f.minute.kind === "value" &&
|
|
141
|
-
f.
|
|
142
|
-
f.hour.kind === "step" &&
|
|
153
|
+
(f.hour.kind === "step" || f.hour.kind === "rangeStep") &&
|
|
143
154
|
f.dom.kind === "star" &&
|
|
144
155
|
f.month.kind === "star" &&
|
|
145
156
|
f.dow.kind === "star") {
|
|
146
|
-
return {
|
|
157
|
+
return {
|
|
158
|
+
calendars: expandFieldValues(f.hour, FIELD_LIMITS.hour).map((Hour) => ({
|
|
159
|
+
Minute: f.minute.kind === "value" ? f.minute.value : 0,
|
|
160
|
+
Hour,
|
|
161
|
+
})),
|
|
162
|
+
};
|
|
147
163
|
}
|
|
148
164
|
// Otherwise build a calendar dict from concrete values. launchd treats any
|
|
149
165
|
// omitted key as "every value", so a `*` field translates to "no key".
|
|
@@ -166,39 +182,101 @@ export function translateToLaunchd(spec) {
|
|
|
166
182
|
if (f.dow.kind === "value")
|
|
167
183
|
calendar.Weekday = f.dow.value;
|
|
168
184
|
// launchd's CalendarInterval requires at least one specific key. If every
|
|
169
|
-
//
|
|
170
|
-
//
|
|
185
|
+
// Expand every-minute to explicit minute boundaries rather than a
|
|
186
|
+
// load-relative 60-second interval.
|
|
171
187
|
if (Object.keys(calendar).length === 0) {
|
|
172
|
-
return {
|
|
188
|
+
return {
|
|
189
|
+
calendars: expandFieldValues({ kind: "step", step: 1 }, FIELD_LIMITS.minute).map((Minute) => ({ Minute })),
|
|
190
|
+
};
|
|
173
191
|
}
|
|
174
192
|
return { calendar };
|
|
175
193
|
}
|
|
194
|
+
function expandFieldValues(field, limit) {
|
|
195
|
+
const start = field.kind === "rangeStep" ? field.start : limit.min;
|
|
196
|
+
const end = field.kind === "rangeStep" ? field.end : limit.max;
|
|
197
|
+
const values = [];
|
|
198
|
+
for (let value = start; value <= end; value += field.step)
|
|
199
|
+
values.push(value);
|
|
200
|
+
return values;
|
|
201
|
+
}
|
|
176
202
|
function rejectStepInsideCalendar(field, name, spec) {
|
|
177
203
|
if (field.kind === "step") {
|
|
178
204
|
throw new UsageError(`Schedule "${spec.raw}" uses step (${name} = */N) in a position macOS launchd cannot express. ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE", "Either restrict the step to the minute or hour field only, or rewrite the schedule with concrete values.");
|
|
179
205
|
}
|
|
206
|
+
if (field.kind === "rangeStep") {
|
|
207
|
+
throw new UsageError(`Schedule "${spec.raw}" uses range-step (${name} = A-B/N) in a position macOS launchd cannot express. ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE", "Restrict the range-step to the minute or hour field, or rewrite the schedule with a concrete value.");
|
|
208
|
+
}
|
|
180
209
|
if (field.kind === "list") {
|
|
181
210
|
throw new UsageError(`Schedule "${spec.raw}" uses comma list (${name} = a,b,...) which macOS launchd cannot express as a single trigger. ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE", "Either install one task per list element, or rewrite the schedule with a step (`*/N`) or single value.");
|
|
182
211
|
}
|
|
183
212
|
}
|
|
213
|
+
const MAX_SCHTASKS_TRIGGERS = 48;
|
|
184
214
|
export function translateToSchtasks(spec) {
|
|
185
215
|
const f = spec.fields;
|
|
186
|
-
|
|
216
|
+
rejectDomDowCombination(spec, "Windows Task Scheduler");
|
|
217
|
+
// A repetition interval is cron-equivalent indefinitely only when it divides
|
|
218
|
+
// the field's wall-clock cycle. Other steps must reset at every hour.
|
|
187
219
|
if (f.minute.kind === "step" &&
|
|
188
220
|
f.hour.kind === "star" &&
|
|
189
221
|
f.dom.kind === "star" &&
|
|
190
222
|
f.month.kind === "star" &&
|
|
191
223
|
f.dow.kind === "star") {
|
|
224
|
+
if (60 % f.minute.step !== 0) {
|
|
225
|
+
return minuteValuesTrigger(expandFieldValues(f.minute, FIELD_LIMITS.minute), spec);
|
|
226
|
+
}
|
|
192
227
|
return { kind: "minute", everyMinutes: f.minute.step };
|
|
193
228
|
}
|
|
194
|
-
|
|
229
|
+
if (f.minute.kind === "rangeStep" &&
|
|
230
|
+
f.hour.kind === "star" &&
|
|
231
|
+
f.dom.kind === "star" &&
|
|
232
|
+
f.month.kind === "star" &&
|
|
233
|
+
f.dow.kind === "star") {
|
|
234
|
+
if (f.minute.start === FIELD_LIMITS.minute.min && f.minute.end === FIELD_LIMITS.minute.max) {
|
|
235
|
+
if (60 % f.minute.step === 0)
|
|
236
|
+
return { kind: "minute", everyMinutes: f.minute.step };
|
|
237
|
+
}
|
|
238
|
+
return minuteValuesTrigger(expandFieldValues(f.minute, FIELD_LIMITS.minute), spec);
|
|
239
|
+
}
|
|
240
|
+
// Fixed-minute hour schedules can use one daily-reset repetition only when
|
|
241
|
+
// the interval divides 24 hours. Non-divisors become explicit daily times.
|
|
195
242
|
if (f.minute.kind === "value" &&
|
|
196
|
-
f.minute.value === 0 &&
|
|
197
243
|
f.hour.kind === "step" &&
|
|
198
244
|
f.dom.kind === "star" &&
|
|
199
245
|
f.month.kind === "star" &&
|
|
200
246
|
f.dow.kind === "star") {
|
|
201
|
-
|
|
247
|
+
if (24 % f.hour.step === 0 && f.hour.step < 24) {
|
|
248
|
+
return { kind: "hour", everyHours: f.hour.step, atMinute: f.minute.value };
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
kind: "hourValues",
|
|
252
|
+
hours: expandFieldValues(f.hour, FIELD_LIMITS.hour),
|
|
253
|
+
atMinute: f.minute.value,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (f.minute.kind === "value" &&
|
|
257
|
+
f.hour.kind === "rangeStep" &&
|
|
258
|
+
f.dom.kind === "star" &&
|
|
259
|
+
f.month.kind === "star" &&
|
|
260
|
+
f.dow.kind === "star") {
|
|
261
|
+
if (f.hour.start === FIELD_LIMITS.hour.min && f.hour.end === FIELD_LIMITS.hour.max) {
|
|
262
|
+
if (24 % f.hour.step === 0 && f.hour.step < 24) {
|
|
263
|
+
return { kind: "hour", everyHours: f.hour.step, atMinute: f.minute.value };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
kind: "hourValues",
|
|
268
|
+
hours: expandFieldValues(f.hour, FIELD_LIMITS.hour),
|
|
269
|
+
atMinute: f.minute.value,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
// `M * * * *` includes the shipped top-of-hour task and arbitrary fixed
|
|
273
|
+
// minutes. A daily calendar trigger resets the hourly repetition each day.
|
|
274
|
+
if (f.minute.kind === "value" &&
|
|
275
|
+
f.hour.kind === "star" &&
|
|
276
|
+
f.dom.kind === "star" &&
|
|
277
|
+
f.month.kind === "star" &&
|
|
278
|
+
f.dow.kind === "star") {
|
|
279
|
+
return { kind: "hour", everyHours: 1, atMinute: f.minute.value };
|
|
202
280
|
}
|
|
203
281
|
// `M H * * *` → DAILY at H:M.
|
|
204
282
|
if (f.minute.kind === "value" &&
|
|
@@ -221,7 +299,18 @@ export function translateToSchtasks(spec) {
|
|
|
221
299
|
daysOfWeek: [f.dow.value],
|
|
222
300
|
};
|
|
223
301
|
}
|
|
224
|
-
throw new UsageError(`Schedule "${spec.raw}" cannot be expressed as a Windows Task Scheduler trigger. ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE", "Use one of:
|
|
302
|
+
throw new UsageError(`Schedule "${spec.raw}" cannot be expressed as a Windows Task Scheduler trigger. ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE", "Use one of: minute steps/range-steps, fixed-minute hour steps/range-steps, hourly, daily, or weekly on a single weekday.");
|
|
303
|
+
}
|
|
304
|
+
function minuteValuesTrigger(minutes, spec) {
|
|
305
|
+
if (minutes.length > MAX_SCHTASKS_TRIGGERS) {
|
|
306
|
+
throw new UsageError(`Schedule "${spec.raw}" requires ${minutes.length} native triggers; Windows Task Scheduler allows at most ${MAX_SCHTASKS_TRIGGERS}.`, "INVALID_FLAG_VALUE", "Use a full-field step whose interval divides 60, or reduce the minute range.");
|
|
307
|
+
}
|
|
308
|
+
return { kind: "minuteValues", minutes };
|
|
309
|
+
}
|
|
310
|
+
function rejectDomDowCombination(spec, backend) {
|
|
311
|
+
if (spec.fields.dom.kind !== "star" && spec.fields.dow.kind !== "star") {
|
|
312
|
+
throw new UsageError(`Schedule "${spec.raw}": day-of-month and day-of-week use OR semantics in cron, which ${backend} cannot express portably.`, "INVALID_FLAG_VALUE", "Use either day-of-month or day-of-week, but not both.");
|
|
313
|
+
}
|
|
225
314
|
}
|
|
226
315
|
/** Human-readable summary used by `tasks doctor`. */
|
|
227
316
|
export const SCHEDULE_SUPPORTED_SUBSET_HINT = SUPPORTED_HINT;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { resolveStashDir } from "../core/common.js";
|
|
6
|
+
import { ConfigError } from "../core/errors.js";
|
|
7
|
+
import { getCacheDir, getConfigDir, getDataDir } from "../core/paths.js";
|
|
8
|
+
export const SCHEDULED_TASK_CONTEXT_KEYS = [
|
|
9
|
+
"AKM_STASH_DIR",
|
|
10
|
+
"AKM_CONFIG_DIR",
|
|
11
|
+
"AKM_DATA_DIR",
|
|
12
|
+
"AKM_CACHE_DIR",
|
|
13
|
+
"AKM_STATE_DIR",
|
|
14
|
+
];
|
|
15
|
+
/** Resolve the complete non-secret AKM directory context captured by schedulers. */
|
|
16
|
+
export function resolveScheduledTaskContext(env = process.env, platform = process.platform) {
|
|
17
|
+
return canonicalContext({
|
|
18
|
+
AKM_STASH_DIR: path.resolve(resolveStashDir(undefined, env)),
|
|
19
|
+
AKM_CONFIG_DIR: path.resolve(getConfigDir(env, platform)),
|
|
20
|
+
AKM_DATA_DIR: path.resolve(getDataDir(env, platform)),
|
|
21
|
+
AKM_CACHE_DIR: path.resolve(getCacheDir(env)),
|
|
22
|
+
// Retain the legacy state root for scheduled commands and upgrade tooling
|
|
23
|
+
// that still honor it even though current durable state lives under DATA.
|
|
24
|
+
AKM_STATE_DIR: path.resolve(resolveStateDir(env, platform)),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Build the one scheduler-generated argv shape consumed by all backends. */
|
|
28
|
+
export function buildScheduledTaskInvocation(akmArgv, id, context) {
|
|
29
|
+
const environment = canonicalContext(context);
|
|
30
|
+
return {
|
|
31
|
+
argv: [...akmArgv, "tasks", "run", id, "--scheduled"],
|
|
32
|
+
environment,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function canonicalContext(input) {
|
|
36
|
+
const inputKeys = Object.keys(input);
|
|
37
|
+
if (inputKeys.length !== SCHEDULED_TASK_CONTEXT_KEYS.length ||
|
|
38
|
+
inputKeys.some((key) => !SCHEDULED_TASK_CONTEXT_KEYS.includes(key))) {
|
|
39
|
+
throw invalidSchedulerContext();
|
|
40
|
+
}
|
|
41
|
+
const context = {};
|
|
42
|
+
for (const key of SCHEDULED_TASK_CONTEXT_KEYS) {
|
|
43
|
+
const value = input[key];
|
|
44
|
+
if (typeof value !== "string" ||
|
|
45
|
+
value.trim().length === 0 ||
|
|
46
|
+
containsControlCharacter(value) ||
|
|
47
|
+
(!path.posix.isAbsolute(value) && !path.win32.isAbsolute(value))) {
|
|
48
|
+
throw invalidSchedulerContext();
|
|
49
|
+
}
|
|
50
|
+
context[key] = value;
|
|
51
|
+
}
|
|
52
|
+
return context;
|
|
53
|
+
}
|
|
54
|
+
function containsControlCharacter(value) {
|
|
55
|
+
for (const char of value) {
|
|
56
|
+
const code = char.codePointAt(0) ?? 0;
|
|
57
|
+
if (code < 0x20 || (code >= 0x7f && code <= 0x9f))
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
function resolveStateDir(env, platform) {
|
|
63
|
+
const override = env.AKM_STATE_DIR?.trim();
|
|
64
|
+
if (override)
|
|
65
|
+
return override;
|
|
66
|
+
if (platform === "win32") {
|
|
67
|
+
const localAppData = env.LOCALAPPDATA?.trim();
|
|
68
|
+
if (localAppData)
|
|
69
|
+
return path.join(localAppData, "akm", "state");
|
|
70
|
+
const userProfile = env.USERPROFILE?.trim();
|
|
71
|
+
if (userProfile)
|
|
72
|
+
return path.join(userProfile, "AppData", "Local", "akm", "state");
|
|
73
|
+
const appData = env.APPDATA?.trim();
|
|
74
|
+
if (appData)
|
|
75
|
+
return path.join(appData, "..", "Local", "akm", "state");
|
|
76
|
+
throw new ConfigError("Unable to determine state directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
|
|
77
|
+
}
|
|
78
|
+
const xdgStateHome = env.XDG_STATE_HOME?.trim();
|
|
79
|
+
if (xdgStateHome)
|
|
80
|
+
return path.join(xdgStateHome, "akm");
|
|
81
|
+
const home = env.HOME?.trim();
|
|
82
|
+
return home ? path.join(home, ".local", "state", "akm") : path.join("/tmp", "akm-state");
|
|
83
|
+
}
|
|
84
|
+
function invalidSchedulerContext() {
|
|
85
|
+
return new ConfigError(`Invalid scheduler context; expected exactly ${SCHEDULED_TASK_CONTEXT_KEYS.join(", ")} as absolute paths.`, "INVALID_CONFIG_FILE");
|
|
86
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { UsageError } from "../core/errors.js";
|
|
5
|
+
const VALID_TASK_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
6
|
+
const TASK_FILE_SUFFIX_RE = /\.(?:yml|yaml)$/i;
|
|
7
|
+
const WINDOWS_RESERVED_DEVICE_RE = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i;
|
|
8
|
+
const WINDOWS_TASK_PATH_MAX_LENGTH = 238;
|
|
9
|
+
const WINDOWS_TASK_FOLDER_PREFIX_LENGTH = "\\akm\\".length;
|
|
10
|
+
const PORTABLE_FILENAME_COMPONENT_MAX_LENGTH = 255;
|
|
11
|
+
const LAUNCHD_FILENAME_OVERHEAD = "com.akm.task.".length + ".plist".length;
|
|
12
|
+
const TASK_FILENAME_OVERHEAD = ".yml".length;
|
|
13
|
+
const SCHTASKS_TEMP_FILENAME_OVERHEAD = "akm-task-".length + "-".length + 13 + ".xml".length;
|
|
14
|
+
export const MAX_PORTABLE_TASK_ID_LENGTH = Math.min(WINDOWS_TASK_PATH_MAX_LENGTH - WINDOWS_TASK_FOLDER_PREFIX_LENGTH, PORTABLE_FILENAME_COMPONENT_MAX_LENGTH - LAUNCHD_FILENAME_OVERHEAD, PORTABLE_FILENAME_COMPONENT_MAX_LENGTH - TASK_FILENAME_OVERHEAD, PORTABLE_FILENAME_COMPONENT_MAX_LENGTH - SCHTASKS_TEMP_FILENAME_OVERHEAD);
|
|
15
|
+
export function validateTaskId(id) {
|
|
16
|
+
if (!id) {
|
|
17
|
+
throw new UsageError("Task id must be non-empty.", "MISSING_REQUIRED_ARGUMENT");
|
|
18
|
+
}
|
|
19
|
+
if (!VALID_TASK_ID_RE.test(id)) {
|
|
20
|
+
throw new UsageError(`Task id "${id}" is invalid. Use letters, digits, dots, underscores, and dashes only.`, "INVALID_FLAG_VALUE");
|
|
21
|
+
}
|
|
22
|
+
if (id.length > MAX_PORTABLE_TASK_ID_LENGTH) {
|
|
23
|
+
throw new UsageError(`Task id "${id}" is invalid. Use at most ${MAX_PORTABLE_TASK_ID_LENGTH} characters for all supported schedulers.`, "INVALID_FLAG_VALUE");
|
|
24
|
+
}
|
|
25
|
+
if (TASK_FILE_SUFFIX_RE.test(id)) {
|
|
26
|
+
throw new UsageError(`Task id "${id}" is invalid. Use the bare task id without a .yml or .yaml suffix.`, "INVALID_FLAG_VALUE");
|
|
27
|
+
}
|
|
28
|
+
if (WINDOWS_RESERVED_DEVICE_RE.test(id)) {
|
|
29
|
+
throw new UsageError(`Task id "${id}" uses a reserved Windows device name. Choose a different task id.`, "INVALID_FLAG_VALUE");
|
|
30
|
+
}
|
|
31
|
+
return id;
|
|
32
|
+
}
|
|
33
|
+
export function normaliseTaskId(raw) {
|
|
34
|
+
// Keep accepting old task-file suffixes at the CLI boundary, but never
|
|
35
|
+
// normalize filesystem-derived ids into a different filename.
|
|
36
|
+
return validateTaskId(raw.trim().replace(/\.(yml|md)$/, ""));
|
|
37
|
+
}
|
package/dist/workflows/db.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { acquireMaintenanceActivitySync } from "../core/maintenance-barrier.js";
|
|
7
|
-
import {
|
|
7
|
+
import { assertNoPendingMigrationOperation } from "../core/migration-operation.js";
|
|
8
8
|
import { getWorkflowDbPath } from "../core/paths.js";
|
|
9
9
|
import { openDatabase } from "../storage/database.js";
|
|
10
|
-
import { runMigrations as runSqliteMigrations } from "../storage/engines/sqlite-migrations.js";
|
|
10
|
+
import { assertCurrentMigrationLedger, assertMigrationLedger, runMigrations as runSqliteMigrations, } from "../storage/engines/sqlite-migrations.js";
|
|
11
11
|
import { applyStandardPragmas } from "../storage/sqlite-pragmas.js";
|
|
12
12
|
/**
|
|
13
13
|
* workflow.db — Durable SQLite database for workflow run state.
|
|
@@ -45,24 +45,37 @@ import { applyStandardPragmas } from "../storage/sqlite-pragmas.js";
|
|
|
45
45
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
46
46
|
export function openWorkflowDatabase(dbPath = getWorkflowDbPath()) {
|
|
47
47
|
const isCanonical = path.resolve(dbPath) === path.resolve(getWorkflowDbPath());
|
|
48
|
+
if (isCanonical)
|
|
49
|
+
assertNoPendingMigrationOperation();
|
|
48
50
|
const releaseActivity = isCanonical ? acquireMaintenanceActivitySync("workflow-db") : undefined;
|
|
49
51
|
let db;
|
|
50
52
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
ensureMigrationBackup();
|
|
53
|
+
if (isCanonical)
|
|
54
|
+
assertNoPendingMigrationOperation();
|
|
55
|
+
const existed = fs.existsSync(dbPath);
|
|
55
56
|
const dir = path.dirname(dbPath);
|
|
56
57
|
if (!fs.existsSync(dir)) {
|
|
57
58
|
fs.mkdirSync(dir, { recursive: true });
|
|
58
59
|
}
|
|
60
|
+
if (existed) {
|
|
61
|
+
const preflight = openDatabase(dbPath, { readonly: true });
|
|
62
|
+
try {
|
|
63
|
+
if (isCanonical)
|
|
64
|
+
assertCurrentMigrationLedger(preflight, WORKFLOW_MIGRATIONS);
|
|
65
|
+
else
|
|
66
|
+
assertMigrationLedger(preflight, WORKFLOW_MIGRATIONS);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
preflight.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
59
72
|
db = openDatabase(dbPath);
|
|
60
73
|
// #589: 30 s busy timeout, matching index.db / state.db. Without it the
|
|
61
74
|
// default is 0 ms, so any concurrent writer fails immediately with
|
|
62
75
|
// SQLITE_BUSY. #628: journal_mode is configurable via AKM_SQLITE_JOURNAL_MODE.
|
|
63
76
|
applyStandardPragmas(db, { dataDir: dir });
|
|
64
77
|
ensureBaseSchema(db);
|
|
65
|
-
runMigrations(db, {
|
|
78
|
+
runMigrations(db, { applyPending: !(isCanonical && existed) });
|
|
66
79
|
if (!releaseActivity)
|
|
67
80
|
return db;
|
|
68
81
|
const openedDb = db;
|
|
@@ -156,7 +169,7 @@ function ensureBaseSchema(db) {
|
|
|
156
169
|
* All workflow.db migrations in application order. New migrations are
|
|
157
170
|
* APPENDED — never inserted in the middle or reordered.
|
|
158
171
|
*/
|
|
159
|
-
const
|
|
172
|
+
export const WORKFLOW_MIGRATIONS = [
|
|
160
173
|
// ── Migration 001 — add scope_key column ────────────────────────────────────
|
|
161
174
|
//
|
|
162
175
|
// Adds the `scope_key` column to `workflow_runs` so runs can be partitioned
|
|
@@ -392,11 +405,9 @@ function bootstrapPreVersioningDb(db) {
|
|
|
392
405
|
* Called automatically by {@link openWorkflowDatabase}.
|
|
393
406
|
*/
|
|
394
407
|
export function runMigrations(db, options) {
|
|
395
|
-
runSqliteMigrations(db,
|
|
408
|
+
runSqliteMigrations(db, WORKFLOW_MIGRATIONS, {
|
|
396
409
|
bootstrap: bootstrapPreVersioningDb,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
ensureMigrationBackup();
|
|
400
|
-
},
|
|
410
|
+
applyPending: options?.applyPending,
|
|
411
|
+
generationMarker: options?.generationMarker,
|
|
401
412
|
});
|
|
402
413
|
}
|
|
@@ -8,15 +8,37 @@
|
|
|
8
8
|
* the whole document or the raw frontmatter at once: duplicate step IDs,
|
|
9
9
|
* step-id format, and the frontmatter key whitelist.
|
|
10
10
|
*/
|
|
11
|
+
import { parseAssetRef, refToString } from "../core/asset/asset-ref.js";
|
|
11
12
|
import { utf8Bytes, WORKFLOW_MAX_INSTRUCTION_BYTES, WORKFLOW_MAX_PARAMS, WORKFLOW_MAX_STEPS } from "./resource-limits.js";
|
|
12
13
|
const STEP_ID_REGEX = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
13
|
-
const ALLOWED_FRONTMATTER_KEYS = new Set(["description", "tags", "params", "name", "updated", "when_to_use"]);
|
|
14
|
+
const ALLOWED_FRONTMATTER_KEYS = new Set(["description", "tags", "params", "name", "updated", "when_to_use", "xrefs"]);
|
|
14
15
|
export function runSemanticChecks(draft, frontmatterData, frontmatterEndLine, errors) {
|
|
15
16
|
checkFrontmatterKeys(frontmatterData, frontmatterEndLine, errors);
|
|
17
|
+
checkXrefs(frontmatterData.xrefs, frontmatterEndLine, errors);
|
|
16
18
|
checkStepIdFormat(draft, errors);
|
|
17
19
|
checkDuplicateStepIds(draft, errors);
|
|
18
20
|
checkResourceLimits(draft, errors);
|
|
19
21
|
}
|
|
22
|
+
function checkXrefs(value, line, errors) {
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
return;
|
|
25
|
+
if (!Array.isArray(value)) {
|
|
26
|
+
errors.push({ line, message: 'Workflow frontmatter "xrefs" must be an array of canonical asset refs.' });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
for (const ref of value) {
|
|
30
|
+
try {
|
|
31
|
+
if (typeof ref !== "string" || refToString(parseAssetRef(ref)) !== ref)
|
|
32
|
+
throw new Error("non-canonical ref");
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
errors.push({
|
|
36
|
+
line,
|
|
37
|
+
message: `Workflow frontmatter "xrefs" contains an invalid or non-canonical ref: ${String(ref)}.`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
20
42
|
function checkResourceLimits(draft, errors) {
|
|
21
43
|
if (draft.steps.length > WORKFLOW_MAX_STEPS) {
|
|
22
44
|
errors.push({ line: 1, message: `Workflow must contain at most ${WORKFLOW_MAX_STEPS} steps.` });
|
|
@@ -39,7 +61,7 @@ function checkFrontmatterKeys(data, fmEndLine, errors) {
|
|
|
39
61
|
continue;
|
|
40
62
|
errors.push({
|
|
41
63
|
line: fmEndLine,
|
|
42
|
-
message: `Workflow frontmatter "${key}" is not supported. Use only: description, tags, params, name, updated, when_to_use.`,
|
|
64
|
+
message: `Workflow frontmatter "${key}" is not supported. Use only: description, tags, params, name, updated, when_to_use, xrefs.`,
|
|
43
65
|
});
|
|
44
66
|
}
|
|
45
67
|
}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
- [Concepts](concepts.md) -- Stashes, registries, asset types, and refs
|
|
6
|
+
- [Getting Started](getting-started.md) -- Quick setup guide
|
|
7
|
+
- [Local Development](local-development.md) -- Dogfooding akm while editing its own source
|
|
8
|
+
- [Agent Install Guide](agents/agent-install.md) -- Step-by-step automated install for agents
|
|
9
|
+
- [Stash Maker's Guide](stash-makers.md) -- Build and share a stash on GitHub, npm, or a network directory
|
|
10
|
+
- [Wikis](wikis.md) -- Multi-wiki knowledge bases (Karpathy-style)
|
|
11
|
+
|
|
12
|
+
## Configuration & Data
|
|
13
|
+
|
|
14
|
+
- [Configuration](configuration.md) -- Providers, settings, and Ollama setup
|
|
15
|
+
- [Configuration](configuration.md#engines) -- Named LLM and agent engines
|
|
16
|
+
- [Data & Telemetry](data-and-telemetry.md) -- Exactly what akm reads and writes on your machine (no remote telemetry)
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- [Workflows](features/workflows.md) -- Structured, resumable multi-step procedures
|
|
21
|
+
- [Search & Discovery](features/search-discovery.md) -- Finding assets without knowing their exact name
|
|
22
|
+
- [Knowledge Management](features/knowledge-management.md) -- Lessons, incidents, and research as first-class assets
|
|
23
|
+
- [Sources & Registries](features/sources-registries.md) -- Where assets come from and how to find more
|
|
24
|
+
- [Wiki Snapshot Fetchers](features/wiki-snapshot-fetchers.md) -- Pluggable fetchers for URL-based knowledge reads
|
|
25
|
+
- [Agent Integration](features/agent-integration.md) -- Wiring akm into any shell-capable coding agent
|
|
26
|
+
- [The Improvement Loop](features/improvement-loop.md) -- How the stash adapts from usage and feedback
|
|
27
|
+
|
|
28
|
+
## Upgrading
|
|
29
|
+
|
|
30
|
+
- [Roadmap](roadmap.md) -- High-level focus for the 0.9 and 1.0 releases
|
|
31
|
+
- [v0.8 -> v0.9 migration guide](migration/v0.8-to-v0.9.md) -- Current-cycle breaking changes
|
|
32
|
+
- [Release notes (latest: 0.9.0)](migration/release-notes/0.9.0.md) -- Per-release notes; see the [release-notes index](migration/release-notes/README.md) for every version
|
|
33
|
+
- [v0.5 -> v0.6 migration guide](migration/v0.5-to-v0.6.md) -- Every breaking change with before/after code, publisher checklist, and troubleshooting
|
|
34
|
+
|
|
35
|
+
## Reference
|
|
36
|
+
|
|
37
|
+
- [CLI](cli.md) -- All `akm` commands and flags
|
|
38
|
+
- [Registry](registry.md) -- Registries, search, hosting, and managing sources
|
|
39
|
+
- [akm-eval](akm-eval.md) -- Standalone toolkit for measuring whether `akm improve` is working
|
|
40
|
+
|
|
41
|
+
## Agents
|
|
42
|
+
|
|
43
|
+
- [AGENTS.md](agents/AGENTS.md) -- The system-prompt reference agents load to use akm
|
|
44
|
+
- [Curate Workmap](agents/curate-workmap.md) -- Read before changing `akm curate` ranking or output
|
|
45
|
+
|
|
46
|
+
## Architecture
|
|
47
|
+
|
|
48
|
+
- [Architecture](technical/architecture.md) -- How akm's sources, cache, index, and registries fit together
|
|
49
|
+
- [Runtime Boundary Design](architecture/runtime-boundary-design.md) -- Isolating `bun:sqlite`/`Bun.*` from the core
|
|
50
|
+
- [Brain Workflow (diagram)](architecture/brain-workflow.html) -- Visual map of the improve/self-learning loop
|
|
51
|
+
|
|
52
|
+
## Example Stash
|
|
53
|
+
|
|
54
|
+
- [Example Stash](example-stash/README.md) -- A documentation-backed example stash showing how asset types fit together
|
|
55
|
+
|
|
56
|
+
## Analysis
|
|
57
|
+
|
|
58
|
+
- [Indexer Vertical Slice Refactor Plan](analysis/indexer-vertical-slice-refactor-plan.md)
|
|
59
|
+
- [Indexer Refactor Review (Expert Options)](analysis/indexer-refactor-expert-options.md)
|
|
60
|
+
|
|
61
|
+
## Design (unshipped work)
|
|
62
|
+
|
|
63
|
+
- [Self-Improvement, Self-Learning & Memory Reference Index](design/self-improvement-learning-memory-reference-index.md) -- Master index for every unshipped improve/self-learning/memory design doc, with a subsystem-to-doc status table
|
|
64
|
+
|
|
65
|
+
Every doc under `docs/design/` must carry a `Status` / `Supersedes` / `Date` header (pre-existing docs are grandfathered until next touched). When a design ships, the shipping PR moves it to `docs/archive/` in the same PR.
|
|
66
|
+
|
|
67
|
+
## Archive
|
|
68
|
+
|
|
69
|
+
- [Archive](archive/README.md) -- Design and implementation plans whose work has already shipped, retained as ADR-style records
|
|
70
|
+
|
|
71
|
+
## Internals (technical/)
|
|
72
|
+
|
|
73
|
+
- [Filesystem](technical/filesystem.md) -- Directory layout plus `.stash.json` deprecation and migration notes
|
|
74
|
+
- [Search](technical/search.md) -- Hybrid search architecture and scoring
|
|
75
|
+
- [Indexing](technical/indexing.md) -- How the search index is built
|
|
76
|
+
- [Classification](technical/classification.md) -- Matcher and renderer behavior
|
|
77
|
+
- [Storage Locations](technical/storage-locations.md) -- Authoritative inventory of every on-disk read/write path
|
|
78
|
+
- [Improve Workflow](technical/improve-workflow.md) -- `akm improve` command surface and pipeline reference
|
|
79
|
+
- [Health Advisories](technical/health-advisories.md) -- `akm health` advisory-to-action map for operators
|
|
80
|
+
- [Fresh-Host Rebuild Runbook](technical/fresh-host-rebuild-runbook.md) -- Rebuild an akm install on a new machine
|
|
81
|
+
- [Ranking Ablation & Saturation Analysis](technical/ranking-ablation-and-saturation-analysis.md) -- Reproducible contributor-ablation measurement and the score-saturation trap
|
|
82
|
+
- [Functional Contract Patterns](technical/functional-contract-patterns.md) -- Quick reference for contributor pipelines and small process contracts
|
|
83
|
+
- [Test Coverage Guide](technical/test-coverage-guide.md) -- High-value testing areas
|
|
84
|
+
- [Testing Workflow](technical/testing-workflow.md) -- End-to-end, Docker, deployment, and upgrade validation
|
|
85
|
+
- [Ref Format](technical/ref.md) -- Wire format for asset references
|
|
86
|
+
- [Core Principles](technical/akm-core-principles.md) -- Design principles and constraints
|
|
87
|
+
- [Claude Code workflows vs. akm workflows](technical/claude-code-vs-akm-workflows.md) -- Comparing the two things that share a name
|
|
88
|
+
- [Workflow source and IR](features/workflows.md) -- Current workflow execution model
|
|
89
|
+
|
|
90
|
+
## Official Ecosystem Repositories
|
|
91
|
+
|
|
92
|
+
- [itlackey/akm-stash](https://github.com/itlackey/akm-stash) -- the official onboarding stash with ready-made assets you can install with `akm add`
|
|
93
|
+
- [itlackey/akm-registry](https://github.com/itlackey/akm-registry) -- the official registry index that powers built-in discovery
|
|
94
|
+
- [itlackey/akm-plugins](https://github.com/itlackey/akm-plugins) -- optional integrations for tools like OpenCode
|
|
95
|
+
- [itlackey/akm-bench](https://github.com/itlackey/akm-bench) -- the standalone benchmark and evaluation repo for akm
|
|
96
|
+
|
|
97
|
+
## Posts
|
|
98
|
+
|
|
99
|
+
- [Blog posts](posts/) -- Articles and posts about akm
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
New docs, in five lines: keep one current-truth doc per subsystem, don't fork a second one. Unshipped designs live in `docs/design/` with a mandatory `Status` / `Supersedes` / `Date` header. The PR that ships the design moves its doc to `docs/archive/` in that same PR. Cite code by symbol and memories by search-terms -- not line numbers or exact refs, both rot. Nothing in `docs/` may reference `.plans/` (it's scratch -- promote the content or drop the link); no new improve-analysis docs until the 30-clean-day gate.
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
Migration notes for akm v0.9.0
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
These notes apply to the 0.9.0 release candidates and final release. If you
|
|
4
|
+
were already running a 0.9.0 beta, align scripts, prompts, and stash layout
|
|
5
|
+
with the current command surface and verify every durable artifact before
|
|
6
|
+
normal use.
|
|
6
7
|
|
|
7
8
|
Key operator-facing changes:
|
|
8
9
|
|
|
9
|
-
-
|
|
10
|
-
>=
|
|
10
|
+
- The published npm package requires Node.js >= 20.12 as its cross-platform
|
|
11
|
+
bootstrap. When a working Bun >= 1.0 is also on `PATH`, the launcher prefers
|
|
12
|
+
Bun after bootstrap; old, unusable, or absent Bun installations fall back to
|
|
13
|
+
Node.js. The standalone binaries are runtime-free.
|
|
11
14
|
- The legacy `vault` asset type and `akm vault ...` command family are gone.
|
|
12
15
|
Use `env:` for whole `.env` groups and `secret:` for a single sensitive
|
|
13
16
|
value.
|
|
@@ -17,6 +20,9 @@ Key operator-facing changes:
|
|
|
17
20
|
- Proposal workflow is fully consolidated around `akm improve`, `akm propose`,
|
|
18
21
|
and `akm proposal ...`. Update any old `akm reflect`, `akm distill`,
|
|
19
22
|
`akm accept`, `akm reject`, or `akm proposals` usage.
|
|
23
|
+
- Config, `state.db`, and `workflow.db` are now classified independently by the
|
|
24
|
+
explicit `akm migrate` coordinator. Normal commands refuse old, future, or
|
|
25
|
+
divergent durable schemas instead of attempting migration as a side effect.
|
|
20
26
|
|
|
21
27
|
Primary public command family for 0.9.0:
|
|
22
28
|
|
|
@@ -28,11 +34,24 @@ Primary public command family for 0.9.0:
|
|
|
28
34
|
- `akm proposal accept <id>`
|
|
29
35
|
- `akm proposal reject <id> --reason "..."`
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
37
|
+
Required upgrade checks:
|
|
38
|
+
|
|
39
|
+
- Before crossing from 0.8 to 0.9, create an independent filesystem backup and
|
|
40
|
+
prepare a valid 0.9 config. The installed 0.8 binary does not know the 0.9
|
|
41
|
+
migration protocol: install or stage the 0.9 binary, then run
|
|
42
|
+
`akm migrate apply --config <prepared-0.9-config>` before any normal command.
|
|
43
|
+
- On an existing 0.9 installation, run `akm migrate status`. If it reports
|
|
44
|
+
`ready`, run `akm migrate apply`; an already-current active config is used as
|
|
45
|
+
the target.
|
|
46
|
+
- Do not continue when status reports `blocked`. Preserve the reported backup
|
|
47
|
+
run and resolve the named artifact or active-operation error first.
|
|
48
|
+
- Restore is explicit and destructive: `akm backup restore --for 0.9.0 --run
|
|
49
|
+
<backup-run-id> --confirm`. It creates and verifies a rescue backup before
|
|
50
|
+
replacing current artifacts.
|
|
51
|
+
- Run `akm help migrate 0.9.0` for the storage and command-surface checklist.
|
|
34
52
|
- Run `akm-migrate-storage --yes` once if the stash ever used `vaults/`.
|
|
35
|
-
- Rebuild
|
|
53
|
+
- Rebuild derived search data with `akm index` only after migration status is
|
|
54
|
+
`current`; indexing does not migrate config or durable schemas.
|
|
36
55
|
- Review agent instructions and docs for old `vault`, `reflect`, and `distill`
|
|
37
56
|
examples.
|
|
38
57
|
|