akm-cli 0.9.17-alpha.8 → 0.9.17

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 (111) hide show
  1. package/CHANGELOG.md +277 -1694
  2. package/STABILITY.md +9 -8
  3. package/dist/assets/hints/cli-hints-full.md +6 -7
  4. package/dist/assets/improve-strategies/catchup.json +0 -3
  5. package/dist/assets/improve-strategies/consolidate.json +0 -1
  6. package/dist/assets/improve-strategies/default.json +1 -2
  7. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
  8. package/dist/assets/improve-strategies/quick.json +1 -2
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -2
  10. package/dist/assets/improve-strategies/thorough.json +0 -3
  11. package/dist/assets/prompts/consolidate-pair.md +20 -0
  12. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +17 -19
  13. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
  14. package/dist/assets/templates/html/health.html +3 -5
  15. package/dist/cli/retired-commands.js +1 -1
  16. package/dist/cli/unknown-flags.js +24 -1
  17. package/dist/cli.js +46 -1
  18. package/dist/commands/health/archive-usage.js +92 -0
  19. package/dist/commands/health/data-dir-usage.js +25 -13
  20. package/dist/commands/health/html-report.js +1 -4
  21. package/dist/commands/health/improve-metrics.js +25 -37
  22. package/dist/commands/health/md-report.js +1 -6
  23. package/dist/commands/health/report-view-model.js +4 -14
  24. package/dist/commands/health/windows.js +0 -1
  25. package/dist/commands/health.js +13 -0
  26. package/dist/commands/improve/consolidate/continuity-check.js +137 -0
  27. package/dist/commands/improve/consolidate/pair-pass.js +791 -0
  28. package/dist/commands/improve/consolidate.js +38 -63
  29. package/dist/commands/improve/extract-prompt.js +1 -2
  30. package/dist/commands/improve/improve-cli.js +1 -1
  31. package/dist/commands/improve/improve-strategies.js +23 -5
  32. package/dist/commands/improve/improve.js +19 -30
  33. package/dist/commands/improve/ledger.js +3 -2
  34. package/dist/commands/improve/loop-stages.js +5 -85
  35. package/dist/commands/improve/memory/memory-belief.js +3 -1
  36. package/dist/commands/improve/memory/memory-improve.js +262 -11
  37. package/dist/commands/improve/planner.js +0 -5
  38. package/dist/commands/improve/preparation.js +20 -135
  39. package/dist/commands/improve/retrieval-scope.js +19 -4
  40. package/dist/commands/improve/salience.js +1 -14
  41. package/dist/commands/improve/stage.js +0 -1
  42. package/dist/commands/lint/base-linter.js +19 -11
  43. package/dist/commands/proposal/drain.js +8 -1
  44. package/dist/commands/proposal/proposal-cli.js +16 -2
  45. package/dist/commands/proposal/proposal-types.js +7 -0
  46. package/dist/commands/proposal/proposal.js +37 -6
  47. package/dist/commands/proposal/repository.js +613 -4
  48. package/dist/commands/proposal/validators/proposals.js +9 -0
  49. package/dist/commands/read/knowledge.js +3 -2
  50. package/dist/commands/read/show.js +0 -14
  51. package/dist/commands/sources/info.js +122 -18
  52. package/dist/commands/sources/stash-cli.js +23 -3
  53. package/dist/core/bundle-rename.js +1 -7
  54. package/dist/core/config/config-schema.js +8 -1
  55. package/dist/core/config/config.js +23 -48
  56. package/dist/core/config/engine-semantics.js +0 -2
  57. package/dist/core/config/schema/improve-processes.js +17 -42
  58. package/dist/core/config/schema/index-config.js +5 -25
  59. package/dist/core/file-change.js +13 -5
  60. package/dist/core/improve-result.js +22 -6
  61. package/dist/core/improve-types.js +0 -1
  62. package/dist/core/loopback.js +7 -12
  63. package/dist/core/parse.js +13 -16
  64. package/dist/core/state/migrations.js +15 -0
  65. package/dist/core/time.js +0 -20
  66. package/dist/indexer/db/llm-cache.js +2 -2
  67. package/dist/indexer/ensure-index.js +2 -2
  68. package/dist/indexer/index-written-assets.js +2 -3
  69. package/dist/indexer/indexer.js +18 -418
  70. package/dist/indexer/passes/metadata.js +0 -19
  71. package/dist/indexer/walk/walker.js +3 -4
  72. package/dist/llm/client.js +8 -10
  73. package/dist/llm/embedders/remote.js +1 -2
  74. package/dist/llm/feature-gate.js +0 -5
  75. package/dist/output/shapes/helpers.js +20 -4
  76. package/dist/output/text/command-format.js +9 -8
  77. package/dist/output/text/proposal-format.js +47 -1
  78. package/dist/output/text/show-format.js +0 -20
  79. package/dist/scripts/akm-migrate-node.js +923 -950
  80. package/dist/scripts/akm-migrate.js +923 -950
  81. package/dist/setup/steps/connection.js +5 -6
  82. package/dist/setup/steps/platforms.js +2 -2
  83. package/dist/sources/providers/git-stash.js +83 -4
  84. package/dist/storage/repositories/improve-ledger-repository.js +48 -7
  85. package/dist/storage/repositories/index-connection.js +5 -2
  86. package/dist/storage/repositories/index-entries-repository.js +4 -7
  87. package/dist/storage/repositories/index-entry-schema.js +4 -2
  88. package/dist/storage/repositories/index-llm-cache-repository.js +7 -26
  89. package/dist/storage/repositories/index-schema.js +55 -104
  90. package/dist/storage/repositories/proposals-repository.js +61 -0
  91. package/dist/storage/repositories/salience-repository.js +1 -19
  92. package/docs/migration/README.md +1 -1
  93. package/docs/migration/release-notes/0.9.17.md +130 -41
  94. package/docs/migration/release-notes/README.md +7 -0
  95. package/docs/reference/cli.md +27 -21
  96. package/docs/reference/configuration.md +21 -12
  97. package/docs/reference/data-and-telemetry.md +0 -1
  98. package/package.json +1 -1
  99. package/schemas/akm-config.json +0 -342
  100. package/dist/assets/improve-strategies/graph-refresh.json +0 -15
  101. package/dist/assets/prompts/contradiction-judge.md +0 -33
  102. package/dist/assets/prompts/graph-extract-system.md +0 -1
  103. package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
  104. package/dist/assets/prompts/metadata-enhance-system.md +0 -1
  105. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
  106. package/dist/indexer/db/graph-db.js +0 -399
  107. package/dist/indexer/graph/graph-extraction.js +0 -809
  108. package/dist/indexer/graph/graph-related.js +0 -131
  109. package/dist/indexer/graph/graph-types.js +0 -4
  110. package/dist/llm/graph-extract.js +0 -892
  111. package/dist/llm/metadata-enhance.js +0 -95
