akm-cli 0.7.5 → 0.8.0-rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CHANGELOG.md +1 -1
- package/dist/cli/parse-args.js +43 -0
- package/dist/cli.js +853 -479
- package/dist/commands/agent-dispatch.js +102 -0
- package/dist/commands/agent-support.js +62 -0
- package/dist/commands/config-cli.js +68 -84
- package/dist/commands/consolidate.js +823 -0
- package/dist/commands/distill-promotion-policy.js +658 -0
- package/dist/commands/distill.js +244 -52
- package/dist/commands/eval-cases.js +40 -0
- package/dist/commands/events.js +2 -23
- package/dist/commands/graph.js +222 -0
- package/dist/commands/health.js +376 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +53 -0
- package/dist/commands/help/help-proposals.md +15 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +8 -0
- package/dist/commands/history.js +3 -30
- package/dist/commands/improve.js +1170 -0
- package/dist/commands/info.js +2 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/install-audit.js +5 -1
- package/dist/commands/installed-stashes.js +118 -138
- package/dist/commands/knowledge.js +133 -0
- package/dist/commands/lint/agent-linter.js +46 -0
- package/dist/commands/lint/base-linter.js +285 -0
- package/dist/commands/lint/command-linter.js +46 -0
- package/dist/commands/lint/default-linter.js +13 -0
- package/dist/commands/lint/index.js +107 -0
- package/dist/commands/lint/knowledge-linter.js +13 -0
- package/dist/commands/lint/memory-linter.js +58 -0
- package/dist/commands/lint/registry.js +33 -0
- package/dist/commands/lint/skill-linter.js +42 -0
- package/dist/commands/lint/task-linter.js +47 -0
- package/dist/commands/lint/types.js +1 -0
- package/dist/commands/lint/workflow-linter.js +53 -0
- package/dist/commands/lint.js +1 -0
- package/dist/commands/proposal.js +8 -7
- package/dist/commands/propose.js +78 -28
- package/dist/commands/reflect.js +143 -35
- package/dist/commands/registry-search.js +2 -2
- package/dist/commands/remember.js +54 -0
- package/dist/commands/schema-repair.js +130 -0
- package/dist/commands/search.js +21 -5
- package/dist/commands/show.js +121 -17
- package/dist/commands/source-add.js +10 -10
- package/dist/commands/source-manage.js +11 -19
- package/dist/commands/tasks.js +385 -0
- package/dist/commands/url-checker.js +39 -0
- package/dist/commands/vault.js +8 -26
- package/dist/core/action-contributors.js +25 -0
- package/dist/core/asset-ref.js +4 -0
- package/dist/core/asset-registry.js +4 -16
- package/dist/core/asset-spec.js +10 -0
- package/dist/core/common.js +94 -0
- package/dist/core/concurrent.js +22 -0
- package/dist/core/config.js +222 -128
- package/dist/core/events.js +73 -126
- package/dist/core/frontmatter.js +3 -1
- package/dist/core/markdown.js +17 -0
- package/dist/core/memory-improve.js +678 -0
- package/dist/core/parse.js +155 -0
- package/dist/core/paths.js +101 -3
- package/dist/core/proposal-validators.js +61 -0
- package/dist/core/proposals.js +49 -38
- package/dist/core/state-db.js +775 -0
- package/dist/core/time.js +51 -0
- package/dist/core/warn.js +59 -1
- package/dist/indexer/db-search.js +52 -238
- package/dist/indexer/db.js +378 -1
- package/dist/indexer/ensure-index.js +61 -0
- package/dist/indexer/graph-boost.js +247 -94
- package/dist/indexer/graph-db.js +201 -0
- package/dist/indexer/graph-dedup.js +99 -0
- package/dist/indexer/graph-extraction.js +409 -76
- package/dist/indexer/index-context.js +10 -0
- package/dist/indexer/indexer.js +442 -290
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/match-contributors.js +141 -0
- package/dist/indexer/matchers.js +24 -190
- package/dist/indexer/memory-inference.js +63 -29
- package/dist/indexer/metadata-contributors.js +26 -0
- package/dist/indexer/metadata.js +194 -175
- package/dist/indexer/path-resolver.js +89 -0
- package/dist/indexer/ranking-contributors.js +204 -0
- package/dist/indexer/ranking.js +74 -0
- package/dist/indexer/search-hit-enrichers.js +22 -0
- package/dist/indexer/search-source.js +24 -9
- package/dist/indexer/semantic-status.js +2 -16
- package/dist/indexer/walker.js +25 -0
- package/dist/integrations/agent/config.js +175 -3
- package/dist/integrations/agent/index.js +3 -1
- package/dist/integrations/agent/pipeline.js +39 -0
- package/dist/integrations/agent/profiles.js +67 -5
- package/dist/integrations/agent/prompts.js +77 -72
- package/dist/integrations/agent/runners.js +31 -0
- package/dist/integrations/agent/sdk-runner.js +120 -0
- package/dist/integrations/agent/spawn.js +71 -16
- package/dist/integrations/lockfile.js +10 -18
- package/dist/integrations/session-logs/index.js +65 -0
- package/dist/integrations/session-logs/providers/claude-code.js +56 -0
- package/dist/integrations/session-logs/providers/opencode.js +52 -0
- package/dist/integrations/session-logs/types.js +1 -0
- package/dist/llm/call-ai.js +74 -0
- package/dist/llm/client.js +61 -122
- package/dist/llm/feature-gate.js +27 -16
- package/dist/llm/graph-extract.js +297 -62
- package/dist/llm/memory-infer.js +49 -71
- package/dist/llm/metadata-enhance.js +39 -22
- package/dist/llm/prompts/graph-extract-user-prompt.md +12 -0
- package/dist/output/cli-hints-full.md +277 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +2 -318
- package/dist/output/renderers.js +190 -123
- package/dist/output/shapes.js +33 -0
- package/dist/output/text.js +239 -2
- package/dist/registry/providers/skills-sh.js +61 -49
- package/dist/registry/providers/static-index.js +44 -48
- package/dist/setup/setup.js +510 -11
- package/dist/sources/provider-factory.js +2 -1
- package/dist/sources/providers/git.js +2 -2
- package/dist/sources/website-ingest.js +4 -0
- package/dist/tasks/backends/cron.js +200 -0
- package/dist/tasks/backends/exec-utils.js +25 -0
- package/dist/tasks/backends/index.js +32 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +184 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +212 -0
- package/dist/tasks/parser.js +198 -0
- package/dist/tasks/resolveAkmBin.js +84 -0
- package/dist/tasks/runner.js +432 -0
- package/dist/tasks/schedule.js +208 -0
- package/dist/tasks/schema.js +13 -0
- package/dist/tasks/validator.js +59 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +12 -0
- package/dist/wiki/wiki.js +10 -61
- package/dist/workflows/authoring.js +5 -25
- package/dist/workflows/renderer.js +8 -3
- package/dist/workflows/runs.js +59 -91
- package/dist/workflows/validator.js +1 -1
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +3 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.8.0.md +43 -0
- package/package.json +3 -2
- package/dist/templates/wiki-templates.js +0 -100
package/dist/core/events.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Append-only events stream —
|
|
2
|
+
* Append-only events stream — backed by state.db (#204, Phase 3).
|
|
3
3
|
*
|
|
4
4
|
* Every mutating CLI verb funnels through `appendEvent` so external
|
|
5
5
|
* observers (sync, replication, audit, dashboards) can react to stash
|
|
6
|
-
* changes
|
|
7
|
-
*
|
|
6
|
+
* changes. Events are stored in the `events` table in `state.db`
|
|
7
|
+
* (SQLite, WAL mode) instead of a flat `events.jsonl` file.
|
|
8
8
|
*
|
|
9
|
-
* The helper is the only thing in akm that writes to events.
|
|
10
|
-
* accepts injectable `
|
|
9
|
+
* The helper is the only thing in akm that writes to the events table. It
|
|
10
|
+
* accepts an injectable `dbPath` (via `EventsContext`) so tests can pin a
|
|
11
11
|
* tmpdir without any global mutation.
|
|
12
12
|
*
|
|
13
|
-
* Format (each
|
|
13
|
+
* Format (each EventEnvelope):
|
|
14
14
|
* { "schemaVersion": 1, "id": <number>, "ts": "<ISO>",
|
|
15
15
|
* "eventType": "<verb>", "ref"?: "<asset-ref>", ... }
|
|
16
16
|
*
|
|
17
|
-
* - `id` is a monotonic
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* - `id` is a monotonic SQLite AUTOINCREMENT rowid. Callers can persist it
|
|
18
|
+
* as a durable cursor for `--since` resumption (replaces the old byte-offset
|
|
19
|
+
* cursor). The public API still surfaces this as `nextOffset` (an opaque
|
|
20
|
+
* number) for backward compatibility with callers that stored byte-offset
|
|
21
|
+
* cursors.
|
|
21
22
|
* - `ts` is ISO-8601 (UTC, millisecond precision).
|
|
22
|
-
*
|
|
23
|
-
* The event `id` is derived at read time (line index) — the file itself
|
|
24
|
-
* is the source of truth, so the writer never has to coordinate with a
|
|
25
|
-
* counter. Tail consumers can persist a byte offset (durable cursor).
|
|
26
23
|
*/
|
|
27
|
-
import fs from "node:fs";
|
|
28
24
|
import path from "node:path";
|
|
29
|
-
import {
|
|
25
|
+
import { getDataDir } from "./paths";
|
|
26
|
+
import { insertEvent, openStateDatabase, readStateEvents } from "./state-db";
|
|
27
|
+
import { error } from "./warn";
|
|
30
28
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* each call. Two cooperating processes (e.g. one writing events, one tailing)
|
|
35
|
-
* MUST inherit the same `XDG_CACHE_HOME` or they will read/write different
|
|
36
|
-
* `events.jsonl` files. This is the same env-isolation behaviour as the rest
|
|
37
|
-
* of akm — config, indexes, and caches all key off XDG paths — so set
|
|
38
|
-
* `XDG_CACHE_HOME` consistently across processes that share the events bus.
|
|
29
|
+
* Legacy events.jsonl path — used only by the migration script
|
|
30
|
+
* (`scripts/migrate-storage.ts`) to import existing event history into
|
|
31
|
+
* state.db. No events are written here by akm v0.9+.
|
|
39
32
|
*/
|
|
40
33
|
export function getEventsPath() {
|
|
41
|
-
return path.join(
|
|
34
|
+
return path.join(getDataDir(), "events.jsonl");
|
|
42
35
|
}
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the state.db path from context:
|
|
38
|
+
* 1. `ctx.dbPath` — explicit override (test seam)
|
|
39
|
+
* 2. default — `<dataDir>/state.db`
|
|
40
|
+
*/
|
|
41
|
+
function resolveDbPath(ctx) {
|
|
42
|
+
if (ctx?.dbPath)
|
|
43
|
+
return ctx.dbPath;
|
|
44
|
+
return path.join(getDataDir(), "state.db");
|
|
45
45
|
}
|
|
46
46
|
function resolveNow(ctx) {
|
|
47
47
|
return ctx?.now ?? Date.now;
|
|
@@ -50,129 +50,76 @@ function resolveNow(ctx) {
|
|
|
50
50
|
* Append a single event. Best-effort: a write failure is logged once to
|
|
51
51
|
* stderr but never propagates — observability must not break mutation.
|
|
52
52
|
*
|
|
53
|
-
*
|
|
54
|
-
* id; the reader assigns it). Keeping it off the wire avoids a coordination
|
|
55
|
-
* step between concurrent appenders.
|
|
53
|
+
* Events are written exclusively to the `events` table in `state.db`.
|
|
56
54
|
*/
|
|
57
55
|
export function appendEvent(input, ctx) {
|
|
58
|
-
const
|
|
56
|
+
const dbPath = resolveDbPath(ctx);
|
|
59
57
|
const now = resolveNow(ctx);
|
|
60
58
|
const ts = new Date(now()).toISOString();
|
|
61
|
-
const envelope = {
|
|
62
|
-
schemaVersion: 1,
|
|
63
|
-
ts,
|
|
64
|
-
eventType: input.eventType,
|
|
65
|
-
...(input.ref !== undefined ? { ref: input.ref } : {}),
|
|
66
|
-
...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
|
|
67
|
-
};
|
|
68
|
-
const line = `${JSON.stringify(envelope)}\n`;
|
|
69
59
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
60
|
+
const db = openStateDatabase(dbPath);
|
|
61
|
+
try {
|
|
62
|
+
insertEvent(db, {
|
|
63
|
+
eventType: input.eventType,
|
|
64
|
+
ts,
|
|
65
|
+
ref: input.ref,
|
|
66
|
+
metadata: input.metadata,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
db.close();
|
|
71
|
+
}
|
|
76
72
|
}
|
|
77
73
|
catch (err) {
|
|
78
74
|
// Best-effort: events stream failures must not break the mutating verb.
|
|
79
75
|
// Surface once to stderr so operators can diagnose.
|
|
80
|
-
|
|
81
|
-
process.stderr.write(`akm: events.jsonl append failed (${message})\n`);
|
|
76
|
+
error(`akm: appendEvent failed: ${String(err)}`);
|
|
82
77
|
}
|
|
83
78
|
}
|
|
84
79
|
/**
|
|
85
80
|
* Read all events matching the filter. Returns a `nextOffset` that callers
|
|
86
|
-
* can persist between processes for monotonic resumption
|
|
87
|
-
* is the durable cursor referenced in the acceptance criteria.
|
|
81
|
+
* can persist between processes for monotonic resumption.
|
|
88
82
|
*/
|
|
89
83
|
export function readEvents(options = {}, ctx) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
const dbPath = resolveDbPath(ctx);
|
|
85
|
+
let db;
|
|
86
|
+
try {
|
|
87
|
+
db = openStateDatabase(dbPath);
|
|
93
88
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return { events: [], nextOffset: stat.size };
|
|
89
|
+
catch {
|
|
90
|
+
// DB does not exist yet or cannot be opened — return empty result.
|
|
91
|
+
return { events: [], nextOffset: 0 };
|
|
98
92
|
}
|
|
99
|
-
const fd = fs.openSync(filePath, "r");
|
|
100
93
|
try {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
const { events: rawEvents, nextId } = readStateEvents(db, {
|
|
95
|
+
sinceId: options.sinceOffset,
|
|
96
|
+
since: options.since,
|
|
97
|
+
type: options.type,
|
|
98
|
+
ref: options.ref,
|
|
99
|
+
});
|
|
100
|
+
// Apply tag filters in application code (same as the old JSONL implementation).
|
|
101
|
+
const events = rawEvents.filter((envelope) => {
|
|
102
|
+
const tags = envelope.metadata?.tags ?? [];
|
|
103
|
+
if (options.excludeTags?.some((t) => tags.includes(t)))
|
|
104
|
+
return false;
|
|
105
|
+
if (options.includeTags && !options.includeTags.every((t) => tags.includes(t)))
|
|
106
|
+
return false;
|
|
107
|
+
return true;
|
|
108
|
+
});
|
|
109
|
+
return { events, nextOffset: nextId };
|
|
107
110
|
}
|
|
108
111
|
finally {
|
|
109
|
-
|
|
112
|
+
db.close();
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
|
-
function parseEventLines(text, options, startOffset) {
|
|
113
|
-
// Each line that ends with \n is a complete event. A trailing partial
|
|
114
|
-
// line (no terminating \n) is ignored — the next read will pick it up
|
|
115
|
-
// once it is fully written.
|
|
116
|
-
const out = [];
|
|
117
|
-
let lineStart = 0;
|
|
118
|
-
// The envelope id is the 1-based line index across the whole file. We
|
|
119
|
-
// approximate that here as the line index from the start of the read
|
|
120
|
-
// window plus a synthetic offset — for callers using `--since`, the
|
|
121
|
-
// absolute id is less useful than the byte cursor anyway. To keep ids
|
|
122
|
-
// monotonic across reads we use absolute byte position as a stable
|
|
123
|
-
// surrogate identifier.
|
|
124
|
-
for (let i = 0; i < text.length; i += 1) {
|
|
125
|
-
if (text.charCodeAt(i) !== 10 /* \n */)
|
|
126
|
-
continue;
|
|
127
|
-
const line = text.slice(lineStart, i);
|
|
128
|
-
const absStart = startOffset + lineStart;
|
|
129
|
-
lineStart = i + 1;
|
|
130
|
-
if (!line.trim())
|
|
131
|
-
continue;
|
|
132
|
-
let parsed;
|
|
133
|
-
try {
|
|
134
|
-
parsed = JSON.parse(line);
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
// Skip malformed lines — better than crashing the read pipeline.
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
const envelope = {
|
|
141
|
-
schemaVersion: 1,
|
|
142
|
-
id: absStart,
|
|
143
|
-
ts: typeof parsed.ts === "string" ? parsed.ts : "",
|
|
144
|
-
eventType: typeof parsed.eventType === "string" ? parsed.eventType : "unknown",
|
|
145
|
-
...(typeof parsed.ref === "string" ? { ref: parsed.ref } : {}),
|
|
146
|
-
...(parsed.metadata !== undefined ? { metadata: parsed.metadata } : {}),
|
|
147
|
-
};
|
|
148
|
-
if (!matchesFilter(envelope, options))
|
|
149
|
-
continue;
|
|
150
|
-
out.push(envelope);
|
|
151
|
-
}
|
|
152
|
-
return out;
|
|
153
|
-
}
|
|
154
|
-
function matchesFilter(envelope, options) {
|
|
155
|
-
if (options.type && envelope.eventType !== options.type)
|
|
156
|
-
return false;
|
|
157
|
-
if (options.ref && envelope.ref !== options.ref)
|
|
158
|
-
return false;
|
|
159
|
-
if (options.since && envelope.ts && envelope.ts < options.since)
|
|
160
|
-
return false;
|
|
161
|
-
const tags = envelope.metadata?.tags ?? [];
|
|
162
|
-
if (options.excludeTags?.some((t) => tags.includes(t)))
|
|
163
|
-
return false;
|
|
164
|
-
if (options.includeTags && !options.includeTags.every((t) => tags.includes(t)))
|
|
165
|
-
return false;
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
115
|
/**
|
|
169
|
-
* Follow events.
|
|
170
|
-
* every new event to `onEvent`. Resolves when `signal` aborts, when
|
|
116
|
+
* Follow the events table in state.db. Polls at `intervalMs` (default 75ms)
|
|
117
|
+
* and emits every new event to `onEvent`. Resolves when `signal` aborts, when
|
|
171
118
|
* `maxEvents` events have been observed, or when `maxDurationMs` elapses.
|
|
172
119
|
*
|
|
173
|
-
* The polling cursor is
|
|
174
|
-
* cause skips: between two reads we always pick up everything
|
|
175
|
-
*
|
|
120
|
+
* The polling cursor is a monotonic SQLite rowid so concurrent writers cannot
|
|
121
|
+
* cause skips: between two reads we always pick up everything inserted since
|
|
122
|
+
* the last `nextOffset`.
|
|
176
123
|
*/
|
|
177
124
|
export async function tailEvents(options = {}, ctx) {
|
|
178
125
|
const intervalMs = options.intervalMs ?? 75;
|
|
@@ -223,7 +170,7 @@ export async function tailEvents(options = {}, ctx) {
|
|
|
223
170
|
cursor = result.nextOffset;
|
|
224
171
|
for (const event of result.events) {
|
|
225
172
|
// Apply --since filter inside the polling loop too — the cursor is
|
|
226
|
-
//
|
|
173
|
+
// rowid-based so it can hand us events the user filtered out.
|
|
227
174
|
if (options.since && event.ts && event.ts < options.since)
|
|
228
175
|
continue;
|
|
229
176
|
collected.push(event);
|
package/dist/core/frontmatter.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Provides a single, canonical YAML-subset frontmatter parser used by both
|
|
5
5
|
* the stash open logic and the metadata generator.
|
|
6
6
|
*/
|
|
7
|
+
import { asNonEmptyString } from "./common";
|
|
7
8
|
/**
|
|
8
9
|
* Parse YAML-subset frontmatter from a Markdown (or similar) string.
|
|
9
10
|
*
|
|
@@ -152,7 +153,8 @@ export function parseYamlScalar(value) {
|
|
|
152
153
|
}
|
|
153
154
|
/**
|
|
154
155
|
* Coerce an unknown value to a trimmed string, or return undefined if empty/non-string.
|
|
156
|
+
* @deprecated Use `asNonEmptyString` from `core/common` directly.
|
|
155
157
|
*/
|
|
156
158
|
export function toStringOrUndefined(value) {
|
|
157
|
-
return
|
|
159
|
+
return asNonEmptyString(value);
|
|
158
160
|
}
|
package/dist/core/markdown.js
CHANGED
|
@@ -75,3 +75,20 @@ export function formatToc(toc) {
|
|
|
75
75
|
parts.push(`\n${toc.totalLines} lines total`);
|
|
76
76
|
return parts.join("\n");
|
|
77
77
|
}
|
|
78
|
+
// ── Fence stripping ──────────────────────────────────────────────────────────
|
|
79
|
+
/**
|
|
80
|
+
* Best-effort fence stripping. Strips `<think>` reasoning blocks emitted by
|
|
81
|
+
* local LLMs (e.g. Qwen3) before the content, which otherwise breaks YAML
|
|
82
|
+
* frontmatter detection. Only strips outer triple-fence pairs — leaves inner
|
|
83
|
+
* code blocks intact.
|
|
84
|
+
*/
|
|
85
|
+
export function stripMarkdownFences(raw) {
|
|
86
|
+
const stripped = raw
|
|
87
|
+
.trim()
|
|
88
|
+
.replace(/<think>[\s\S]*?<\/think>/gi, "")
|
|
89
|
+
.trim();
|
|
90
|
+
const fence = stripped.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/i);
|
|
91
|
+
if (fence)
|
|
92
|
+
return fence[1].trim();
|
|
93
|
+
return stripped;
|
|
94
|
+
}
|