akm-cli 0.7.5 → 0.8.0-rc.6

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.
Files changed (236) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +113 -2
  2. package/README.md +20 -4
  3. package/SECURITY.md +93 -0
  4. package/dist/cli/config-migrate.js +144 -0
  5. package/dist/cli/config-validate.js +39 -0
  6. package/dist/cli/confirm.js +73 -0
  7. package/dist/cli/parse-args.js +133 -0
  8. package/dist/cli.js +1995 -551
  9. package/dist/commands/agent-dispatch.js +110 -0
  10. package/dist/commands/agent-support.js +68 -0
  11. package/dist/commands/completions.js +3 -0
  12. package/dist/commands/config-cli.js +130 -534
  13. package/dist/commands/consolidate.js +1531 -0
  14. package/dist/commands/curate.js +44 -3
  15. package/dist/commands/db-cli.js +23 -0
  16. package/dist/commands/distill-promotion-policy.js +660 -0
  17. package/dist/commands/distill.js +990 -75
  18. package/dist/commands/eval-cases.js +43 -0
  19. package/dist/commands/events.js +5 -23
  20. package/dist/commands/graph.js +477 -0
  21. package/dist/commands/health.js +400 -0
  22. package/dist/commands/help/help-accept.md +9 -0
  23. package/dist/commands/help/help-improve.md +77 -0
  24. package/dist/commands/help/help-proposals.md +15 -0
  25. package/dist/commands/help/help-propose.md +17 -0
  26. package/dist/commands/help/help-reject.md +8 -0
  27. package/dist/commands/history.js +54 -46
  28. package/dist/commands/improve-profiles.js +146 -0
  29. package/dist/commands/improve-result-file.js +103 -0
  30. package/dist/commands/improve.js +2175 -0
  31. package/dist/commands/info.js +5 -2
  32. package/dist/commands/init.js +50 -2
  33. package/dist/commands/installed-stashes.js +102 -139
  34. package/dist/commands/knowledge.js +136 -0
  35. package/dist/commands/lint/agent-linter.js +49 -0
  36. package/dist/commands/lint/base-linter.js +479 -0
  37. package/dist/commands/lint/command-linter.js +49 -0
  38. package/dist/commands/lint/default-linter.js +16 -0
  39. package/dist/commands/lint/index.js +183 -0
  40. package/dist/commands/lint/knowledge-linter.js +16 -0
  41. package/dist/commands/lint/markdown-insertion.js +343 -0
  42. package/dist/commands/lint/memory-linter.js +61 -0
  43. package/dist/commands/lint/registry.js +36 -0
  44. package/dist/commands/lint/skill-linter.js +45 -0
  45. package/dist/commands/lint/task-linter.js +50 -0
  46. package/dist/commands/lint/types.js +4 -0
  47. package/dist/commands/lint/vault-key-rules.js +139 -0
  48. package/dist/commands/lint/workflow-linter.js +56 -0
  49. package/dist/commands/lint.js +4 -0
  50. package/dist/commands/migration-help.js +5 -2
  51. package/dist/commands/proposal.js +66 -12
  52. package/dist/commands/propose.js +86 -31
  53. package/dist/commands/reflect.js +1119 -73
  54. package/dist/commands/registry-search.js +5 -2
  55. package/dist/commands/remember.js +69 -6
  56. package/dist/commands/schema-repair.js +203 -0
  57. package/dist/commands/search.js +115 -14
  58. package/dist/commands/self-update.js +3 -0
  59. package/dist/commands/show.js +144 -25
  60. package/dist/commands/source-add.js +17 -45
  61. package/dist/commands/source-clone.js +3 -0
  62. package/dist/commands/source-manage.js +14 -19
  63. package/dist/commands/tasks.js +438 -0
  64. package/dist/commands/url-checker.js +42 -0
  65. package/dist/commands/vault.js +130 -77
  66. package/dist/core/action-contributors.js +28 -0
  67. package/dist/core/asset-ref.js +7 -0
  68. package/dist/core/asset-registry.js +7 -16
  69. package/dist/core/asset-serialize.js +88 -0
  70. package/dist/core/asset-spec.js +22 -0
  71. package/dist/core/common.js +157 -0
  72. package/dist/core/concurrent.js +25 -0
  73. package/dist/core/config-io.js +347 -0
  74. package/dist/core/config-migration.js +625 -0
  75. package/dist/core/config-schema.js +501 -0
  76. package/dist/core/config-sources.js +108 -0
  77. package/dist/core/config-types.js +4 -0
  78. package/dist/core/config-walker.js +337 -0
  79. package/dist/core/config.js +327 -987
  80. package/dist/core/errors.js +40 -19
  81. package/dist/core/events.js +91 -138
  82. package/dist/core/file-lock.js +104 -0
  83. package/dist/core/frontmatter.js +3 -6
  84. package/dist/core/lesson-lint.js +3 -0
  85. package/dist/core/markdown.js +20 -0
  86. package/dist/core/memory-belief.js +62 -0
  87. package/dist/core/memory-contradiction-detect.js +274 -0
  88. package/dist/core/memory-improve.js +806 -0
  89. package/dist/core/parse.js +158 -0
  90. package/dist/core/paths.js +326 -14
  91. package/dist/core/proposal-quality-validators.js +364 -0
  92. package/dist/core/proposal-validators.js +69 -0
  93. package/dist/core/proposals.js +498 -42
  94. package/dist/core/state-db.js +927 -0
  95. package/dist/core/text-truncation.js +107 -0
  96. package/dist/core/time.js +54 -0
  97. package/dist/core/warn.js +62 -1
  98. package/dist/core/write-source.js +3 -0
  99. package/dist/indexer/db-backup.js +391 -0
  100. package/dist/indexer/db-search.js +152 -253
  101. package/dist/indexer/db.js +933 -103
  102. package/dist/indexer/ensure-index.js +64 -0
  103. package/dist/indexer/file-context.js +3 -0
  104. package/dist/indexer/graph-boost.js +376 -101
  105. package/dist/indexer/graph-db.js +391 -0
  106. package/dist/indexer/graph-dedup.js +95 -0
  107. package/dist/indexer/graph-extraction.js +550 -124
  108. package/dist/indexer/index-context.js +4 -0
  109. package/dist/indexer/indexer.js +506 -291
  110. package/dist/indexer/llm-cache.js +47 -0
  111. package/dist/indexer/manifest.js +3 -0
  112. package/dist/indexer/matchers.js +148 -160
  113. package/dist/indexer/memory-inference.js +99 -74
  114. package/dist/indexer/metadata-contributors.js +29 -0
  115. package/dist/indexer/metadata.js +255 -196
  116. package/dist/indexer/path-resolver.js +92 -0
  117. package/dist/indexer/project-context.js +192 -0
  118. package/dist/indexer/ranking-contributors.js +331 -0
  119. package/dist/indexer/ranking.js +81 -0
  120. package/dist/indexer/search-fields.js +5 -9
  121. package/dist/indexer/search-hit-enrichers.js +111 -0
  122. package/dist/indexer/search-source.js +44 -10
  123. package/dist/indexer/semantic-status.js +5 -16
  124. package/dist/indexer/staleness-detect.js +447 -0
  125. package/dist/indexer/usage-events.js +12 -9
  126. package/dist/indexer/walker.js +28 -0
  127. package/dist/integrations/agent/builders.js +135 -0
  128. package/dist/integrations/agent/config.js +122 -230
  129. package/dist/integrations/agent/detect.js +3 -0
  130. package/dist/integrations/agent/index.js +7 -13
  131. package/dist/integrations/agent/model-aliases.js +55 -0
  132. package/dist/integrations/agent/profiles.js +70 -5
  133. package/dist/integrations/agent/prompts.js +150 -74
  134. package/dist/integrations/agent/runner.js +151 -0
  135. package/dist/integrations/agent/sdk-runner.js +126 -0
  136. package/dist/integrations/agent/spawn.js +118 -23
  137. package/dist/integrations/github.js +3 -0
  138. package/dist/integrations/lockfile.js +32 -69
  139. package/dist/integrations/session-logs/index.js +68 -0
  140. package/dist/integrations/session-logs/providers/claude-code.js +59 -0
  141. package/dist/integrations/session-logs/providers/opencode.js +55 -0
  142. package/dist/integrations/session-logs/types.js +4 -0
  143. package/dist/llm/call-ai.js +62 -0
  144. package/dist/llm/client.js +72 -124
  145. package/dist/llm/embedder.js +3 -19
  146. package/dist/llm/embedders/cache.js +3 -7
  147. package/dist/llm/embedders/local.js +3 -0
  148. package/dist/llm/embedders/remote.js +20 -8
  149. package/dist/llm/embedders/types.js +3 -7
  150. package/dist/llm/feature-gate.js +89 -48
  151. package/dist/llm/graph-extract.js +676 -70
  152. package/dist/llm/index-passes.js +9 -23
  153. package/dist/llm/memory-infer.js +52 -71
  154. package/dist/llm/metadata-enhance.js +42 -29
  155. package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
  156. package/dist/output/cli-hints-full.md +281 -0
  157. package/dist/output/cli-hints-short.md +65 -0
  158. package/dist/output/cli-hints.js +5 -318
  159. package/dist/output/context.js +3 -0
  160. package/dist/output/renderers.js +223 -256
  161. package/dist/output/shapes.js +150 -105
  162. package/dist/output/text.js +318 -30
  163. package/dist/registry/build-index.js +3 -0
  164. package/dist/registry/create-provider-registry.js +3 -0
  165. package/dist/registry/factory.js +3 -0
  166. package/dist/registry/origin-resolve.js +3 -0
  167. package/dist/registry/providers/index.js +3 -0
  168. package/dist/registry/providers/skills-sh.js +70 -49
  169. package/dist/registry/providers/static-index.js +53 -48
  170. package/dist/registry/providers/types.js +3 -24
  171. package/dist/registry/resolve.js +11 -16
  172. package/dist/registry/types.js +3 -0
  173. package/dist/scripts/migrate-storage.js +17307 -0
  174. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8900 -0
  175. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  176. package/dist/setup/detect.js +3 -0
  177. package/dist/setup/ripgrep-install.js +3 -0
  178. package/dist/setup/ripgrep-resolve.js +3 -0
  179. package/dist/setup/setup.js +775 -37
  180. package/dist/setup/steps.js +3 -15
  181. package/dist/sources/include.js +3 -0
  182. package/dist/sources/provider-factory.js +5 -12
  183. package/dist/sources/provider.js +3 -20
  184. package/dist/sources/providers/filesystem.js +19 -23
  185. package/dist/sources/providers/git.js +7 -5
  186. package/dist/sources/providers/index.js +3 -0
  187. package/dist/sources/providers/install-types.js +3 -13
  188. package/dist/sources/providers/npm.js +3 -4
  189. package/dist/sources/providers/provider-utils.js +3 -0
  190. package/dist/sources/providers/sync-from-ref.js +3 -11
  191. package/dist/sources/providers/tar-utils.js +3 -0
  192. package/dist/sources/providers/website.js +18 -22
  193. package/dist/sources/resolve.js +3 -0
  194. package/dist/sources/types.js +3 -0
  195. package/dist/sources/website-ingest.js +7 -0
  196. package/dist/tasks/backends/cron.js +203 -0
  197. package/dist/tasks/backends/exec-utils.js +28 -0
  198. package/dist/tasks/backends/index.js +24 -0
  199. package/dist/tasks/backends/launchd-template.xml +19 -0
  200. package/dist/tasks/backends/launchd.js +187 -0
  201. package/dist/tasks/backends/schtasks-template.xml +29 -0
  202. package/dist/tasks/backends/schtasks.js +215 -0
  203. package/dist/tasks/parser.js +211 -0
  204. package/dist/tasks/resolveAkmBin.js +87 -0
  205. package/dist/tasks/runner.js +458 -0
  206. package/dist/tasks/schedule.js +211 -0
  207. package/dist/tasks/schema.js +15 -0
  208. package/dist/tasks/validator.js +62 -0
  209. package/dist/version.js +3 -0
  210. package/dist/wiki/index-template.md +12 -0
  211. package/dist/wiki/ingest-workflow-template.md +54 -0
  212. package/dist/wiki/log-template.md +8 -0
  213. package/dist/wiki/schema-template.md +61 -0
  214. package/dist/wiki/wiki-templates.js +15 -0
  215. package/dist/wiki/wiki.js +13 -61
  216. package/dist/workflows/authoring.js +8 -25
  217. package/dist/workflows/cli.js +3 -0
  218. package/dist/workflows/db.js +140 -10
  219. package/dist/workflows/document-cache.js +3 -10
  220. package/dist/workflows/parser.js +3 -0
  221. package/dist/workflows/renderer.js +11 -3
  222. package/dist/workflows/runs.js +62 -91
  223. package/dist/workflows/schema.js +3 -0
  224. package/dist/workflows/scope-key.js +3 -0
  225. package/dist/workflows/validator.js +4 -8
  226. package/dist/workflows/workflow-template.md +24 -0
  227. package/docs/README.md +9 -2
  228. package/docs/data-and-telemetry.md +225 -0
  229. package/docs/migration/release-notes/0.7.0.md +1 -1
  230. package/docs/migration/release-notes/0.7.5.md +2 -2
  231. package/docs/migration/release-notes/0.8.0.md +48 -0
  232. package/docs/migration/v0.7-to-v0.8.md +1307 -0
  233. package/package.json +20 -8
  234. package/.github/LICENSE +0 -374
  235. package/dist/commands/install-audit.js +0 -381
  236. package/dist/templates/wiki-templates.js +0 -100