@@ -10,19 +10,6 @@ import { z } from "zod";
10
10
  import { warnOnce } from "../../warn.js";
11
11
  import { engineName, LlmInvocationOverridesSchema, nonEmptyString, positiveInt } from "./primitives.js";
12
12
  // ── Index / per-pass ────────────────────────────────────────────────────────
13
- //
14
- // WI-9.6c: `graphExtractionIncludeTypes` is no longer validated against a
15
- // hardcoded allowlist (the prior GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED,
16
- // which included a stale `wiki` entry and was already missing `fact` from the
17
- // runtime consumer's own list — the schema-level allowlist had drifted from
18
- // reality). Accept-any until Chunk 2 sources a real type list from adapter
19
- // metadata: the field is now an array of arbitrary non-empty strings.
20
- // Runtime consumers already handle unknown/unsupported type strings
21
- // gracefully — src/indexer/graph/graph-extraction.ts's
22
- // `SUPPORTED_GRAPH_EXTRACTION_INCLUDE_TYPES` set (and `collectEligibleFiles`)
23
- // silently skips any type it doesn't recognize (no placement entry ⇒ zero
24
- // eligible files for that type; no crash). This is a permissive-direction
25
- // behavior change: configs with a previously-rejected type string now parse.
26
13
  const INDEX_PASS_RETIRED_KEYS = new Set([
27
14
  "endpoint",
28
15
  "provider",
@@ -60,12 +47,8 @@ export const IndexPassConfigSchema = z.preprocess((raw, ctx) => {
60
47
  timeoutMs: z.union([positiveInt, z.null()]).optional(),
61
48
  enabled: z.boolean().optional(),
62
49
  llm: LlmInvocationOverridesSchema.optional(),
63
- graphExtractionBatchSize: positiveInt.optional(),
64
- // Accept-any until Chunk 2 (WI-9.6c) — no longer enum-restricted.
65
- graphExtractionIncludeTypes: z.array(z.string().min(1)).nonempty().optional(),
66
50
  })
67
51
  .passthrough());
68
- const MetadataEnhanceSchema = z.object({ enabled: z.boolean().optional() }).passthrough();
69
52
  const IndexDefaultsSchema = z
70
53
  .object({
71
54
  engine: engineName.optional(),
@@ -76,16 +59,14 @@ const IndexDefaultsSchema = z
76
59
  .passthrough();
77
60
  /**
78
61
  * Index config is a union of reserved feature sections and per-pass entries.
79
- * Passthrough so per-pass entries (keyed by arbitrary pass names like `graph`,
80
- * `enrichment`) can live next to the reserved keys.
62
+ * Passthrough so per-pass entries (keyed by arbitrary pass names like
63
+ * `memory`, or a retired one like `graph`) can live next to the reserved keys.
81
64
  * The outer preprocess emits the legacy parser's actionable error messages
82
65
  * for the two most common type-shape mistakes:
83
66
  * - An array at the `index` block.
84
67
  * - A non-object at `index.<passName>`.
85
- * Inner field validation (graphExtractionIncludeTypes shape, invocation
86
- * overrides, provider-key rejection) is delegated to {@link IndexPassConfigSchema}.
87
- * `graphExtractionIncludeTypes` accepts arbitrary non-empty strings
88
- * (WI-9.6c — no hardcoded type allowlist; accept-any until Chunk 2).
68
+ * Inner field validation (invocation overrides, provider-key rejection) is
69
+ * delegated to {@link IndexPassConfigSchema}.
89
70
  */
90
71
  const IndexConfigRuntimeSchema = z.preprocess((raw, ctx) => {
91
72
  if (raw === undefined || raw === null)
@@ -93,7 +74,7 @@ const IndexConfigRuntimeSchema = z.preprocess((raw, ctx) => {
93
74
  if (Array.isArray(raw)) {
94
75
  ctx.addIssue({
95
76
  code: z.ZodIssueCode.custom,
96
- message: 'Invalid `index` config: expected an object keyed by pass name (e.g. `{ "enrichment": { "enabled": false } }`).',
77
+ message: 'Invalid `index` config: expected an object keyed by pass name (e.g. `{ "memory": { "enabled": false } }`).',
97
78
  });
98
79
  return raw;
99
80
  }
@@ -119,7 +100,6 @@ const IndexConfigRuntimeSchema = z.preprocess((raw, ctx) => {
119
100
  }, z
120
101
  .object({
121
102
  defaults: IndexDefaultsSchema.optional(),
122
- metadataEnhance: MetadataEnhanceSchema.optional(),
123
103
  })
124
104
  .catchall(IndexPassConfigSchema));
125
105
  // The runtime catchall correctly validates arbitrary pass objects, but its
@@ -4,13 +4,21 @@
4
4
  /**
5
5
  * The content of a proposal's PRIMARY change (WI-6.2 envelope read path).
6
6
  *
7
- * By construction `changes[0].after === payload.content`; consumers read
8
- * through this accessor so the single-content assumption lives in ONE place
9
- * once multi-file proposals (consolidate ops) ride the envelope. Typed
10
- * structurally (not against `Proposal`) so this module stays dependency-free.
7
+ * By construction `changes[0].after === payload.content` for a `create` or
8
+ * `update` change; consumers read through this accessor so the
9
+ * single-content assumption lives in ONE place. A `delete` primary change (a
10
+ * consolidate retire proposal, alpha.9) writes no content by definition, so
11
+ * it reads as `""` here rather than throwing — every generic proposal scan
12
+ * (dedup-by-body-hash, the triage drain's diff-size filter, the quality
13
+ * validators) can call this on ANY pending proposal, retire included,
14
+ * without special-casing the op first. Typed structurally (not against
15
+ * `Proposal`) so this module stays dependency-free.
11
16
  */
12
17
  export function proposalContent(p) {
13
- const content = p.changes[0]?.after;
18
+ const primary = p.changes[0];
19
+ if (primary?.op === "delete")
20
+ return "";
21
+ const content = primary?.after;
14
22
  if (content === undefined)
15
23
  throw new Error("Proposal primary change has no content.");
16
24
  return content;
@@ -37,6 +37,9 @@ const COMMON_FIELDS = [
37
37
  "deadUrlCoverage",
38
38
  "reflectsWithErrorContext",
39
39
  "memoryInference",
40
+ // 0.9.17-alpha.9: no longer written (the LLM entity-graph extraction that
41
+ // wrote these was retired), kept readable for the same reason as
42
+ // `evalCasesWritten` above.
40
43
  "graphExtraction",
41
44
  "memoryInferenceDurationMs",
42
45
  "graphExtractionDurationMs",
@@ -119,7 +122,7 @@ function validateConsolidationPlan(value) {
119
122
  ]));
120
123
  if (!isRecord(value.configured))
121
124
  fail("plan.consolidation.configured must be an object");
122
- requireExactFields(value.configured, new Set(["enabled", "minPoolSize", "limit", "maxChunkSize", "incrementalSince"]));
125
+ requireExactFields(value.configured, new Set(["enabled", "minPoolSize", "limit", "maxChunkSize"]));
123
126
  if (value.configured.enabled !== undefined && typeof value.configured.enabled !== "boolean") {
124
127
  fail("plan.consolidation.configured.enabled must be a boolean");
125
128
  }
@@ -128,9 +131,6 @@ function validateConsolidationPlan(value) {
128
131
  fail(`plan.consolidation.configured.${field} must be a number`);
129
132
  }
130
133
  }
131
- if (value.configured.incrementalSince !== undefined && typeof value.configured.incrementalSince !== "string") {
132
- fail("plan.consolidation.configured.incrementalSince must be a string");
133
- }
134
134
  if (!isRecord(value.effective))
135
135
  fail("plan.consolidation.effective must be an object");
136
136
  requireExactFields(value.effective, new Set(["enabled", "minPoolSize", "limit", "chunkSize"]));
@@ -200,7 +200,15 @@ function validateProcessRoutingRows(value) {
200
200
  fail("plan.processes entries must be objects");
201
201
  requireExactFields(row, new Set(["process", "enabled", "engine", "model", "engineKind", "notices", "unavailable", "eligibleRefs"]));
202
202
  if (typeof row.process !== "string" ||
203
- !(canonicalNames.includes(row.process) || row.process === "triage.judgment")) {
203
+ !(canonicalNames.includes(row.process) ||
204
+ row.process === "triage.judgment" ||
205
+ // 0.9.17-alpha.9: "graphExtraction" is no longer a process a fresh
206
+ // plan can route (the LLM entity-graph extraction it named was
207
+ // retired), but every pre-alpha.9 run recorded one here — kept
208
+ // readable, same as `stageNames`' "graph-extraction" above and
209
+ // `graphExtraction` in COMMON_FIELDS, so a historical run still
210
+ // decodes for `akm health` instead of failing this row outright.
211
+ row.process === "graphExtraction")) {
204
212
  fail("plan.processes.process is invalid");
205
213
  }
206
214
  if (seen.has(row.process))
@@ -385,10 +393,18 @@ function validateImprovePlan(value, dryRun, plannedRefNames) {
385
393
  if (value.limits.totalCeiling !== undefined && value.effectiveRefs.length > value.limits.totalCeiling) {
386
394
  fail("plan.effectiveRefs cannot exceed plan.limits.totalCeiling");
387
395
  }
388
- validateProcessRoutingRows(value.processes);
396
+ // #947 added plan.processes (2026-09-09T09:03:19Z); every run recorded
397
+ // before it legitimately stored `plan` with no `processes` key at all —
398
+ // validate the rows only when present, same as `proactive` below, so a
399
+ // pre-#947 envelope still decodes (AGENTS.md "Reading persisted data").
400
+ if (value.processes !== undefined)
401
+ validateProcessRoutingRows(value.processes);
389
402
  if (value.proactive !== undefined)
390
403
  validateProactivePlan(value.proactive);
391
404
  validateConsolidationPlan(value.consolidation);
405
+ // "graph-extraction" is no longer a stage a fresh plan can name — retired
406
+ // 0.9.17-alpha.9 — but stays here so a historical envelope that captured a
407
+ // dry-run plan before the retirement still decodes.
392
408
  const stageNames = new Set(["consolidation", "extract", "graph-extraction", "memory-inference"]);
393
409
  if (!Array.isArray(value.stages))
394
410
  fail("plan.stages must be an array");
@@ -32,7 +32,6 @@ export function classifyImproveAction(mode) {
32
32
  case "reflect":
33
33
  case "distill":
34
34
  case "memory-inference":
35
- case "graph-extraction":
36
35
  return "accepted";
37
36
  case "reflect-cooldown":
38
37
  case "reflect-skipped":
@@ -20,10 +20,11 @@
20
20
  *
21
21
  * The ONE local-vs-remote answer for concurrency defaults — the workflow
22
22
  * engine's frozen engine concurrency (`workflows/concurrency-policy.ts`) and
23
- * the indexer's LLM pool (`indexer/indexer.ts` `getDefaultLlmConcurrency`)
24
- * both classify through here. NOT shared with the website snapshot fetcher's
25
- * SSRF policy (`sources/snapshot-fetchers/host-guard.ts`), which deliberately
26
- * draws different lines for a different threat model.
23
+ * the embedding pool (`resolveEmbeddingConcurrency`,
24
+ * `src/llm/embedders/remote.ts`) both classify through here. NOT shared with
25
+ * the website snapshot fetcher's SSRF policy
26
+ * (`sources/snapshot-fetchers/host-guard.ts`), which deliberately draws
27
+ * different lines for a different threat model.
27
28
  *
28
29
  * @module core/loopback
29
30
  */
@@ -92,14 +93,8 @@ export function isLoopbackEndpoint(endpoint) {
92
93
  * endpoint: 1 for a loopback endpoint (a local model server serves one
93
94
  * inference at a time; concurrent requests thrash it — reload thrash, HTTP
94
95
  * 500 "Model reloaded"), 2 for a remote one (enough to overlap request
95
- * latency without hammering a rate-limited API). A leaf helper so both
96
- * callers — the indexer's LLM enrichment pool (`getDefaultLlmConcurrency`,
97
- * `src/indexer/indexer.ts`) and the embedding pool
98
- * (`resolveEmbeddingConcurrency`, `src/llm/embedders/remote.ts`) — share one
99
- * definition instead of mirroring it; `src/llm/embedders/remote.ts` cannot
100
- * import `getDefaultLlmConcurrency` directly (`src/indexer/indexer.ts`
101
- * already depends on this module transitively through
102
- * materialize-embeddings.ts).
96
+ * latency without hammering a rate-limited API). Used by the embedding pool
97
+ * (`resolveEmbeddingConcurrency`, `src/llm/embedders/remote.ts`).
103
98
  */
104
99
  export function defaultConcurrencyForEndpoint(endpoint) {
105
100
  return isLoopbackEndpoint(endpoint) ? 1 : 2;
@@ -101,24 +101,24 @@ export function parseJsonResponse(raw) {
101
101
  * balanced `{ }` or `[ ]` structure in the text and attempts to parse that
102
102
  * substring. Returns `undefined` if no valid JSON structure is found.
103
103
  *
104
- * Shape preference is controlled by {@link ParseEmbeddedJsonOptions.expect}:
105
- * - `"any"` (default): non-array results are preferred — a `{…}` object found
106
- * first is returned immediately; arrays (`[…]`) are a fallback.
107
- * - `"array"`: only top-level arrays are returned. The direct parse is
108
- * accepted only if it is an array, and the scanner returns the first
109
- * balanced `[…]` while skipping `{…}` openers entirely.
104
+ * Object-preferring: a `{…}` object found first is returned immediately;
105
+ * a top-level array (`[…]`) is only a fallback, returned if no object is
106
+ * found. Preserves the historical behaviour every current caller (all in
107
+ * `src/`) relies on.
108
+ *
109
+ * An array-preferring mode (`expect: "array"`, for callers that specifically
110
+ * expect a JSON array) existed here for batched LLM entity-graph extraction
111
+ * (#635) and was removed with it in 0.9.17-alpha.9 — every remaining caller
112
+ * calls this with no options, so it was dead weight, not a live choice.
110
113
  */
111
- export function parseEmbeddedJsonResponse(raw, options) {
112
- const expectArray = options?.expect === "array";
114
+ export function parseEmbeddedJsonResponse(raw) {
113
115
  const direct = parseJsonResponse(raw);
114
- if (direct !== undefined && (!expectArray || Array.isArray(direct)))
116
+ if (direct !== undefined)
115
117
  return direct;
116
118
  const text = escapeJsonStringControls(stripCodeFences(stripThinkBlocks(raw)));
117
119
  let arrayFallback;
118
120
  // Scan only *top-level* balanced structures: once a `{…}`/`[…]` is matched we
119
121
  // jump `start` past its closing bracket rather than re-scanning its interior.
120
- // This keeps array mode from salvaging an array *nested inside* a leading
121
- // object (e.g. the `entities` array of a bare `{entities,relations}` object).
122
122
  for (let start = 0; start < text.length; start++) {
123
123
  const opener = text[start];
124
124
  if (opener !== "{" && opener !== "[")
@@ -161,13 +161,10 @@ export function parseEmbeddedJsonResponse(raw, options) {
161
161
  try {
162
162
  const parsed = JSON.parse(text.slice(start, end + 1));
163
163
  if (Array.isArray(parsed)) {
164
- // First valid array wins in array mode; in "any" mode it is the
165
- // fallback returned only if no object is found.
166
- if (expectArray)
167
- return parsed;
164
+ // Fallback, returned only if no object is found.
168
165
  arrayFallback ??= parsed;
169
166
  }
170
- else if (!expectArray) {
167
+ else {
171
168
  return parsed;
172
169
  }
173
170
  // Skip past this balanced structure so we don't descend into it.
@@ -47,6 +47,7 @@ export const STATE_MIGRATION_SAFETY_BY_ID = Object.freeze({
47
47
  "026-proposals-strip-legacy-fragment-refs": "data-preserving-rebuild",
48
48
  "027-extract-sessions-seen-harness-rename": "data-preserving-rebuild",
49
49
  "028-improve-ledger": "historical-destructive",
50
+ "029-improve-ledger-content-hash": "additive",
50
51
  });
51
52
  export const STATE_MIGRATIONS = [
52
53
  // ── Migration 001 — initial schema ──────────────────────────────────────────
@@ -1284,6 +1285,20 @@ export const STATE_MIGRATIONS = [
1284
1285
  DROP TABLE IF EXISTS canary_queries;
1285
1286
  `,
1286
1287
  },
1288
+ // ── Migration 029 — improve_ledger.content_hash ───────────────────────────
1289
+ //
1290
+ // The consolidate pair pass (alpha.9) selects initiators by content change,
1291
+ // not a time window: no row means eligible, a row whose content_hash
1292
+ // differs from the asset's current body means eligible, otherwise not
1293
+ // (src/commands/improve/consolidate/pair-pass.ts, selectInitiators). Every
1294
+ // other source still uses next_eligible_at; this column is additional, not
1295
+ // a replacement.
1296
+ {
1297
+ id: "029-improve-ledger-content-hash",
1298
+ up: `
1299
+ ALTER TABLE improve_ledger ADD COLUMN content_hash TEXT DEFAULT NULL;
1300
+ `,
1301
+ },
1287
1302
  ];
1288
1303
  assertMigrationRegistry(STATE_MIGRATIONS);
1289
1304
  function assertStateMigrationSafetyRegistry() {
package/dist/core/time.js CHANGED
@@ -95,23 +95,3 @@ export function parseSinceToIso(since) {
95
95
  }
96
96
  return parsed.toISOString();
97
97
  }
98
- /**
99
- * LENIENT variant for config-driven since-values that may be a duration
100
- * shorthand (e.g. `"30m"`, `"24h"`, `"7d"` per {@link DURATION_UNITS}):
101
- * a matching duration resolves to the ISO timestamp of `now - duration`;
102
- * anything else is returned UNCHANGED (assumed to already be an ISO
103
- * timestamp) — this function never throws.
104
- *
105
- * Deliberately distinct from {@link parseSinceToIso}, which rejects
106
- * unparseable input with a UsageError: the identity fallback here is
107
- * behavior consumers rely on (consolidate's incremental narrowing compares
108
- * the returned string lexicographically against file-mtime ISO strings, so
109
- * garbage input silently selects nothing rather than failing the run —
110
- * pinned by tests/commands/goldens-duration-flags.test.ts).
111
- */
112
- export function parseSinceToIsoLenient(since) {
113
- const ms = parseDuration(since, DURATION_UNITS);
114
- if (ms === null)
115
- return since;
116
- return new Date(Date.now() - ms).toISOString();
117
- }
@@ -27,10 +27,10 @@ import { computeBodyHash, getLlmCacheEntry, upsertLlmCacheEntry, } from "../../s
27
27
  * already hashed the body (e.g. to reuse it elsewhere)
28
28
  * should pass this to avoid the redundant work.
29
29
  * @param cacheVariant - Namespace token for the cache row so different passes'
30
- * rows do not collide (e.g. memory-inference vs graph).
30
+ * rows do not collide (e.g. memory-inference vs metadata-enhance).
31
31
  * @param hooks - Optional event sink for telemetry (see {@link WithLlmCacheHooks}).
32
32
  */
33
- export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate, precomputedHash, cacheVariant = "", hooks) {
33
+ export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate, precomputedHash, cacheVariant, hooks) {
34
34
  const bodyHash = precomputedHash ?? computeBodyHash(body);
35
35
  if (!reEnrich) {
36
36
  const cacheHit = bestEffort(() => {
@@ -115,8 +115,8 @@ function hasNewerIndexableFiles(stashDir, builtAt, indexedPaths, indexedHashes)
115
115
  continue;
116
116
  // Newer than the last full build — only stale if its current content
117
117
  // actually differs from what is indexed. No stored hash means the row
118
- // predates content-hash tracking (or a hash-less enrichment pass), so
119
- // fall back to the conservative mtime-stale answer.
118
+ // predates content-hash tracking, so fall back to the conservative
119
+ // mtime-stale answer.
120
120
  const indexedHash = indexedHashes.get(file);
121
121
  if (indexedHash === undefined)
122
122
  return true;
@@ -15,9 +15,8 @@
15
15
  * This is NOT a general reindex. It upserts exactly the files the caller just
16
16
  * wrote: frontmatter/metadata via the shared matcher pipeline, the canonical
17
17
  * row with its transactionally owned FTS projection, and vectors for changed
18
- * entry IDs when semantic search is enabled. Index-time LLM passes, graph
19
- * extraction, `builtAt`, and the per-dir walk cache remain full-index
20
- * responsibilities.
18
+ * entry IDs when semantic search is enabled. `builtAt` and the per-dir walk
19
+ * cache remain full-index responsibilities.
21
20
  */
22
21
  import fs from "node:fs";
23
22
  import path from "node:path";