akm-cli 0.7.4 → 0.8.0-rc.3
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 → .github/CHANGELOG.md} +34 -1
- package/.github/LICENSE +374 -0
- package/dist/cli/parse-args.js +86 -0
- package/dist/cli.js +1223 -650
- package/dist/commands/agent-dispatch.js +107 -0
- package/dist/commands/agent-support.js +62 -0
- package/dist/commands/config-cli.js +68 -84
- package/dist/commands/consolidate.js +812 -0
- package/dist/commands/curate.js +1 -0
- package/dist/commands/distill-promotion-policy.js +658 -0
- package/dist/commands/distill.js +224 -39
- package/dist/commands/eval-cases.js +40 -0
- package/dist/commands/events.js +12 -24
- 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 +1161 -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 +291 -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 +145 -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/vault-key-rules.js +67 -0
- package/dist/commands/lint/workflow-linter.js +53 -0
- package/dist/commands/lint.js +1 -0
- package/dist/commands/migration-help.js +2 -2
- package/dist/commands/proposal.js +8 -7
- package/dist/commands/propose.js +106 -43
- package/dist/commands/reflect.js +167 -41
- package/dist/commands/registry-search.js +2 -2
- package/dist/commands/remember.js +55 -1
- package/dist/commands/schema-repair.js +130 -0
- package/dist/commands/search.js +21 -5
- package/dist/commands/show.js +135 -55
- 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 +173 -87
- package/dist/core/action-contributors.js +25 -0
- package/dist/core/asset-ref.js +4 -0
- package/dist/core/asset-registry.js +5 -17
- package/dist/core/asset-spec.js +11 -1
- package/dist/core/common.js +100 -0
- package/dist/core/concurrent.js +22 -0
- package/dist/core/config.js +240 -127
- package/dist/core/events.js +87 -123
- package/dist/core/frontmatter.js +0 -6
- 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 +731 -0
- package/dist/core/time.js +51 -0
- package/dist/core/warn.js +59 -1
- package/dist/indexer/db-search.js +86 -472
- package/dist/indexer/db.js +418 -59
- package/dist/indexer/ensure-index.js +133 -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 +417 -74
- package/dist/indexer/index-context.js +10 -0
- package/dist/indexer/indexer.js +480 -298
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/matchers.js +124 -160
- package/dist/indexer/memory-inference.js +63 -29
- package/dist/indexer/metadata-contributors.js +26 -0
- package/dist/indexer/metadata.js +196 -197
- 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/builders.js +109 -0
- package/dist/integrations/agent/config.js +203 -3
- package/dist/integrations/agent/index.js +5 -2
- package/dist/integrations/agent/model-aliases.js +63 -0
- package/dist/integrations/agent/profiles.js +67 -5
- package/dist/integrations/agent/prompts.js +114 -29
- package/dist/integrations/agent/sdk-runner.js +120 -0
- package/dist/integrations/agent/spawn.js +158 -34
- 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 +63 -86
- package/dist/llm/feature-gate.js +27 -16
- package/dist/llm/graph-extract.js +297 -64
- package/dist/llm/memory-infer.js +52 -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 -309
- package/dist/output/renderers.js +226 -257
- package/dist/output/shapes.js +109 -96
- package/dist/output/text.js +274 -36
- package/dist/registry/providers/skills-sh.js +61 -49
- package/dist/registry/providers/static-index.js +44 -48
- package/dist/registry/resolve.js +8 -16
- package/dist/setup/setup.js +510 -11
- package/dist/sources/provider-factory.js +2 -1
- package/dist/sources/providers/filesystem.js +16 -23
- package/dist/sources/providers/git.js +45 -4
- package/dist/sources/providers/website.js +15 -22
- 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/db.js +9 -0
- package/dist/workflows/renderer.js +8 -3
- package/dist/workflows/runs.js +73 -88
- package/dist/workflows/scope-key.js +76 -0
- package/dist/workflows/validator.js +1 -1
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +5 -2
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +20 -0
- package/docs/migration/release-notes/0.8.0.md +43 -0
- package/package.json +4 -3
- package/dist/templates/wiki-templates.js +0 -100
package/dist/workflows/runs.js
CHANGED
|
@@ -12,55 +12,64 @@ import { resolveAssetPath } from "../sources/resolve";
|
|
|
12
12
|
import { formatWorkflowErrors } from "./authoring";
|
|
13
13
|
import { closeWorkflowDatabase, openWorkflowDatabase } from "./db";
|
|
14
14
|
import { parseWorkflow } from "./parser";
|
|
15
|
+
import { getCurrentWorkflowScopeKey } from "./scope-key";
|
|
16
|
+
async function withWorkflowDb(fn) {
|
|
17
|
+
const db = openWorkflowDatabase();
|
|
18
|
+
try {
|
|
19
|
+
return await Promise.resolve(fn(db));
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
closeWorkflowDatabase(db);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
15
25
|
export async function startWorkflowRun(ref, params = {}) {
|
|
16
26
|
const asset = await loadWorkflowAsset(ref);
|
|
17
|
-
|
|
18
|
-
try {
|
|
27
|
+
return withWorkflowDb(async (db) => {
|
|
19
28
|
const now = new Date().toISOString();
|
|
20
29
|
const runId = randomUUID();
|
|
30
|
+
const scopeKey = getCurrentWorkflowScopeKey();
|
|
21
31
|
const currentStepId = asset.steps[0]?.id ?? null;
|
|
22
32
|
const workflowEntryId = resolveWorkflowEntryId(asset.sourcePath, asset.ref);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.run(runId, asset.ref, workflowEntryId, asset.title, JSON.stringify(params), currentStepId, now, now);
|
|
29
|
-
const insertStep = workflowDb.prepare(`INSERT INTO workflow_run_steps (
|
|
33
|
+
db.transaction(() => {
|
|
34
|
+
db.prepare(`INSERT INTO workflow_runs (
|
|
35
|
+
id, workflow_ref, scope_key, workflow_entry_id, workflow_title, status, params_json, current_step_id, created_at, updated_at
|
|
36
|
+
) VALUES (?, ?, ?, ?, ?, 'active', ?, ?, ?, ?)`).run(runId, asset.ref, scopeKey, workflowEntryId, asset.title, JSON.stringify(params), currentStepId, now, now);
|
|
37
|
+
const insertStep = db.prepare(`INSERT INTO workflow_run_steps (
|
|
30
38
|
run_id, step_id, step_title, instructions, completion_json, sequence_index, status
|
|
31
39
|
) VALUES (?, ?, ?, ?, ?, ?, 'pending')`);
|
|
32
40
|
for (const step of asset.steps) {
|
|
33
41
|
insertStep.run(runId, step.id, step.title, step.instructions, step.completionCriteria ? JSON.stringify(step.completionCriteria) : null, step.sequenceIndex ?? 0);
|
|
34
42
|
}
|
|
35
43
|
})();
|
|
36
|
-
const result = getWorkflowStatus(runId);
|
|
44
|
+
const result = await getWorkflowStatus(runId);
|
|
37
45
|
appendEvent({
|
|
38
46
|
eventType: "workflow_started",
|
|
39
47
|
ref: ref,
|
|
40
48
|
metadata: { runId: result.run.id, title: result.run.workflowTitle },
|
|
41
49
|
});
|
|
42
50
|
return result;
|
|
43
|
-
}
|
|
44
|
-
finally {
|
|
45
|
-
closeWorkflowDatabase(workflowDb);
|
|
46
|
-
}
|
|
51
|
+
});
|
|
47
52
|
}
|
|
48
|
-
export function getWorkflowStatus(runId) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const steps = readWorkflowRunSteps(workflowDb, run.id);
|
|
53
|
+
export async function getWorkflowStatus(runId) {
|
|
54
|
+
return withWorkflowDb((db) => {
|
|
55
|
+
const run = readWorkflowRun(db, runId);
|
|
56
|
+
const steps = readWorkflowRunSteps(db, run.id);
|
|
53
57
|
return buildWorkflowRunDetail(run, steps);
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
closeWorkflowDatabase(workflowDb);
|
|
57
|
-
}
|
|
58
|
+
});
|
|
58
59
|
}
|
|
59
|
-
export function
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
export async function hasWorkflowRun(runId) {
|
|
61
|
+
return withWorkflowDb((db) => {
|
|
62
|
+
const row = db.prepare("SELECT 1 FROM workflow_runs WHERE id = ? LIMIT 1").get(runId);
|
|
63
|
+
return !!row;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export async function listWorkflowRuns(input) {
|
|
67
|
+
return withWorkflowDb((db) => {
|
|
62
68
|
const filters = [];
|
|
63
69
|
const params = [];
|
|
70
|
+
const scopeKey = getCurrentWorkflowScopeKey();
|
|
71
|
+
filters.push("scope_key = ?");
|
|
72
|
+
params.push(scopeKey);
|
|
64
73
|
if (input?.workflowRef) {
|
|
65
74
|
const parsed = parseAssetRef(input.workflowRef);
|
|
66
75
|
if (parsed.type !== "workflow") {
|
|
@@ -73,20 +82,16 @@ export function listWorkflowRuns(input) {
|
|
|
73
82
|
filters.push("status IN ('active', 'blocked')");
|
|
74
83
|
}
|
|
75
84
|
const where = filters.length > 0 ? `WHERE ${filters.join(" AND ")}` : "";
|
|
76
|
-
const rows =
|
|
85
|
+
const rows = db
|
|
77
86
|
.prepare(`SELECT * FROM workflow_runs ${where} ORDER BY updated_at DESC, created_at DESC`)
|
|
78
87
|
.all(...params);
|
|
79
88
|
return { runs: rows.map(toWorkflowRunSummary) };
|
|
80
|
-
}
|
|
81
|
-
finally {
|
|
82
|
-
closeWorkflowDatabase(workflowDb);
|
|
83
|
-
}
|
|
89
|
+
});
|
|
84
90
|
}
|
|
85
91
|
export async function getNextWorkflowStep(specifier, params) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const steps = readWorkflowRunSteps(workflowDb, run.id);
|
|
92
|
+
return withWorkflowDb(async (db) => {
|
|
93
|
+
const { run, autoStarted } = await resolveRunSpecifier(db, specifier, params);
|
|
94
|
+
const steps = readWorkflowRunSteps(db, run.id);
|
|
90
95
|
const currentStep = resolveCurrentStep(run, steps);
|
|
91
96
|
const done = run.status === "completed" ? true : undefined;
|
|
92
97
|
return {
|
|
@@ -100,54 +105,44 @@ export async function getNextWorkflowStep(specifier, params) {
|
|
|
100
105
|
...(done ? { done } : {}),
|
|
101
106
|
...(autoStarted ? { autoStarted } : {}),
|
|
102
107
|
};
|
|
103
|
-
}
|
|
104
|
-
finally {
|
|
105
|
-
closeWorkflowDatabase(workflowDb);
|
|
106
|
-
}
|
|
108
|
+
});
|
|
107
109
|
}
|
|
108
|
-
export function resumeWorkflowRun(runId) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const run = readWorkflowRun(workflowDb, runId);
|
|
110
|
+
export async function resumeWorkflowRun(runId) {
|
|
111
|
+
return withWorkflowDb((db) => {
|
|
112
|
+
const run = readWorkflowRun(db, runId);
|
|
112
113
|
if (run.status === "completed") {
|
|
113
114
|
throw new UsageError(`Workflow run ${run.id} is already completed and cannot be resumed.`);
|
|
114
115
|
}
|
|
115
116
|
if (run.status === "active") {
|
|
116
|
-
const steps = readWorkflowRunSteps(
|
|
117
|
+
const steps = readWorkflowRunSteps(db, run.id);
|
|
117
118
|
return buildWorkflowRunDetail(run, steps);
|
|
118
119
|
}
|
|
119
120
|
// blocked or failed → flip back to active and re-open the current step so
|
|
120
121
|
// it can be reclassified (completed, failed, skipped) after resuming.
|
|
121
122
|
const now = new Date().toISOString();
|
|
122
|
-
|
|
123
|
+
db.transaction(() => {
|
|
123
124
|
if (run.current_step_id) {
|
|
124
|
-
|
|
125
|
-
.prepare(`UPDATE workflow_run_steps
|
|
125
|
+
db.prepare(`UPDATE workflow_run_steps
|
|
126
126
|
SET status = 'pending', notes = NULL, evidence_json = NULL, completed_at = NULL
|
|
127
|
-
WHERE run_id = ? AND step_id = ? AND status IN ('blocked', 'failed')`)
|
|
128
|
-
.run(run.id, run.current_step_id);
|
|
127
|
+
WHERE run_id = ? AND step_id = ? AND status IN ('blocked', 'failed')`).run(run.id, run.current_step_id);
|
|
129
128
|
}
|
|
130
|
-
|
|
129
|
+
db.prepare("UPDATE workflow_runs SET status = 'active', updated_at = ? WHERE id = ?").run(now, run.id);
|
|
131
130
|
})();
|
|
132
131
|
const updated = { ...run, status: "active", updated_at: now };
|
|
133
|
-
const steps = readWorkflowRunSteps(
|
|
132
|
+
const steps = readWorkflowRunSteps(db, run.id);
|
|
134
133
|
return buildWorkflowRunDetail(updated, steps);
|
|
135
|
-
}
|
|
136
|
-
finally {
|
|
137
|
-
closeWorkflowDatabase(workflowDb);
|
|
138
|
-
}
|
|
134
|
+
});
|
|
139
135
|
}
|
|
140
|
-
export function completeWorkflowStep(input) {
|
|
141
|
-
|
|
142
|
-
try {
|
|
136
|
+
export async function completeWorkflowStep(input) {
|
|
137
|
+
return withWorkflowDb((db) => {
|
|
143
138
|
let updatedRun;
|
|
144
139
|
let refreshedSteps = [];
|
|
145
|
-
|
|
146
|
-
const run = readWorkflowRun(
|
|
140
|
+
db.transaction(() => {
|
|
141
|
+
const run = readWorkflowRun(db, input.runId);
|
|
147
142
|
if (run.status !== "active") {
|
|
148
143
|
throw new UsageError(`Workflow run ${run.id} is ${run.status} and cannot be updated.`);
|
|
149
144
|
}
|
|
150
|
-
const existing =
|
|
145
|
+
const existing = db
|
|
151
146
|
.prepare("SELECT * FROM workflow_run_steps WHERE run_id = ? AND step_id = ?")
|
|
152
147
|
.get(run.id, input.stepId);
|
|
153
148
|
if (!existing) {
|
|
@@ -160,18 +155,14 @@ export function completeWorkflowStep(input) {
|
|
|
160
155
|
throw new UsageError(`Step "${input.stepId}" is not the current step for workflow run ${run.id}. Complete "${run.current_step_id}" first.`);
|
|
161
156
|
}
|
|
162
157
|
const completedAt = new Date().toISOString();
|
|
163
|
-
|
|
164
|
-
.prepare(`UPDATE workflow_run_steps
|
|
158
|
+
db.prepare(`UPDATE workflow_run_steps
|
|
165
159
|
SET status = ?, notes = ?, evidence_json = ?, completed_at = ?
|
|
166
|
-
WHERE run_id = ? AND step_id = ?`)
|
|
167
|
-
|
|
168
|
-
refreshedSteps = readWorkflowRunSteps(workflowDb, run.id);
|
|
160
|
+
WHERE run_id = ? AND step_id = ?`).run(input.status, input.notes?.trim() || null, input.evidence ? JSON.stringify(input.evidence) : null, completedAt, run.id, input.stepId);
|
|
161
|
+
refreshedSteps = readWorkflowRunSteps(db, run.id);
|
|
169
162
|
const state = deriveRunState(refreshedSteps);
|
|
170
|
-
|
|
171
|
-
.prepare(`UPDATE workflow_runs
|
|
163
|
+
db.prepare(`UPDATE workflow_runs
|
|
172
164
|
SET status = ?, current_step_id = ?, updated_at = ?, completed_at = ?
|
|
173
|
-
WHERE id = ?`)
|
|
174
|
-
.run(state.status, state.currentStepId, completedAt, state.completedAt, run.id);
|
|
165
|
+
WHERE id = ?`).run(state.status, state.currentStepId, completedAt, state.completedAt, run.id);
|
|
175
166
|
updatedRun = {
|
|
176
167
|
...run,
|
|
177
168
|
status: state.status,
|
|
@@ -190,10 +181,7 @@ export function completeWorkflowStep(input) {
|
|
|
190
181
|
appendEvent({ eventType: "workflow_finished", ref: detail.run.workflowRef, metadata: { runId: input.runId } });
|
|
191
182
|
}
|
|
192
183
|
return detail;
|
|
193
|
-
}
|
|
194
|
-
finally {
|
|
195
|
-
closeWorkflowDatabase(workflowDb);
|
|
196
|
-
}
|
|
184
|
+
});
|
|
197
185
|
}
|
|
198
186
|
async function resolveRunSpecifier(db, specifier, params) {
|
|
199
187
|
const explicitRun = db.prepare("SELECT * FROM workflow_runs WHERE id = ?").get(specifier);
|
|
@@ -211,9 +199,10 @@ async function resolveRunSpecifier(db, specifier, params) {
|
|
|
211
199
|
throw new UsageError(`Expected a workflow ref or workflow run id, got "${specifier}".`);
|
|
212
200
|
}
|
|
213
201
|
const ref = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
|
|
202
|
+
const scopeKey = getCurrentWorkflowScopeKey();
|
|
214
203
|
const active = db
|
|
215
|
-
.prepare("SELECT * FROM workflow_runs WHERE workflow_ref = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1")
|
|
216
|
-
.get(ref);
|
|
204
|
+
.prepare("SELECT * FROM workflow_runs WHERE workflow_ref = ? AND scope_key = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1")
|
|
205
|
+
.get(ref, scopeKey);
|
|
217
206
|
if (active) {
|
|
218
207
|
if (params && Object.keys(params).length > 0) {
|
|
219
208
|
throw new UsageError(`--params can only be set on a new run; ${ref} already has an active run`);
|
|
@@ -246,7 +235,7 @@ async function loadWorkflowAsset(ref) {
|
|
|
246
235
|
if (!assetPath) {
|
|
247
236
|
throw new NotFoundError(`Workflow not found for ref: workflow:${parsed.name}`);
|
|
248
237
|
}
|
|
249
|
-
const resolvedSourcePath = sourcePath ??
|
|
238
|
+
const resolvedSourcePath = sourcePath ?? config.stashDir ?? assetPath;
|
|
250
239
|
const fullRef = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
|
|
251
240
|
const cached = readWorkflowDocumentFromIndex(resolvedSourcePath, fullRef);
|
|
252
241
|
const document = cached ?? loadWorkflowDocumentFromDisk(assetPath);
|
|
@@ -358,6 +347,7 @@ function toWorkflowRunSummary(run) {
|
|
|
358
347
|
return {
|
|
359
348
|
id: run.id,
|
|
360
349
|
workflowRef: run.workflow_ref,
|
|
350
|
+
scopeKey: run.scope_key,
|
|
361
351
|
workflowEntryId: run.workflow_entry_id,
|
|
362
352
|
workflowTitle: run.workflow_title,
|
|
363
353
|
status: run.status,
|
|
@@ -435,18 +425,13 @@ function parseJsonArray(value) {
|
|
|
435
425
|
}
|
|
436
426
|
return undefined;
|
|
437
427
|
}
|
|
438
|
-
export function getActiveWorkflowRun() {
|
|
439
|
-
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
.
|
|
443
|
-
.get();
|
|
444
|
-
closeWorkflowDatabase(workflowDb);
|
|
428
|
+
export async function getActiveWorkflowRun(scopeKey = getCurrentWorkflowScopeKey()) {
|
|
429
|
+
return withWorkflowDb((db) => {
|
|
430
|
+
const row = db
|
|
431
|
+
.query("SELECT id, current_step_id, workflow_ref FROM workflow_runs WHERE scope_key = ? AND status IN ('active', 'blocked') ORDER BY updated_at DESC LIMIT 1")
|
|
432
|
+
.get(scopeKey);
|
|
445
433
|
if (!row)
|
|
446
434
|
return null;
|
|
447
435
|
return { runId: row.id, stepId: row.current_step_id, workflowRef: row.workflow_ref };
|
|
448
|
-
}
|
|
449
|
-
catch {
|
|
450
|
-
return null; // fail-open: never crash show output due to DB error
|
|
451
|
-
}
|
|
436
|
+
}).catch(() => null); // fail-open: never crash show output due to DB error
|
|
452
437
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { isWithin, resolveStashDir, safeRealpath, toPosix } from "../core/common";
|
|
5
|
+
const PROJECT_CONFIG_RELATIVE_PATH = path.join(".akm", "config.json");
|
|
6
|
+
export function getCurrentWorkflowScopeKey() {
|
|
7
|
+
const anchor = resolveWorkflowScopeAnchor(process.cwd());
|
|
8
|
+
const normalized = normalizeScopePath(anchor);
|
|
9
|
+
const digest = createHash("sha256").update(normalized).digest("hex");
|
|
10
|
+
return `dir:v1:${digest}`;
|
|
11
|
+
}
|
|
12
|
+
export function resolveWorkflowScopeAnchor(startDir) {
|
|
13
|
+
const cwd = safeRealpath(startDir);
|
|
14
|
+
const projectRoot = findNearestProjectConfigRoot(cwd);
|
|
15
|
+
if (projectRoot)
|
|
16
|
+
return projectRoot;
|
|
17
|
+
const gitRoot = findNearestGitRoot(cwd);
|
|
18
|
+
if (gitRoot)
|
|
19
|
+
return gitRoot;
|
|
20
|
+
try {
|
|
21
|
+
const stashDir = safeRealpath(resolveStashDir({ readOnly: true }));
|
|
22
|
+
if (isWithin(cwd, stashDir))
|
|
23
|
+
return stashDir;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Ignore stash resolution failures and fall back to cwd.
|
|
27
|
+
}
|
|
28
|
+
return cwd;
|
|
29
|
+
}
|
|
30
|
+
function findNearestProjectConfigRoot(startDir) {
|
|
31
|
+
let currentDir = startDir;
|
|
32
|
+
while (true) {
|
|
33
|
+
const configPath = path.join(currentDir, PROJECT_CONFIG_RELATIVE_PATH);
|
|
34
|
+
if (isFile(configPath)) {
|
|
35
|
+
return safeRealpath(currentDir);
|
|
36
|
+
}
|
|
37
|
+
const parentDir = path.dirname(currentDir);
|
|
38
|
+
if (parentDir === currentDir)
|
|
39
|
+
return null;
|
|
40
|
+
currentDir = parentDir;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function findNearestGitRoot(startDir) {
|
|
44
|
+
let currentDir = startDir;
|
|
45
|
+
while (true) {
|
|
46
|
+
const gitPath = path.join(currentDir, ".git");
|
|
47
|
+
if (exists(gitPath)) {
|
|
48
|
+
return safeRealpath(currentDir);
|
|
49
|
+
}
|
|
50
|
+
const parentDir = path.dirname(currentDir);
|
|
51
|
+
if (parentDir === currentDir)
|
|
52
|
+
return null;
|
|
53
|
+
currentDir = parentDir;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function exists(candidate) {
|
|
57
|
+
try {
|
|
58
|
+
fs.accessSync(candidate, fs.constants.F_OK);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function isFile(candidate) {
|
|
66
|
+
try {
|
|
67
|
+
return fs.statSync(candidate).isFile();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function normalizeScopePath(value) {
|
|
74
|
+
const posix = toPosix(value);
|
|
75
|
+
return process.platform === "win32" ? posix.toLowerCase() : posix;
|
|
76
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* step-id format, and the frontmatter key whitelist.
|
|
7
7
|
*/
|
|
8
8
|
const STEP_ID_REGEX = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
9
|
-
const ALLOWED_FRONTMATTER_KEYS = new Set(["description", "tags", "params"]);
|
|
9
|
+
const ALLOWED_FRONTMATTER_KEYS = new Set(["description", "tags", "params", "name", "updated"]);
|
|
10
10
|
export function runSemanticChecks(draft, frontmatterData, frontmatterEndLine, errors) {
|
|
11
11
|
checkFrontmatterKeys(frontmatterData, frontmatterEndLine, errors);
|
|
12
12
|
checkStepIdFormat(draft, errors);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Describe what this workflow accomplishes
|
|
3
|
+
tags:
|
|
4
|
+
- example
|
|
5
|
+
params:
|
|
6
|
+
example_param: Explain this parameter
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Workflow: {{TITLE}}
|
|
10
|
+
|
|
11
|
+
## Step: {{FIRST_STEP_TITLE}}
|
|
12
|
+
Step ID: {{FIRST_STEP_ID}}
|
|
13
|
+
|
|
14
|
+
### Instructions
|
|
15
|
+
Describe what to do in this step.
|
|
16
|
+
|
|
17
|
+
### Completion Criteria
|
|
18
|
+
- Confirm the first step is complete
|
|
19
|
+
|
|
20
|
+
## Step: Second Step
|
|
21
|
+
Step ID: second-step
|
|
22
|
+
|
|
23
|
+
### Instructions
|
|
24
|
+
Describe what happens next.
|
package/docs/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
## Upgrading
|
|
12
12
|
|
|
13
13
|
- [v1 migration guide](migration/v1.md) -- The path from 0.x to v1.0, including the `.stash.json` removal scheduled for v0.8.0
|
|
14
|
-
- [Release notes (latest: 0.
|
|
14
|
+
- [Release notes (latest: 0.8.0)](migration/release-notes/0.8.0.md) -- Per-release notes drop into `migration/release-notes/`, including current pre-release removals
|
|
15
15
|
- [v0.5 → v0.6 migration guide](migration/v0.5-to-v0.6.md) -- Every breaking change with before/after code, publisher checklist, and troubleshooting
|
|
16
16
|
|
|
17
17
|
## Reference
|
|
@@ -33,12 +33,15 @@
|
|
|
33
33
|
- [Search](technical/search.md) -- Hybrid search architecture and scoring
|
|
34
34
|
- [Indexing](technical/indexing.md) -- How the search index is built
|
|
35
35
|
- [Classification](technical/classification.md) -- Matcher and renderer behavior
|
|
36
|
+
- [Functional Contract Patterns](technical/functional-contract-patterns.md) -- Quick reference for contributor pipelines and small process contracts
|
|
37
|
+
- [Implementation Plan: Functional Contract Refactor](technical/implementation-plan-functional-contract-refactor.md) -- Phased plan to move behavior from type-centric switchboards to process-local contributors
|
|
38
|
+
- [Architecture Cleanup Checklist](technical/architecture-cleanup-checklist.md) -- Living checklist for executing the cleanup plan with parity gates, reviews, and git hygiene
|
|
36
39
|
- [Show Response](technical/show-response.md) -- `akm show` output fields by asset type
|
|
37
40
|
- [Testing Workflow](technical/testing-workflow.md) -- End-to-end, Docker, deployment, and upgrade validation
|
|
38
41
|
- [Ref Format](technical/ref.md) -- Wire format for asset references
|
|
39
42
|
- [Test Coverage Guide](technical/test-coverage-guide.md) -- High-value testing areas
|
|
40
43
|
- [Core Principles](technical/akm-core-principles.md) -- Design principles and constraints
|
|
41
|
-
-
|
|
44
|
+
- `technical/benchmark.md` (planned) -- Search-quality benchmark suite
|
|
42
45
|
|
|
43
46
|
## Posts
|
|
44
47
|
|
|
@@ -90,7 +90,7 @@ until the user opts in. Seven keys ship in 0.7.0:
|
|
|
90
90
|
| `feedback_distillation` | `akm distill <ref>` |
|
|
91
91
|
| `embedding_fallback_score` | scorer fallback when embeddings unavailable |
|
|
92
92
|
| `memory_inference` | indexer split of pending memories into atomic facts |
|
|
93
|
-
| `graph_extraction` | indexer entity/relation extraction →
|
|
93
|
+
| `graph_extraction` | indexer entity/relation extraction → SQLite graph tables |
|
|
94
94
|
|
|
95
95
|
Every gated call site uses `tryLlmFeature(feature, config, fn, fallback)`
|
|
96
96
|
from `src/llm/feature-gate.ts`. The wrapper guarantees:
|
|
@@ -14,4 +14,4 @@ This is a patch release that fixes the publish process. No functional changes fr
|
|
|
14
14
|
- **`.stash.json` no longer drives incremental stale detection** — editing `.stash.json` alone no longer forces directories to rescan during incremental indexing.
|
|
15
15
|
- **One-shot URL ingest for `akm import` and `akm wiki stash`** — both commands now accept a single HTTP/HTTPS URL in addition to file paths and stdin. `akm import <url>` fetches the exact page, converts it to markdown, and writes it into `knowledge/` using a URL-path-derived default name. `akm wiki stash <wiki> <url>` fetches the exact page, converts it to markdown, and writes it into `wikis/<wiki>/raw/`. Neither command registers a persistent website source or crawls linked pages.
|
|
16
16
|
|
|
17
|
-
For stash authors on the 0.7.x pre-release line: `.stash.json` remains supported for compatibility in this release, but it is deprecated and will be removed in v0.8.0. That timeline is intentional: during this aggressive pre-release phase-out window, compatibility shims do not stay around until 1.0 unless they still earn their cost. Prefer frontmatter for markdown assets and structured code comments for scripts, and migrate any remaining `.stash.json` metadata before taking the 0.8 upgrade.
|
|
17
|
+
For stash authors on the 0.7.x pre-release line: `.stash.json` remains supported for compatibility in this release, but it is deprecated and will be removed in v0.8.0. That timeline is intentional: during this aggressive pre-release phase-out window, compatibility shims do not stay around until 1.0 unless they still earn their cost. Prefer frontmatter for markdown assets and structured code comments for scripts, and migrate any remaining `.stash.json` metadata before taking the 0.8 upgrade.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Migration notes for akm v0.7.5
|
|
2
|
+
|
|
3
|
+
0.7.5 is a patch release that rolls up all changes shipped on `main` after v0.7.4.
|
|
4
|
+
|
|
5
|
+
- **Workflow runs are now workspace-scoped** — ref-based workflow commands (`workflow next`, `workflow status`, `workflow list`) now resolve runs within the current project/worktree/directory instead of sharing one global active-run pool across every repo and local sandbox. This fixes cross-repo blocking and leakage for workflow refs such as `workflow:github-issues-parallel-implementer`. Direct run-id commands still target the exact run.
|
|
6
|
+
- **Workflow help/docs now explain scope semantics** — CLI help, operator docs, and workflow-facing hints now describe the current-scope behavior so teams know that ref-based run resolution is local to the current workspace.
|
|
7
|
+
- **`show` now prefers fresh index state** — stale index reads auto-refresh instead of silently falling back to a raw filesystem path, reducing search/show drift.
|
|
8
|
+
- **External agent + LLM parsing is more defensive** — reflect/propose and related local-model JSON parsing paths are hardened against malformed or partial output.
|
|
9
|
+
- **Reflect temp-file handling is safer** — draft files for reflection flows now live in OS temp space instead of the stash.
|
|
10
|
+
- **Memory inference respects token budgets** — long inputs are now trimmed to the configured LLM budget.
|
|
11
|
+
- **Vault and save follow-ups landed** — vault path/run flows and named git stash selection in `akm save` received targeted fixes and regression coverage.
|
|
12
|
+
- **Release/migration packaging is tighter** — published static files, changelog resolution, and rerunnable release automation received follow-up fixes.
|
|
13
|
+
|
|
14
|
+
No manual migration is required for most users, but note these operator-facing details:
|
|
15
|
+
|
|
16
|
+
1. If you were relying on ref-based workflow runs being globally shared across repos or directories, that behavior is gone. Use direct run IDs when you intentionally need to resume a specific run created elsewhere.
|
|
17
|
+
2. Existing older workflow runs created before the new workspace scope key was introduced are still accessible by direct run ID, but ref-based commands now prefer runs created in the current scope.
|
|
18
|
+
3. `akm help migrate latest` now resolves to 0.7.5 and uses the published `.github/CHANGELOG.md` path.
|
|
19
|
+
|
|
20
|
+
Full changelog: https://github.com/itlackey/akm/blob/main/.github/CHANGELOG.md
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Migration notes for akm v0.8.0
|
|
2
|
+
|
|
3
|
+
This release combines the 0.8.0 CLI/storage break with the final improve-owned
|
|
4
|
+
maintenance migration work that landed before release validation closed.
|
|
5
|
+
|
|
6
|
+
Key operator-facing changes:
|
|
7
|
+
|
|
8
|
+
- `akm index --enrich` and `akm index --re-enrich` are removed.
|
|
9
|
+
- Plain `akm index` now owns metadata enhancement only.
|
|
10
|
+
- Memory inference and graph extraction now run from `akm improve` after
|
|
11
|
+
consolidation, not from `akm index`.
|
|
12
|
+
- Manual QA and sandbox guidance now require isolating `AKM_DATA_DIR` in
|
|
13
|
+
addition to `HOME`, XDG dirs, and `AKM_STASH_DIR`.
|
|
14
|
+
- `akm health` is available for post-upgrade runtime checks against `state.db`,
|
|
15
|
+
task-history integrity, agent availability, and recent improve telemetry.
|
|
16
|
+
|
|
17
|
+
Primary public command family for 0.8.0:
|
|
18
|
+
|
|
19
|
+
- `akm improve [<type>|<ref>]`
|
|
20
|
+
- `akm propose <type> <name> (--task "..." | --file <path>)`
|
|
21
|
+
- `akm proposals`
|
|
22
|
+
- `akm show proposal <id>`
|
|
23
|
+
- `akm diff proposal <id>`
|
|
24
|
+
- `akm accept <id>`
|
|
25
|
+
- `akm reject <id> --reason "..."`
|
|
26
|
+
|
|
27
|
+
Proposal-queue and improvement workflows are consolidated around this family.
|
|
28
|
+
Update scripts, prompts, docs, and agent instructions before upgrading.
|
|
29
|
+
|
|
30
|
+
Behavior changes worth noting:
|
|
31
|
+
|
|
32
|
+
- `akm search` with no query now fails with `MISSING_REQUIRED_ARGUMENT`.
|
|
33
|
+
- `akm remember --enrich` is fail-soft: if no LLM is configured, the memory is
|
|
34
|
+
still written.
|
|
35
|
+
- `akm wiki stash <wiki> <url>` once again fetches and stores URL snapshots in
|
|
36
|
+
`wikis/<name>/raw/`.
|
|
37
|
+
- `akm health --since 24h` provides a fast operator check for state-db
|
|
38
|
+
round-trip health, task-log backing, and recent improve outcomes.
|
|
39
|
+
|
|
40
|
+
Release validation status for the final 0.8.0 cut:
|
|
41
|
+
|
|
42
|
+
- Full repo test suite passed.
|
|
43
|
+
- Docker install matrix passed for both Bun and binary install paths.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-rc.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Kit Manager) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
|
|
6
6
|
"keywords": [
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"files": [
|
|
35
35
|
"dist",
|
|
36
36
|
"README.md",
|
|
37
|
-
"CHANGELOG.md",
|
|
37
|
+
".github/CHANGELOG.md",
|
|
38
38
|
"LICENSE",
|
|
39
39
|
"docs/migration/release-notes"
|
|
40
40
|
],
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"akm": "dist/cli.js"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
|
-
"build": "rm -rf dist && bun run tsc --project ./tsconfig.build.json",
|
|
45
|
+
"build": "rm -rf dist && bun run tsc --project ./tsconfig.build.json && bun scripts/copy-assets.ts",
|
|
46
46
|
"check": "bun run lint && bunx tsc --noEmit && bun test ./tests",
|
|
47
47
|
"check:changed": "bun test tests/output-baseline.test.ts tests/e2e.test.ts tests/stash-search.test.ts && bun run lint && bunx tsc --noEmit",
|
|
48
48
|
"test": "bun test ./tests",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@clack/prompts": "^1.3.0",
|
|
77
|
+
"@opencode-ai/sdk": "1.2.20",
|
|
77
78
|
"citty": "^0.2.2",
|
|
78
79
|
"dotenv": "^17.4.2",
|
|
79
80
|
"yaml": "^2.8.4"
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scaffolded content for a fresh `wikis/<name>/` directory.
|
|
3
|
-
*
|
|
4
|
-
* Inlined as TypeScript constants so they ship with the published bundle
|
|
5
|
-
* (the build step is `tsc` — non-TS files are not copied to dist).
|
|
6
|
-
*
|
|
7
|
-
* The scaffold is deliberately minimal: akm does not prescribe conventions
|
|
8
|
-
* beyond the three-layer layout. `schema.md` is the per-wiki rulebook the
|
|
9
|
-
* agent reads first; authors customise it freely.
|
|
10
|
-
*/
|
|
11
|
-
export function buildSchemaMd(wikiName) {
|
|
12
|
-
return `---
|
|
13
|
-
description: Rules that govern this wiki. Read before ingesting, searching, or editing pages.
|
|
14
|
-
wikiRole: schema
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
# ${wikiName} wiki schema
|
|
18
|
-
|
|
19
|
-
This wiki follows the three-layer pattern:
|
|
20
|
-
|
|
21
|
-
- \`raw/\` — immutable ingested sources (never edit)
|
|
22
|
-
- \`<page>.md\` and \`<topic>/<page>.md\` — agent-authored pages
|
|
23
|
-
- \`schema.md\` (this file), \`index.md\`, \`log.md\` — wiki-level metadata
|
|
24
|
-
|
|
25
|
-
## Page frontmatter
|
|
26
|
-
|
|
27
|
-
Every page should carry frontmatter so akm can index and link it:
|
|
28
|
-
|
|
29
|
-
\`\`\`yaml
|
|
30
|
-
---
|
|
31
|
-
description: one-sentence summary used in search and lint
|
|
32
|
-
pageKind: entity | concept | question | note | <your-custom-kind>
|
|
33
|
-
xrefs:
|
|
34
|
-
- wiki:${wikiName}/other-page
|
|
35
|
-
sources:
|
|
36
|
-
- raw/<slug>.md
|
|
37
|
-
---
|
|
38
|
-
\`\`\`
|
|
39
|
-
|
|
40
|
-
\`pageKind\` accepts any non-empty string. Add new categories freely; they
|
|
41
|
-
will surface in \`index.md\` as new sections after the next \`akm index\` run.
|
|
42
|
-
|
|
43
|
-
## Three operations
|
|
44
|
-
|
|
45
|
-
### Ingest
|
|
46
|
-
|
|
47
|
-
1. Copy the new source into \`raw/\` with \`akm wiki stash ${wikiName} <path>\`.
|
|
48
|
-
2. Find related pages: \`akm wiki search ${wikiName} "<terms>"\`.
|
|
49
|
-
3. For each related page: append a section, note a contradiction, or create a
|
|
50
|
-
new page. Update xrefs on both sides.
|
|
51
|
-
4. Cite the raw source in each touched page's \`sources:\` frontmatter.
|
|
52
|
-
5. Append one entry to \`log.md\` describing what was assimilated.
|
|
53
|
-
|
|
54
|
-
### Query
|
|
55
|
-
|
|
56
|
-
1. \`akm wiki search ${wikiName} "<question>"\` — find candidate pages.
|
|
57
|
-
2. \`akm show wiki:${wikiName}/<page>\` — read the top hits.
|
|
58
|
-
3. Compose the answer from the wiki; cite raw sources only when the wiki
|
|
59
|
-
points at them.
|
|
60
|
-
|
|
61
|
-
### Lint
|
|
62
|
-
|
|
63
|
-
1. \`akm wiki lint ${wikiName}\` — deterministic structural checks.
|
|
64
|
-
2. Resolve each finding: link orphans, fix broken xrefs, add descriptions,
|
|
65
|
-
cite uncited raws, refresh the index.
|
|
66
|
-
|
|
67
|
-
## Hard rules
|
|
68
|
-
|
|
69
|
-
- \`raw/\` is immutable. Never edit ingested sources.
|
|
70
|
-
- Cross-references must point at pages that actually exist.
|
|
71
|
-
- Prefer appending to an existing page over duplicating one.
|
|
72
|
-
- Cite the raw source id (e.g. \`raw/2026-04-foo.md\`) when copying claims.
|
|
73
|
-
`;
|
|
74
|
-
}
|
|
75
|
-
export function buildIndexMd(wikiName) {
|
|
76
|
-
return `---
|
|
77
|
-
description: Catalog of pages in the ${wikiName} wiki. Regenerated by \`akm index\`.
|
|
78
|
-
wikiRole: index
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
# ${wikiName} — index
|
|
82
|
-
|
|
83
|
-
_This file is regenerated on every \`akm index\` run. Manual edits are
|
|
84
|
-
preserved until the next regeneration, then replaced._
|
|
85
|
-
|
|
86
|
-
_(no pages yet — create one with your editor, or ingest a source with \`akm
|
|
87
|
-
wiki stash ${wikiName} <path>\`.)_
|
|
88
|
-
`;
|
|
89
|
-
}
|
|
90
|
-
export function buildLogMd(wikiName) {
|
|
91
|
-
return `---
|
|
92
|
-
description: Append-only log for the ${wikiName} wiki. Newest entries at the top.
|
|
93
|
-
wikiRole: log
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
# ${wikiName} — log
|
|
97
|
-
|
|
98
|
-
_Each entry: ISO date, operation, brief summary._
|
|
99
|
-
`;
|
|
100
|
-
}
|