akm-cli 0.9.17-alpha.6 → 0.9.17-alpha.7

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 (38) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/STABILITY.md +2 -2
  3. package/dist/akm +7 -7
  4. package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
  5. package/dist/commands/improve/consolidate.js +11 -0
  6. package/dist/commands/improve/improve-cli.js +27 -7
  7. package/dist/commands/improve/ledger.js +7 -3
  8. package/dist/commands/improve/preparation.js +40 -10
  9. package/dist/commands/improve/reflect.js +46 -22
  10. package/dist/commands/improve/retrieval-gate.js +127 -0
  11. package/dist/commands/improve/retrieval-scope.js +77 -0
  12. package/dist/commands/read/curate.js +1 -17
  13. package/dist/commands/tasks/tasks-cli.js +10 -12
  14. package/dist/commands/tasks/tasks.js +57 -56
  15. package/dist/commands/tasks/validate.js +27 -46
  16. package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
  17. package/dist/core/improve-result.js +4 -1
  18. package/dist/core/non-task-input.js +20 -0
  19. package/dist/core/paths.js +0 -4
  20. package/dist/indexer/indexer.js +1 -3
  21. package/dist/indexer/usage/usage-events.js +34 -0
  22. package/dist/scripts/akm-migrate-node.js +5822 -5833
  23. package/dist/scripts/akm-migrate.js +6301 -6312
  24. package/dist/storage/repositories/proposals-repository.js +4 -0
  25. package/dist/tasks/backends/cron.js +80 -43
  26. package/dist/tasks/backends/launchd.js +28 -15
  27. package/dist/tasks/backends/schtasks.js +25 -10
  28. package/dist/tasks/run/load-task.js +1 -1
  29. package/dist/tasks/scheduler-binding.js +4 -2
  30. package/dist/tasks/scheduler-invocation.js +127 -235
  31. package/dist/tasks/scheduler-sync.js +13 -8
  32. package/dist/tasks/source/parse-task-source.js +22 -126
  33. package/dist/tasks/source/task-to-v3.js +1 -55
  34. package/dist/tasks/source/task-to-v4.js +1 -13
  35. package/docs/migration/v0.9.1-to-v0.9.2.md +7 -3
  36. package/docs/reference/cli.md +4 -3
  37. package/docs/reference/tasks.md +58 -40
  38. package/package.json +1 -1
@@ -17,11 +17,9 @@
17
17
  * ── validate (spec §6 task validation column) ──
18
18
  *
19
19
  * Validation enters the canonical task source parser (`parseTaskSource`,
20
- * task source v4 native as of P4 — a `version: 3` or `version: 2` document
21
- * is auto-read through the in-memory migration shim in
22
- * `parse-task-source.ts`; only a version the shim's deterministic planners
23
- * cannot convert, or any other unsupported number, fails closed with
24
- * `TASK_SCHEMA_VERSION_UNSUPPORTED`). That parser owns the closed key sets,
20
+ * which reads only task source v4 — a `version: 2` or `version: 3`
21
+ * document fails closed with `TASK_SCHEMA_VERSION_UNSUPPORTED`, naming
22
+ * `akm migrate apply`, which converts it). That parser owns the closed key sets,
25
23
  * the executable-selector XOR, hostile YAML policy, the `akm/command`
26
24
  * builtin, bounds, and physical `working-directory` containment. The
27
25
  * adapter only translates a parser failure into the format-family
@@ -33,7 +31,8 @@
33
31
  */
34
32
  import fs from "node:fs";
35
33
  import path from "node:path";
36
- import { parseTaskSource } from "../../../tasks/source/parse-task-source.js";
34
+ import { readBoundedTaskSourceYaml } from "../../../tasks/source/bounded-document.js";
35
+ import { parseTaskSource, peekTaskSourceVersion } from "../../../tasks/source/parse-task-source.js";
37
36
  import { TASK_EXTENSION, TASK_NEAR_MISS_EXTENSION, taskExtensionDetail, taskSourceErrorDetail, } from "../../../tasks/source-v3.js";
38
37
  import { toPosix } from "../../common.js";
39
38
  import { hashContent } from "./shared.js";
@@ -125,8 +124,11 @@ export const akmTaskAdapter = {
125
124
  return ["."];
126
125
  },