@@ -0,0 +1,211 @@
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
+ /**
5
+ * Cross-platform schedule parsing and translation.
6
+ *
7
+ * Users always type cron-style expressions:
8
+ *
9
+ * • `m h dom mon dow` — five-field cron (UNIX minute/hour/dom/mon/dow)
10
+ * • `@hourly` `@daily` `@weekly` `@monthly`
11
+ *
12
+ * Each {@link ScheduleSpec} can then be translated to:
13
+ *
14
+ * • a verbatim cron line (Linux backend),
15
+ * • a launchd plist `<StartCalendarInterval>` / `<StartInterval>` (macOS),
16
+ * • Task Scheduler XML triggers (Windows).
17
+ *
18
+ * The shared subset is `*`, single integers, `*\/N`, plus the `@hourly /
19
+ * @daily / @weekly / @monthly` aliases. Patterns outside that — multi-value
20
+ * lists, ranges, step values other than `*\/N`, day-of-month AND
21
+ * day-of-week combinations — are rejected with a {@link UsageError}.
22
+ *
23
+ * Cron is the most permissive of the three backends; some patterns it
24
+ * accepts (e.g. `@hourly` = `0 * * * *`) have no clean schtasks primitive.
25
+ * Validation runs against the *active* backend, so a task authored on
26
+ * Linux may fail to translate when copied to macOS/Windows. `tasks sync`
27
+ * re-validates against the local backend and surfaces any incompatibility.
28
+ */
29
+ import { UsageError } from "../core/errors";
30
+ const ALIAS_TO_CRON = {
31
+ "@hourly": "0 * * * *",
32
+ "@daily": "0 0 * * *",
33
+ "@midnight": "0 0 * * *",
34
+ "@weekly": "0 0 * * 0",
35
+ "@monthly": "0 0 1 * *",
36
+ };
37
+ const FIELD_LIMITS = {
38
+ minute: { min: 0, max: 59 },
39
+ hour: { min: 0, max: 23 },
40
+ dom: { min: 1, max: 31 },
41
+ month: { min: 1, max: 12 },
42
+ dow: { min: 0, max: 6 },
43
+ };
44
+ const SUPPORTED_HINT = "Supported subset: `*`, single integers (`5`), and step-on-star (`*/N`). " +
45
+ "Aliases: `@hourly`, `@daily`, `@weekly`, `@monthly`. " +
46
+ "Lists, ranges, and named days/months are not supported.";
47
+ export function parseSchedule(input, backend) {
48
+ const cron = expandAlias(input);
49
+ const fields = parseCronFields(cron, input);
50
+ const spec = { raw: input, cron, fields };
51
+ // Validate translatability for the active backend so the caller does not
52
+ // silently accept expressions that backend cannot run. Note: cron is the
53
+ // most permissive of the three (e.g. `@hourly` is `0 * * * *` which has
54
+ // no clean schtasks primitive), so a task authored on Linux may not be
55
+ // portable to macOS/Windows. `tasks sync` on the destination platform
56
+ // re-runs this with the local backend and will surface any incompatibility.
57
+ if (backend === "launchd")
58
+ translateToLaunchd(spec);
59
+ if (backend === "schtasks")
60
+ translateToSchtasks(spec);
61
+ return spec;
62
+ }
63
+ function expandAlias(raw) {
64
+ const trimmed = raw.trim();
65
+ if (!trimmed) {
66
+ throw new UsageError("Schedule is empty.", "MISSING_REQUIRED_ARGUMENT");
67
+ }
68
+ const lower = trimmed.toLowerCase();
69
+ if (lower in ALIAS_TO_CRON)
70
+ return ALIAS_TO_CRON[lower];
71
+ return trimmed;
72
+ }
73
+ function parseCronFields(cron, original) {
74
+ const parts = cron.split(/\s+/);
75
+ if (parts.length !== 5) {
76
+ throw new UsageError(`Invalid schedule "${original}": expected 5 fields, got ${parts.length}. ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE");
77
+ }
78
+ const [m, h, dom, mon, dow] = parts;
79
+ return {
80
+ minute: parseField(m, "minute", FIELD_LIMITS.minute, original),
81
+ hour: parseField(h, "hour", FIELD_LIMITS.hour, original),
82
+ dom: parseField(dom, "day-of-month", FIELD_LIMITS.dom, original),
83
+ month: parseField(mon, "month", FIELD_LIMITS.month, original),
84
+ dow: parseField(dow, "day-of-week", FIELD_LIMITS.dow, original),
85
+ };
86
+ }
87
+ function parseField(raw, name, limit, original) {
88
+ if (raw === "*")
89
+ return { kind: "star" };
90
+ const stepMatch = raw.match(/^\*\/(\d+)$/);
91
+ if (stepMatch) {
92
+ const step = Number(stepMatch[1]);
93
+ // Step must be ≥1 and ≤ the field's range size, not just `max`. For
94
+ // 1-based fields like day-of-month (1-31) and month (1-12) the previous
95
+ // `max + 1` bound let invalid steps like `*/32` or `*/13` slip through.
96
+ const range = limit.max - limit.min + 1;
97
+ if (!Number.isInteger(step) || step <= 0 || step > range) {
98
+ throw new UsageError(`Invalid ${name} step "${raw}" in schedule "${original}". ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE");
99
+ }
100
+ return { kind: "step", step };
101
+ }
102
+ if (/^\d+$/.test(raw)) {
103
+ const value = Number(raw);
104
+ if (value < limit.min || value > limit.max) {
105
+ throw new UsageError(`Invalid ${name} value "${raw}" in schedule "${original}" (allowed ${limit.min}-${limit.max}).`, "INVALID_FLAG_VALUE");
106
+ }
107
+ return { kind: "value", value };
108
+ }
109
+ throw new UsageError(`Unsupported ${name} expression "${raw}" in schedule "${original}". ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE");
110
+ }
111
+ // ── Backend translators ─────────────────────────────────────────────────────
112
+ /** Verbatim cron line, alias-expanded. */
113
+ export function translateToCron(spec) {
114
+ return spec.cron;
115
+ }
116
+ export function translateToLaunchd(spec) {
117
+ const f = spec.fields;
118
+ // `*/N` minute (everything else `*`) → StartInterval = N*60 seconds.
119
+ if (f.minute.kind === "step" &&
120
+ f.hour.kind === "star" &&
121
+ f.dom.kind === "star" &&
122
+ f.month.kind === "star" &&
123
+ f.dow.kind === "star") {
124
+ return { intervalSeconds: f.minute.step * 60 };
125
+ }
126
+ // `*/N` hour → StartInterval = N*3600.
127
+ if (f.minute.kind === "value" &&
128
+ f.minute.value === 0 &&
129
+ f.hour.kind === "step" &&
130
+ f.dom.kind === "star" &&
131
+ f.month.kind === "star" &&
132
+ f.dow.kind === "star") {
133
+ return { intervalSeconds: f.hour.step * 3600 };
134
+ }
135
+ // Otherwise build a calendar dict from concrete values. launchd treats any
136
+ // omitted key as "every value", so a `*` field translates to "no key".
137
+ // Exception: launchd does not support arbitrary step values inside a
138
+ // calendar dict — reject those.
139
+ const calendar = {};
140
+ rejectStepInsideCalendar(f.minute, "minute", spec);
141
+ rejectStepInsideCalendar(f.hour, "hour", spec);
142
+ rejectStepInsideCalendar(f.dom, "day-of-month", spec);
143
+ rejectStepInsideCalendar(f.month, "month", spec);
144
+ rejectStepInsideCalendar(f.dow, "day-of-week", spec);
145
+ if (f.minute.kind === "value")
146
+ calendar.Minute = f.minute.value;
147
+ if (f.hour.kind === "value")
148
+ calendar.Hour = f.hour.value;
149
+ if (f.dom.kind === "value")
150
+ calendar.Day = f.dom.value;
151
+ if (f.month.kind === "value")
152
+ calendar.Month = f.month.value;
153
+ if (f.dow.kind === "value")
154
+ calendar.Weekday = f.dow.value;
155
+ // launchd's CalendarInterval requires at least one specific key. If every
156
+ // field is `*` the schedule has no anchor and we'd need a StartInterval
157
+ // instead — treat this as "every minute".
158
+ if (Object.keys(calendar).length === 0) {
159
+ return { intervalSeconds: 60 };
160
+ }
161
+ return { calendar };
162
+ }
163
+ function rejectStepInsideCalendar(field, name, spec) {
164
+ if (field.kind === "step") {
165
+ 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.");
166
+ }
167
+ }
168
+ export function translateToSchtasks(spec) {
169
+ const f = spec.fields;
170
+ // `*/N` minute → MINUTE, every N.
171
+ if (f.minute.kind === "step" &&
172
+ f.hour.kind === "star" &&
173
+ f.dom.kind === "star" &&
174
+ f.month.kind === "star" &&
175
+ f.dow.kind === "star") {
176
+ return { kind: "minute", everyMinutes: f.minute.step };
177
+ }
178
+ // `0 */N * * *` → HOURLY, every N.
179
+ if (f.minute.kind === "value" &&
180
+ f.minute.value === 0 &&
181
+ f.hour.kind === "step" &&
182
+ f.dom.kind === "star" &&
183
+ f.month.kind === "star" &&
184
+ f.dow.kind === "star") {
185
+ return { kind: "hour", everyHours: f.hour.step };
186
+ }
187
+ // `M H * * *` → DAILY at H:M.
188
+ if (f.minute.kind === "value" &&
189
+ f.hour.kind === "value" &&
190
+ f.dom.kind === "star" &&
191
+ f.month.kind === "star" &&
192
+ f.dow.kind === "star") {
193
+ return { kind: "daily", atHour: f.hour.value, atMinute: f.minute.value };
194
+ }
195
+ // `M H * * D` → WEEKLY at H:M on day D.
196
+ if (f.minute.kind === "value" &&
197
+ f.hour.kind === "value" &&
198
+ f.dom.kind === "star" &&
199
+ f.month.kind === "star" &&
200
+ f.dow.kind === "value") {
201
+ return {
202
+ kind: "weekly",
203
+ atHour: f.hour.value,
204
+ atMinute: f.minute.value,
205
+ daysOfWeek: [f.dow.value],
206
+ };
207
+ }
208
+ throw new UsageError(`Schedule "${spec.raw}" cannot be expressed as a Windows Task Scheduler trigger. ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE", "Use one of: */N minutes, every N hours (0 */N * * *), daily at HH:MM, or weekly on a single weekday.");
209
+ }
210
+ /** Human-readable summary used by `tasks doctor`. */
211
+ export const SCHEDULE_SUPPORTED_SUBSET_HINT = SUPPORTED_HINT;
@@ -0,0 +1,15 @@
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
+ /**
5
+ * Task asset schema. A task pairs a cron-style schedule with exactly one of:
6
+ *
7
+ * • a workflow target — invoked via `startWorkflowRun()`
8
+ * • a prompt target — invoked via `runAgent()` against the configured
9
+ * agent harness (e.g. `opencode run`)
10
+ * • a command target — invoked directly via `Bun.spawn()`, no AI agent
11
+ *
12
+ * Tasks are stored as pure YAML files at `<stash>/tasks/<id>.yml`. Multi-line
13
+ * inline prompts use a YAML block scalar (`prompt: |`).
14
+ */
15
+ export const TASK_SCHEMA_VERSION = 1;
@@ -0,0 +1,62 @@
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
+ /**
5
+ * Validate a parsed {@link TaskDocument} for runnability.
6
+ *
7
+ * Enforces:
8
+ * • the schedule is parseable and translates to the active backend
9
+ * • the workflow ref resolves (workflow targets)
10
+ * • the asset/file source exists (prompt targets)
11
+ * • the agent profile resolves (prompt targets)
12
+ *
13
+ * Validation is deliberately split from parsing: callers that only want to
14
+ * read frontmatter (e.g. `tasks list`) can skip these checks, while
15
+ * `tasks add` and `tasks run` should always run them.
16
+ */
17
+ import fs from "node:fs";
18
+ import path from "node:path";
19
+ import { parseAssetRef } from "../core/asset-ref";
20
+ import { resolveStashDir } from "../core/common";
21
+ import { loadConfig } from "../core/config";
22
+ import { NotFoundError } from "../core/errors";
23
+ import { requireAgentProfile } from "../integrations/agent";
24
+ import { resolveAssetPath } from "../sources/resolve";
25
+ import { parseSchedule } from "./schedule";
26
+ export async function validateTaskDocument(task, options) {
27
+ // Schedule must parse and translate.
28
+ parseSchedule(task.schedule, options.backend);
29
+ if (task.target.kind === "workflow") {
30
+ const stashDir = options.stashDir ?? resolveStashDir();
31
+ const ref = parseAssetRef(task.target.ref);
32
+ if (ref.type !== "workflow") {
33
+ throw new NotFoundError(`Task "${task.id}" workflow target must be a workflow ref (got "${task.target.ref}").`, "WORKFLOW_NOT_FOUND");
34
+ }
35
+ await resolveAssetPath(stashDir, "workflow", ref.name);
36
+ return;
37
+ }
38
+ if (task.target.kind !== "prompt") {
39
+ return;
40
+ }
41
+ // Prompt target. Resolve the profile unconditionally — when no profile is
42
+ // set on the task, requireAgentProfile falls back to defaults.agent and
43
+ // throws a clear error if neither is configured. Catching this at
44
+ // `tasks add` / `tasks sync` time is much more useful than failing only
45
+ // when the OS scheduler fires.
46
+ const config = loadConfig();
47
+ requireAgentProfile(config, task.target.profile);
48
+ const src = task.target.source;
49
+ if (src.kind === "asset") {
50
+ const stashDir = options.stashDir ?? resolveStashDir();
51
+ const ref = parseAssetRef(src.ref);
52
+ await resolveAssetPath(stashDir, ref.type, ref.name);
53
+ }
54
+ else if (src.kind === "file") {
55
+ const taskDir = path.dirname(task.source.path);
56
+ const resolved = path.isAbsolute(src.path) ? src.path : path.resolve(taskDir, src.path);
57
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
58
+ throw new NotFoundError(`Prompt file not found for task "${task.id}": ${src.path} (resolved to ${resolved}).`, "FILE_NOT_FOUND");
59
+ }
60
+ }
61
+ // inline source is always valid post-parse.
62
+ }
package/dist/version.js CHANGED
@@ -1,3 +1,6 @@
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/.
1
4
  import fs from "node:fs";