127
126
  /**
128
- * Install-time probe (§1.2): a root holding a top-level, valid task-v3
129
- * `.yml` file. The full parser keeps this disjoint from unrelated YAML and
127
+ * Install-time probe (§1.2): a root holding a top-level, valid task source
128
+ * v4 `.yml` file, or a task v2/v3 file that `akm migrate apply` has yet to
129
+ * convert (the runtime refuses those, so without this the migrator and
130
+ * `akm task sync` would not find them in a bundle whose adapter config does
131
+ * not record). The full parser keeps this disjoint from unrelated YAML and
130
132
  * prevents probe semantics from drifting from validation semantics.
131
133
  */
132
134
  looksLikeRoot(root) {
@@ -152,9 +154,28 @@ export const akmTaskAdapter = {
152
154
  return true;
153
155
  }
154
156
  catch {
157
+ if (isLegacyTaskDocument(raw, entry.name))
158
+ return true;
155
159
  // Continue probing the remaining top-level .yml files.
156
160
  }
157
161
  }
158
162
  return false;
159
163
  },
160
164
  };
165
+ /** The executable keys each retired task schema version required one of. */
166
+ const LEGACY_TASK_TARGET_KEYS = {
167
+ 2: ["workflow", "prompt", "command"],
168
+ 3: ["uses", "run"],
169
+ };
170
+ /** A task v2/v3 document: its schema version, plus a target key that version used. */
171
+ function isLegacyTaskDocument(yaml, filePath) {
172
+ let root;
173
+ try {
174
+ root = readBoundedTaskSourceYaml({ yaml, filePath }, { sourceLabel: "task source" }).root;
175
+ }
176
+ catch {
177
+ return false;
178
+ }
179
+ const keys = LEGACY_TASK_TARGET_KEYS[peekTaskSourceVersion(root) ?? 0];
180
+ return keys !== undefined && keys.some((key) => Object.hasOwn(root, key));
181
+ }
@@ -306,6 +306,8 @@ function validateImprovePlan(value, dryRun, plannedRefNames) {
306
306
  fail("plan.limits.totalCeiling must equal plan.limits.effective + plan.limits.additiveReplayAllowance");
307
307
  }
308
308
  const gateNames = new Set(["profile", "cleanup", "validation", "signal", "disk", "limit"]);
309
+ // Plans stored before 0.9.17-alpha.7 (#986) have no retrieval gate.
310
+ const optionalGateNames = new Set(["retrieval"]);
309
311
  if (!Array.isArray(value.gates))
310
312
  fail("plan.gates must be an array");
311
313
  const gateRemovedByName = new Map();
@@ -313,8 +315,9 @@ function validateImprovePlan(value, dryRun, plannedRefNames) {
313
315
  if (!isRecord(gate))
314
316
  fail("plan.gates entries must be objects");
315
317
  requireExactFields(gate, new Set(["name", "removed", "reason"]));
316
- if (typeof gate.name !== "string" || !gateNames.has(gate.name))
318
+ if (typeof gate.name !== "string" || !(gateNames.has(gate.name) || optionalGateNames.has(gate.name))) {
317
319
  fail("plan.gates.name is invalid");
320
+ }
318
321
  requireCount(gate, "removed", "plan.gates entry");
319
322
  if (typeof gate.reason !== "string")
320
323
  fail("plan.gates.reason must be a string");
@@ -0,0 +1,20 @@
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
+ /** The line of `src/assets/stash-skeleton/README.md` that reaches curate verbatim as a query. */
5
+ const STASH_README_LINE = "This is an **AKM stash** — a structured knowledge repository that stores reusable";
6
+ /**
7
+ * What the (trimmed) curate input is when it is not a task, else undefined.
8
+ * Harness and tool envelopes (`<task-notification>…`, `<system-reminder>…`,
9
+ * `<cross-session-message …>…`) start with a tag and close one, and the stash
10
+ * README line arrives verbatim; on the retrieval suite neither shape occurs in
11
+ * a real query. Length is not a signal: prompts over 2,000 characters found
12
+ * relevant assets at about the rate of shorter long prompts.
13
+ */
14
+ export function nonTaskInput(query) {
15
+ if (query.startsWith("<") && query.includes("</"))
16
+ return "a harness or tool envelope";
17
+ if (query === STASH_README_LINE)
18
+ return "the akm stash README boilerplate";
19
+ return undefined;
20
+ }
@@ -249,10 +249,6 @@ export function getIndexRebuildLockPath() {
249
249
  export function getStateDbPathInDataDir() {
250
250
  return path.join(getDataDir(), "state.db");
251
251
  }
252
- /** Content-addressed scheduler runtime descriptors. */
253
- export function getTaskContextDir(env = process.env) {
254
- return path.join(getDataDir(env), "tasks", "context");
255
- }
256
252
  /** Path to the akm.lock file in $DATA. */
257
253
  export function getLockfilePath() {
258
254
  return path.join(getDataDir(), "akm.lock");
@@ -35,7 +35,7 @@ import { generateEmbeddingsForDb } from "./materialize-embeddings.js";
35
35
  import { canUseIncrementalSkip, computeDirFingerprint, getCachedDirState, getDirIndexState, inferZeroRowReason, } from "./passes/dir-staleness.js";
36
36
  import { isEnrichmentComplete, isWorkflowSkipWarning, withFileSize, } from "./passes/metadata.js";
37
37
  import { drainDirDocuments } from "./scan/drain-dir.js";
38
- import { purgeOldUsageEvents } from "./usage/usage-events.js";
38
+ import { purgeOldUsageEvents, USAGE_EVENT_RETENTION_DAYS } from "./usage/usage-events.js";
39
39
  import { walkStashFlatWithStatus } from "./walk/walker.js";
40
40
  function collectLoweringNotices(target, notices) {
41
41
  const keys = new Set(target.map((notice) => JSON.stringify(notice)));
@@ -1624,8 +1624,6 @@ export async function lookup(ref) {
1624
1624
  return lookupBundleRef({ bundle: ref.origin, conceptId: conceptIdFromTypeName(ref.type, ref.name) });
1625
1625
  }
1626
1626
  // ── Utility score recomputation ──────────────────────────────────────────────
1627
- /** Retention window for usage events: events older than this are purged. */
1628
- const USAGE_EVENT_RETENTION_DAYS = 90;
1629
1627
  /**
1630
1628
  * Recompute utility scores for all entries based on usage_events data.
1631
1629
  *
@@ -91,6 +91,40 @@ export function countUsageEventsByType(db, eventType) {
91
91
  return db.prepare("SELECT COUNT(*) AS cnt FROM usage_events WHERE event_type = ?").get(eventType)
92
92
  .cnt;
93
93
  }
94
+ /**
95
+ * Durable refs a user-attributed `search`, `curate` or `show` returned, or a
96
+ * user `feedback` named, at or after `sinceIso`. Machine traffic (`improve`,
97
+ * `task`, `audit`, `unknown`) is not demand, as in {@link countFeedbackSignals}.
98
+ */
99
+ export function listUsedEntryRefs(db, sinceIso) {
100
+ const rows = db
101
+ .prepare(`SELECT DISTINCT entry_ref FROM usage_events
102
+ WHERE event_type IN ('search', 'curate', 'show', 'feedback')
103
+ AND source = 'user'
104
+ AND entry_ref IS NOT NULL
105
+ AND julianday(created_at) >= julianday(?)`)
106
+ .all(sinceIso);
107
+ return rows.map((row) => row.entry_ref);
108
+ }
109
+ /**
110
+ * The distinct query texts of user `search` and `curate` events that returned
111
+ * `conceptId` (any bundle prefix), most recent first.
112
+ */
113
+ export function listRetrievalQueries(db, conceptId) {
114
+ const rows = db
115
+ .prepare(`SELECT query, MAX(created_at) AS last_at FROM usage_events
116
+ WHERE event_type IN ('search', 'curate')
117
+ AND source = 'user'
118
+ AND query IS NOT NULL AND trim(query) != ''
119
+ AND instr(entry_ref, '//') > 0
120
+ AND substr(entry_ref, instr(entry_ref, '//') + 2) = ?
121
+ GROUP BY query
122
+ ORDER BY last_at DESC`)
123
+ .all(conceptId);
124
+ return rows.map((row) => row.query);
125
+ }
126
+ /** Usage events older than this many days are purged on every `akm index`. */
127
+ export const USAGE_EVENT_RETENTION_DAYS = 90;
94
128
  /**
95
129
  * Delete usage events older than the given number of days.
96
130
  */