2
5
  import path from "node:path";
3
6
  // Version: prefer compile-time define, then package.json, then fallback
@@ -0,0 +1,12 @@
1
+ ---
2
+ description: Catalog of pages in the {{WIKI_NAME}} wiki. Regenerated by `akm index`.
3
+ wikiRole: index
4
+ ---
5
+
6
+ # {{WIKI_NAME}} — index
7
+
8
+ _This file is regenerated on every `akm index` run. Manual edits are
9
+ preserved until the next regeneration, then replaced._
10
+
11
+ _(no pages yet — create one with your editor, or ingest a source with `akm
12
+ wiki stash {{WIKI_NAME}} <path>`.)_
@@ -0,0 +1,54 @@
1
+ # Ingest workflow for wiki:{{WIKI_NAME}}
2
+
3
+ Wiki location: {{WIKI_DIR}}
4
+ Schema: {{SCHEMA_PATH}}
5
+
6
+ Follow these steps. akm commands handle the invariants; use your native
7
+ Read/Write/Edit tools for page edits.
8
+
9
+ 1. **Read the schema.** Open `{{SCHEMA_PATH}}`. It defines the voice, page
10
+ kinds, contradiction policy, and any wiki-specific conventions. Do not
11
+ skip this step even on familiar wikis — the schema may have changed.
12
+
13
+ 2. **File the source under `raw/`.**
14
+ ```sh
15
+ akm wiki stash {{WIKI_NAME}} <path-or-url-to-source>
16
+ # or: cat <source> | akm wiki stash {{WIKI_NAME}} -
17
+ ```
18
+ Returns `{ slug, path, ref }`. The raw copy is immutable — never edit it.
19
+
20
+ 3. **Find related existing pages.**
21
+ ```sh
22
+ akm wiki search {{WIKI_NAME}} "<key terms from the source>"
23
+ ```
24
+ Read the top hits with `akm show wiki:{{WIKI_NAME}}/<page>`. Use
25
+ `akm show wiki:{{WIKI_NAME}}/<page> toc` for large pages.
26
+
27
+ 4. **Decide for each candidate.** For each related page:
28
+ - **Append**: add a section or paragraph under the relevant heading.
29
+ Include the raw source in the page's `sources:` frontmatter list.
30
+ - **Contradict**: note the tension explicitly; don't silently overwrite.
31
+ Follow the schema's contradiction policy.
32
+ - **Skip**: source doesn't add to this page — move on.
33
+
34
+ 5. **Create new pages for concepts/entities the source introduces.** Each
35
+ new page must have frontmatter with `description`, `pageKind`,
36
+ `xrefs`, and `sources`. Cross-reference with related pages both
37
+ directions.
38
+
39
+ 6. **Update xrefs both ways.** If page A now xrefs page B, page B must xref
40
+ page A. `akm wiki lint {{WIKI_NAME}}` will flag violations.
41
+
42
+ 7. **Append to `log.md`.** One entry per ingest: date, source slug, one-line
43
+ summary, refs to created/edited pages. Newest at the top.
44
+
45
+ 8. **Regenerate the index + verify.**
46
+ ```sh
47
+ akm index
48
+ akm wiki lint {{WIKI_NAME}}
49
+ ```
50
+ Resolve any lint findings before calling the ingest done.
51
+
52
+ That's it. `akm` never calls an LLM — reasoning is your job; it just owns
53
+ the invariants (raw immutability, unique slugs, ref validation, index
54
+ regeneration, structural lint).
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Append-only log for the {{WIKI_NAME}} wiki. Newest entries at the top.
3
+ wikiRole: log
4
+ ---
5
+
6
+ # {{WIKI_NAME}} — log
7
+
8
+ _Each entry: ISO date, operation, brief summary._
@@ -0,0 +1,61 @@
1
+ ---
2
+ description: Rules that govern this wiki. Read before ingesting, searching, or editing pages.
3
+ wikiRole: schema
4
+ ---
5
+
6
+ # {{WIKI_NAME}} wiki schema
7
+
8
+ This wiki follows the three-layer pattern:
9
+
10
+ - `raw/` — immutable ingested sources (never edit)
11
+ - `<page>.md` and `<topic>/<page>.md` — agent-authored pages
12
+ - `schema.md` (this file), `index.md`, `log.md` — wiki-level metadata
13
+
14
+ ## Page frontmatter
15
+
16
+ Every page should carry frontmatter so akm can index and link it:
17
+
18
+ ```yaml
19
+ ---
20
+ description: one-sentence summary used in search and lint
21
+ pageKind: entity | concept | question | note | <your-custom-kind>
22
+ xrefs:
23
+ - wiki:{{WIKI_NAME}}/other-page
24
+ sources:
25
+ - raw/<slug>.md
26
+ ---
27
+ ```
28
+
29
+ `pageKind` accepts any non-empty string. Add new categories freely; they
30
+ will surface in `index.md` as new sections after the next `akm index` run.
31
+
32
+ ## Three operations
33
+
34
+ ### Ingest
35
+
36
+ 1. Copy the new source into `raw/` with `akm wiki stash {{WIKI_NAME}} <path>`.
37
+ 2. Find related pages: `akm wiki search {{WIKI_NAME}} "<terms>"`.
38
+ 3. For each related page: append a section, note a contradiction, or create a
39
+ new page. Update xrefs on both sides.
40
+ 4. Cite the raw source in each touched page's `sources:` frontmatter.
41
+ 5. Append one entry to `log.md` describing what was assimilated.
42
+
43
+ ### Query
44
+
45
+ 1. `akm wiki search {{WIKI_NAME}} "<question>"` — find candidate pages.
46
+ 2. `akm show wiki:{{WIKI_NAME}}/<page>` — read the top hits.
47
+ 3. Compose the answer from the wiki; cite raw sources only when the wiki
48
+ points at them.
49
+
50
+ ### Lint
51
+
52
+ 1. `akm wiki lint {{WIKI_NAME}}` — deterministic structural checks.
53
+ 2. Resolve each finding: link orphans, fix broken xrefs, add descriptions,
54
+ cite uncited raws, refresh the index.
55
+
56
+ ## Hard rules
57
+
58
+ - `raw/` is immutable. Never edit ingested sources.
59
+ - Cross-references must point at pages that actually exist.
60
+ - Prefer appending to an existing page over duplicating one.
61
+ - Cite the raw source id (e.g. `raw/2026-04-foo.md`) when copying claims.
@@ -0,0 +1,15 @@
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 indexTemplate from "./index-template.md" with { type: "text" };
5
+ import logTemplate from "./log-template.md" with { type: "text" };
6
+ import schemaTemplate from "./schema-template.md" with { type: "text" };
7
+ export function buildSchemaMd(wikiName) {
8
+ return schemaTemplate.replaceAll("{{WIKI_NAME}}", wikiName);
9
+ }
10
+ export function buildIndexMd(wikiName) {
11
+ return indexTemplate.replaceAll("{{WIKI_NAME}}", wikiName);
12
+ }
13
+ export function buildLogMd(wikiName) {
14
+ return logTemplate.replaceAll("{{WIKI_NAME}}", wikiName);
15
+ }
package/dist/wiki/wiki.js CHANGED
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * Multi-wiki support for akm (issue #119).
3
6
  *
@@ -42,12 +45,13 @@ import fs from "node:fs";
42
45
  import path from "node:path";
43
46
  import { parse as yamlParse } from "yaml";
44
47
  import { akmSearch } from "../commands/search";
45
- import { isWithin } from "../core/common";
46
- import { loadUserConfig, saveConfig } from "../core/config";
48
+ import { isWithin, todayIso } from "../core/common";
49
+ import { getSources, loadUserConfig, saveConfig } from "../core/config";
47
50
  import { NotFoundError, UsageError } from "../core/errors";
48
51
  import { parseFrontmatter, parseFrontmatterBlock } from "../core/frontmatter";
49
52
  import { resolveSourceEntries } from "../indexer/search-source";
50
- import { buildIndexMd, buildLogMd, buildSchemaMd } from "../templates/wiki-templates";
53
+ import ingestWorkflowTemplate from "./ingest-workflow-template.md" with { type: "text" };
54
+ import { buildIndexMd, buildLogMd, buildSchemaMd } from "./wiki-templates";
51
55
  // ── Constants ───────────────────────────────────────────────────────────────
52
56
  export const WIKIS_SUBDIR = "wikis";
53
57
  export const SCHEMA_MD = "schema.md";
@@ -399,12 +403,11 @@ export function removeWiki(stashDir, name, options = {}) {
399
403
  const isStashWiki = fs.existsSync(wikiDir) && isRecognizedStashWiki(wikiDir);
400
404
  if (!isStashWiki && external) {
401
405
  const config = loadUserConfig();
402
- const filteredSources = (config.sources ?? config.stashes ?? []).filter((entry) => entry.wikiName !== name);
406
+ const filteredSources = getSources(config).filter((entry) => entry.wikiName !== name);
403
407
  const installed = (config.installed ?? []).filter((entry) => entry.wikiName !== name);
404
408
  saveConfig({
405
409
  ...config,
406
410
  sources: filteredSources.length > 0 ? filteredSources : undefined,
407
- stashes: undefined,
408
411
  installed: installed.length > 0 ? installed : undefined,
409
412
  });
410
413
  return {
@@ -665,7 +668,7 @@ function withRawFrontmatter(content, slug) {
665
668
  // tag the wikiRole for the indexer.
666
669
  if (content.startsWith("---"))
667
670
  return content;
668
- const date = new Date().toISOString().slice(0, 10);
671
+ const date = todayIso();
669
672
  return `---\nwikiRole: raw\ningestedAt: ${date}\nslug: ${slug}\n---\n\n${content}`;
670
673
  }
671
674
  function ensureTrailingNewline(value) {
@@ -960,60 +963,9 @@ export function regenerateAllWikiIndexes(stashDir) {
960
963
  export function buildIngestWorkflow(stashDir, name) {
961
964
  const wikiDir = resolveWikiSource(stashDir, name).path;
962
965
  const schemaPath = path.join(wikiDir, SCHEMA_MD);
963
- const workflow = `# Ingest workflow for wiki:${name}
964
-
965
- Wiki location: ${wikiDir}
966
- Schema: ${schemaPath}
967
-
968
- Follow these steps. akm commands handle the invariants; use your native
969
- Read/Write/Edit tools for page edits.
970
-
971
- 1. **Read the schema.** Open \`${schemaPath}\`. It defines the voice, page
972
- kinds, contradiction policy, and any wiki-specific conventions. Do not
973
- skip this step even on familiar wikis — the schema may have changed.
974
-
975
- 2. **File the source under \`raw/\`.**
976
- \`\`\`sh
977
- akm wiki stash ${name} <path-or-url-to-source>
978
- # or: cat <source> | akm wiki stash ${name} -
979
- \`\`\`
980
- Returns \`{ slug, path, ref }\`. The raw copy is immutable — never edit it.
981
-
982
- 3. **Find related existing pages.**
983
- \`\`\`sh
984
- akm wiki search ${name} "<key terms from the source>"
985
- \`\`\`
986
- Read the top hits with \`akm show wiki:${name}/<page>\`. Use
987
- \`akm show wiki:${name}/<page> toc\` for large pages.
988
-
989
- 4. **Decide for each candidate.** For each related page:
990
- - **Append**: add a section or paragraph under the relevant heading.
991
- Include the raw source in the page's \`sources:\` frontmatter list.
992
- - **Contradict**: note the tension explicitly; don't silently overwrite.
993
- Follow the schema's contradiction policy.
994
- - **Skip**: source doesn't add to this page — move on.
995
-
996
- 5. **Create new pages for concepts/entities the source introduces.** Each
997
- new page must have frontmatter with \`description\`, \`pageKind\`,
998
- \`xrefs\`, and \`sources\`. Cross-reference with related pages both
999
- directions.
1000
-
1001
- 6. **Update xrefs both ways.** If page A now xrefs page B, page B must xref
1002
- page A. \`akm wiki lint ${name}\` will flag violations.
1003
-
1004
- 7. **Append to \`log.md\`.** One entry per ingest: date, source slug, one-line
1005
- summary, refs to created/edited pages. Newest at the top.
1006
-
1007
- 8. **Regenerate the index + verify.**
1008
- \`\`\`sh
1009
- akm index
1010
- akm wiki lint ${name}
1011
- \`\`\`
1012
- Resolve any lint findings before calling the ingest done.
1013
-
1014
- That's it. \`akm\` never calls an LLM — reasoning is your job; it just owns
1015
- the invariants (raw immutability, unique slugs, ref validation, index
1016
- regeneration, structural lint).
1017
- `;
966
+ const workflow = ingestWorkflowTemplate
967
+ .replaceAll("{{WIKI_NAME}}", name)
968
+ .replaceAll("{{WIKI_DIR}}", wikiDir)
969
+ .replaceAll("{{SCHEMA_PATH}}", schemaPath);
1018
970
  return { wiki: name, path: wikiDir, schemaPath, workflow };
1019
971
  }
@@ -1,3 +1,6 @@
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/.
1
4
  import fs from "node:fs";
2
5
  import path from "node:path";
3
6
  import { resolveAssetPathFromName } from "../core/asset-spec";
@@ -5,6 +8,7 @@ import { isWithin, resolveStashDir } from "../core/common";
5
8
  import { UsageError } from "../core/errors";
6
9
  import { warn } from "../core/warn";
7
10
  import { parseWorkflow } from "./parser";
11
+ import workflowTemplate from "./workflow-template.md" with { type: "text" };
8
12
  const DEFAULT_WORKFLOW_TEMPLATE = renderWorkflowTemplate({
9
13
  title: "Example Workflow",
10
14
  firstStepTitle: "First Step",
@@ -134,29 +138,8 @@ export function validateWorkflowSource(target) {
134
138
  return { path: target, parse: parseWorkflow(content, { path: target }) };
135
139
  }
136
140
  function renderWorkflowTemplate(input) {
137
- return `---
138
- description: Describe what this workflow accomplishes
139
- tags:
140
- - example
141
- params:
142
- example_param: Explain this parameter
143
- ---
144
-
145
- # Workflow: ${input.title}
146
-
147
- ## Step: ${input.firstStepTitle}
148
- Step ID: ${input.firstStepId}
149
-
150
- ### Instructions
151
- Describe what to do in this step.
152
-
153
- ### Completion Criteria
154
- - Confirm the first step is complete
155
-
156
- ## Step: Second Step
157
- Step ID: second-step
158
-
159
- ### Instructions
160
- Describe what happens next.
161
- `;
141
+ return workflowTemplate
142
+ .replace("{{TITLE}}", input.title)
143
+ .replace("{{FIRST_STEP_TITLE}}", input.firstStepTitle)
144
+ .replace("{{FIRST_STEP_ID}}", input.firstStepId);
162
145
  }
@@ -1,3 +1,6 @@
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/.
1
4
  import { UsageError } from "../core/errors";
2
5
  export const WORKFLOW_STEP_STATES = [
3
6
  "completed",