akm-cli 0.9.17-alpha.8 → 0.9.17-alpha.9

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 (104) hide show
  1. package/CHANGELOG.md +334 -0
  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/commands/health/archive-usage.js +98 -0
  17. package/dist/commands/health/data-dir-usage.js +25 -13
  18. package/dist/commands/health/html-report.js +1 -4
  19. package/dist/commands/health/improve-metrics.js +0 -25
  20. package/dist/commands/health/md-report.js +1 -6
  21. package/dist/commands/health/report-view-model.js +4 -14
  22. package/dist/commands/health/windows.js +0 -1
  23. package/dist/commands/health.js +13 -0
  24. package/dist/commands/improve/consolidate/continuity-check.js +137 -0
  25. package/dist/commands/improve/consolidate/pair-pass.js +791 -0
  26. package/dist/commands/improve/consolidate.js +38 -63
  27. package/dist/commands/improve/extract-prompt.js +1 -2
  28. package/dist/commands/improve/improve-cli.js +1 -1
  29. package/dist/commands/improve/improve-strategies.js +23 -5
  30. package/dist/commands/improve/improve.js +19 -30
  31. package/dist/commands/improve/ledger.js +3 -2
  32. package/dist/commands/improve/loop-stages.js +5 -85
  33. package/dist/commands/improve/memory/memory-belief.js +3 -1
  34. package/dist/commands/improve/memory/memory-improve.js +269 -11
  35. package/dist/commands/improve/planner.js +0 -5
  36. package/dist/commands/improve/preparation.js +20 -135
  37. package/dist/commands/improve/retrieval-scope.js +19 -4
  38. package/dist/commands/improve/salience.js +1 -14
  39. package/dist/commands/improve/stage.js +0 -1
  40. package/dist/commands/lint/base-linter.js +19 -11
  41. package/dist/commands/proposal/drain.js +8 -1
  42. package/dist/commands/proposal/proposal-cli.js +16 -2
  43. package/dist/commands/proposal/proposal-types.js +7 -0
  44. package/dist/commands/proposal/proposal.js +37 -6
  45. package/dist/commands/proposal/repository.js +613 -4
  46. package/dist/commands/proposal/validators/proposals.js +9 -0
  47. package/dist/commands/read/knowledge.js +3 -2
  48. package/dist/commands/read/show.js +0 -14
  49. package/dist/commands/sources/stash-cli.js +2 -2
  50. package/dist/core/bundle-rename.js +1 -7
  51. package/dist/core/config/config-schema.js +8 -1
  52. package/dist/core/config/config.js +23 -48
  53. package/dist/core/config/engine-semantics.js +0 -2
  54. package/dist/core/config/schema/improve-processes.js +17 -42
  55. package/dist/core/config/schema/index-config.js +5 -25
  56. package/dist/core/file-change.js +13 -5
  57. package/dist/core/improve-result.js +16 -5
  58. package/dist/core/improve-types.js +0 -1
  59. package/dist/core/loopback.js +7 -12
  60. package/dist/core/parse.js +13 -16
  61. package/dist/core/state/migrations.js +15 -0
  62. package/dist/core/time.js +0 -20
  63. package/dist/indexer/db/llm-cache.js +2 -2
  64. package/dist/indexer/ensure-index.js +2 -2
  65. package/dist/indexer/index-written-assets.js +2 -3
  66. package/dist/indexer/indexer.js +18 -418
  67. package/dist/indexer/passes/metadata.js +0 -19
  68. package/dist/indexer/walk/walker.js +3 -4
  69. package/dist/llm/client.js +8 -10
  70. package/dist/llm/embedders/remote.js +1 -2
  71. package/dist/llm/feature-gate.js +0 -5
  72. package/dist/output/shapes/helpers.js +20 -4
  73. package/dist/output/text/command-format.js +0 -8
  74. package/dist/output/text/proposal-format.js +47 -1
  75. package/dist/output/text/show-format.js +0 -20
  76. package/dist/scripts/akm-migrate-node.js +917 -950
  77. package/dist/scripts/akm-migrate.js +917 -950
  78. package/dist/setup/steps/connection.js +5 -6
  79. package/dist/setup/steps/platforms.js +2 -2
  80. package/dist/sources/providers/git-stash.js +55 -4
  81. package/dist/storage/repositories/improve-ledger-repository.js +48 -7
  82. package/dist/storage/repositories/index-entries-repository.js +4 -7
  83. package/dist/storage/repositories/index-entry-schema.js +4 -2
  84. package/dist/storage/repositories/index-llm-cache-repository.js +7 -26
  85. package/dist/storage/repositories/index-schema.js +55 -104
  86. package/dist/storage/repositories/proposals-repository.js +61 -0
  87. package/dist/storage/repositories/salience-repository.js +1 -19
  88. package/docs/reference/cli.md +16 -19
  89. package/docs/reference/configuration.md +21 -12
  90. package/docs/reference/data-and-telemetry.md +0 -1
  91. package/package.json +1 -1
  92. package/schemas/akm-config.json +0 -342
  93. package/dist/assets/improve-strategies/graph-refresh.json +0 -15
  94. package/dist/assets/prompts/contradiction-judge.md +0 -33
  95. package/dist/assets/prompts/graph-extract-system.md +0 -1
  96. package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
  97. package/dist/assets/prompts/metadata-enhance-system.md +0 -1
  98. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
  99. package/dist/indexer/db/graph-db.js +0 -399
  100. package/dist/indexer/graph/graph-extraction.js +0 -809
  101. package/dist/indexer/graph/graph-related.js +0 -131
  102. package/dist/indexer/graph/graph-types.js +0 -4
  103. package/dist/llm/graph-extract.js +0 -892
  104. package/dist/llm/metadata-enhance.js +0 -95
@@ -4,12 +4,21 @@
4
4
  /** Proposal validation and the one content repair applied before promotion. */
5
5
  import { repairTruncatedDescription } from "../../../core/text-truncation.js";
6
6
  import { splitFrontmatter } from "../../improve/reflect-noise.js";
7
+ import { isRetireProposal } from "../proposal-types.js";
7
8
  import { runProposalValidators } from "./proposal-validators.js";
8
9
  /**
9
10
  * Validate a proposal before promotion: it must parse and carry a body, and a
10
11
  * type with a canonical validator runs it.
12
+ *
13
+ * A retire proposal (alpha.9 consolidate pair pass) writes no new content —
14
+ * it deletes an asset that already passed validation when IT was created —
15
+ * so the content-quality validators (built for new/updated bodies: a
16
+ * description, lesson shape, reflect size ratio, …) do not apply and are
17
+ * skipped entirely rather than misreading the empty body as a defect.
11
18
  */
12
19
  export function validateProposal(proposal) {
20
+ if (isRetireProposal(proposal))
21
+ return { ok: true, findings: [] };
13
22
  return runProposalValidators(proposal);
14
23
  }
15
24
  /**
@@ -612,8 +612,9 @@ export async function writeMarkdownAsset(options) {
612
612
  // no longer trigger reindexes, so keeping the index current is the writer's
613
613
  // job. Demoted files reindex under their own containing root (usually the
614
614
  // write target itself; the working stash when writing to a --target) so
615
- // `--belief current` filtering and the beliefState ranking demotion take
616
- // effect without waiting for the next full index.
615
+ // `--belief current` filtering takes effect without waiting for the next
616
+ // full index. (alpha.4 removed belief weights from ranking; `--belief` is
617
+ // the only reader of `beliefState` today.)
617
618
  const demotedInTargetRoot = demotedByRoot.get(source.path) ?? [];
618
619
  demotedByRoot.delete(source.path);
619
620
  await indexWrittenAssets(source.path, [result.path, ...demotedInTargetRoot], { bundleId: resolved.ref.origin });
@@ -33,7 +33,6 @@ import { appendEvent } from "../../core/events.js";
33
33
  import { SCRIPT_EXTENSIONS } from "../../core/recognition-util.js";
34
34
  import { presentationFor } from "../../core/type-presentation.js";
35
35
  import { warn, warnOnce } from "../../core/warn.js";
36
- import { listRelatedPathsForFile } from "../../indexer/graph/graph-related.js";
37
36
  import { lookupBundleRef, lookupBundleRefWithResolution } from "../../indexer/indexer.js";
38
37
  import { projectMarkdownFragmentContent } from "../../indexer/passes/metadata.js";
39
38
  import { ensurePrimaryIndexForRead, resolveReadSources } from "../../indexer/read-preflight.js";
@@ -384,19 +383,6 @@ export async function showLocal(input) {
384
383
  origin: source?.registryId ?? null,
385
384
  editable,
386
385
  ...(!editable ? { editHint: buildEditHint(canonicalRef) } : {}),
387
- related: (() => {
388
- try {
389
- return withIndexDb((db) => {
390
- const related = listRelatedPathsForFile(sourceStashDir, assetPath, 5, db);
391
- return { total: related.length, hits: related };
392
- });
393
- }
394
- catch (err) {
395
- rethrowIfTestIsolationError(err);
396
- rethrowIfDataDirUnreadable(err);
397
- return { total: 0, hits: [] };
398
- }
399
- })(),
400
386
  ...showLinks(indexedEntry.itemRef, displayDefaultBundle),
401
387
  };
402
388
  const activeRun = await getActiveWorkflowRun(getCurrentWorkflowScopeKey());
@@ -83,10 +83,10 @@ export const indexCommand = defineCommand({
83
83
  async run({ args }) {
84
84
  await runWithJsonErrors(async () => {
85
85
  if (getHyphenatedBoolean(args, "enrich") || getParsedInvocation().getFlagValue("--enrich") !== undefined) {
86
- throw new UsageError("`akm index --enrich` has been removed. Plain `akm index` now performs metadata enrichment by default.");
86
+ throw new UsageError("`akm index --enrich` has been removed. Metadata enrichment during indexing is retired; it never runs.");
87
87
  }
88
88
  if (getHyphenatedBoolean(args, "re-enrich") || getParsedInvocation().getFlagValue("--re-enrich") !== undefined) {
89
- throw new UsageError("`akm index --re-enrich` has been removed. Re-enrichment of index-time LLM passes is not exposed in this slice.");
89
+ throw new UsageError("`akm index --re-enrich` has been removed. Metadata enrichment during indexing is retired; there is nothing to re-enrich.");
90
90
  }
91
91
  // #956: opt-in, non-blocking rebuild lock — never gates a human-typed
92
92
  // `akm index` (it only warns and contends), but a scheduled/opportunistic
@@ -30,7 +30,6 @@ import fs from "node:fs";
30
30
  import { readLockfile, renameLockEntry } from "../integrations/lockfile.js";
31
31
  import { closeDatabase, openIndexDatabase, openReadonlyExistingDatabase, } from "../storage/repositories/index-connection.js";
32
32
  import { getFilePathsByBundle, renameEntriesBundleId } from "../storage/repositories/index-entries-repository.js";
33
- import { renameLlmCacheAssetRefs } from "../storage/repositories/index-llm-cache-repository.js";
34
33
  import { countProposalsForBundleRename, renameProposalsBundleRef } from "../storage/repositories/proposals-repository.js";
35
34
  import { countTaskHistoryTargetRefs, renameTaskHistoryTargetRefs, } from "../storage/repositories/task-history-repository.js";
36
35
  import { selectBackend } from "../tasks/backends/index.js";
@@ -243,18 +242,13 @@ export async function renameBundle(oldId, newId, options = {}, deps) {
243
242
  // e.g. a plain filesystem bundle).
244
243
  await renameLockEntry(oldId, newId);
245
244
  // Index: bundle_id/item_ref on every entry row (see index-entries-repository's
246
- // renameEntriesBundleId docstring for why no FTS/vector rebuild is needed),
247
- // and the metadata-enrichment LLM cache keyed by the same canonical
248
- // item_ref — in the SAME write, so a rename can never land between the two
249
- // and leave the cache stranded under the old prefix (the next `akm index`'s
250
- // clearStaleCacheEntries would then delete it, forcing a full re-enrich).
245
+ // renameEntriesBundleId docstring for why no FTS/vector rebuild is needed).
251
246
  const readIndexDb = openReadonlyExistingDatabase(getDbPath());
252
247
  if (readIndexDb) {
253
248
  closeDatabase(readIndexDb);
254
249
  const writeIndexDb = openIndexDatabase(getDbPath());
255
250
  try {
256
251
  renameEntriesBundleId(writeIndexDb, oldId, newId);
257
- renameLlmCacheAssetRefs(writeIndexDb, oldId, newId);
258
252
  }
259
253
  finally {
260
254
  closeDatabase(writeIndexDb);
@@ -60,7 +60,7 @@ export { EngineConfigSchema, EnginesSchema, LlmConnectionConfigSchema, LlmProfil
60
60
  export { ExperimentalConfigSchema } from "./schema/experimental.js";
61
61
  export { FEEDBACK_FAILURE_MODES, FeedbackConfigSchema } from "./schema/feedback.js";
62
62
  export { ImproveConfigSchema } from "./schema/improve.js";
63
- export { ConsolidateProcessConfigSchema, DistillProcessConfigSchema, ExtractProcessConfigSchema, GraphExtractionProcessConfigSchema, ImproveProcessConfigSchema, ImproveProfileConfigSchema, MemoryInferenceProcessConfigSchema, ProactiveMaintenanceProcessConfigSchema, ReflectProcessConfigSchema, TriageProcessConfigSchema, ValidationProcessConfigSchema, } from "./schema/improve-processes.js";
63
+ export { ConsolidateProcessConfigSchema, DistillProcessConfigSchema, ExtractProcessConfigSchema, ImproveProcessConfigSchema, ImproveProfileConfigSchema, MemoryInferenceProcessConfigSchema, ProactiveMaintenanceProcessConfigSchema, ReflectProcessConfigSchema, TriageProcessConfigSchema, ValidationProcessConfigSchema, } from "./schema/improve-processes.js";
64
64
  export { IndexConfigSchema, IndexPassConfigSchema } from "./schema/index-config.js";
65
65
  export { OutputConfigSchema } from "./schema/output.js";
66
66
  export { CURRENT_CONFIG_VERSION, LlmInvocationOverridesSchema } from "./schema/primitives.js";
@@ -240,6 +240,13 @@ export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) =>
240
240
  }
241
241
  const defaultStrategy = config.defaults?.improveStrategy;
242
242
  if (defaultStrategy &&
243
+ // graph-refresh is exempt from this eager, config-load-time check: unlike
244
+ // a plain typo, it is a specific, known retirement, and refusing it here
245
+ // would fail every command's config load (including `akm migrate apply`,
246
+ // the one that fixes this). `resolveImproveStrategy`
247
+ // (src/commands/improve/improve-strategies.ts) refuses it lazily, at
248
+ // improve-invocation time, with the same message either way.
249
+ defaultStrategy !== "graph-refresh" &&
243
250
  !BUILTIN_IMPROVE_STRATEGY_NAMES.includes(defaultStrategy) &&
244
251
  !config.improve?.strategies?.[defaultStrategy]) {
245
252
  ctx.addIssue({
@@ -27,36 +27,6 @@ export { VALID_HARNESS_IDS } from "./config-types.js";
27
27
  // Canonical taxonomy lives in the schema/validator layer; re-exported here so
28
28
  // existing `../core/config/config` import sites keep working.
29
29
  export { FEEDBACK_FAILURE_MODES } from "./config-schema.js";
30
- /**
31
- * Default value for {@link IndexPassConfig.graphExtractionBatchSize}. Chosen
32
- * empirically: 4 amortises the per-call HTTP overhead 4× while keeping the
33
- * combined prompt size well under common 8K/16K context windows (each body is
34
- * sliced to ~500 chars in the graph-extract prompt builder).
35
- */
36
- const DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE = 4;
37
- /**
38
- * Approximate character budget per asset body inside a batched
39
- * graph-extraction prompt — used by {@link resolveBatchSize} to derive a
40
- * context-window ceiling when `llm.contextLength` is configured. This accounts
41
- * for the actual `MAX_BODY_CHARS` (500) in graph-extract.ts plus the system
42
- * prompt, user prompt wrapper, and expected JSON response overhead.
43
- */
44
- const GRAPH_EXTRACTION_CHARS_PER_BODY = 1500;
45
- /**
46
- * Clamp a configured batch size against the model's known context window.
47
- *
48
- * `configured` defaults to {@link DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE} when
49
- * `undefined`. When `contextLength` is provided, the result is the smaller of
50
- * `configured` and `floor(contextLength / GRAPH_EXTRACTION_CHARS_PER_BODY)`,
51
- * with a floor of 1 so the batched path always processes at least one body.
52
- */
53
- export function resolveBatchSize(configured, contextLength) {
54
- const base = configured && configured > 0 ? configured : DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE;
55
- if (!contextLength || contextLength <= 0)
56
- return base;
57
- const ceiling = Math.max(1, Math.floor(contextLength / GRAPH_EXTRACTION_CHARS_PER_BODY));
58
- return Math.max(1, Math.min(base, ceiling));
59
- }
60
30
  // ── Defaults ────────────────────────────────────────────────────────────────
61
31
  export const DEFAULT_CONFIG = {
62
32
  configVersion: "0.9.0",
@@ -210,6 +180,18 @@ function buildEffectiveConfig(liftedLocalRaw, sourcePath) {
210
180
  assertUniquePhysicalBundleRoots(finalResult.data, sourcePath);
211
181
  return finalResult.data;
212
182
  }
183
+ /**
184
+ * Retired `index.<passName>` keys that still validate against the generic
185
+ * per-pass catchall schema (`IndexPassConfigSchema`), so `resolveSchemaAt`
186
+ * below never returns `undefined` for them and the walk would otherwise
187
+ * treat them as a live, ordinary pass. Named here so the same three
188
+ * guarantees apply as any other retired config key (AGENTS.md "Reading
189
+ * persisted data"): a config setting one keeps loading, is named once by
190
+ * the unknown-key warning, and is dropped only by `akm migrate apply`.
191
+ * Both were retired in 0.9.17-alpha.9: `index.graph` (the LLM entity-graph
192
+ * extraction pass) and `index.metadataEnhance` (LLM metadata enrichment).
193
+ */
194
+ const RETIRED_CATCHALL_KEY_PATHS = new Set(["index.graph", "index.metadataEnhance"]);
213
195
  /**
214
196
  * Every dotted key in `raw` the schema does not know, at any depth (arrays
215
197
  * are not descended). Unknown keys are never an error: they are a typo, or a
@@ -221,7 +203,7 @@ export function unknownConfigKeyPaths(root, node = root, prefix = []) {
221
203
  const found = [];
222
204
  for (const key of Object.keys(node).sort()) {
223
205
  const keyPath = [...prefix, key];
224
- if (resolveSchemaAt(keyPath, root) === undefined) {
206
+ if (RETIRED_CATCHALL_KEY_PATHS.has(keyPath.join(".")) || resolveSchemaAt(keyPath, root) === undefined) {
225
207
  found.push(keyPath);
226
208
  continue;
227
209
  }
@@ -695,6 +677,16 @@ export function normalizeConfigFile(configPath, options) {
695
677
  const body = withSchedulerOnDisk(configWriteBody(localRaw, current, next), next);
696
678
  for (const keyPath of unknownConfigKeyPaths(body))
697
679
  deleteConfigPath(body, keyPath);
680
+ // `improve.strategies["graph-refresh"]` is schema-valid (any name is a
681
+ // legal custom-strategy key), so it never reaches the unknown-key sweep
682
+ // above. It can only be a leftover override of the deleted graph-refresh
683
+ // built-in (0.9.17-alpha.9) — resolveImproveStrategy now refuses that
684
+ // name unconditionally, so the override can never apply again. Drop it
685
+ // the same way any other retired key is dropped: only by `akm migrate
686
+ // apply`, never by an ordinary write.
687
+ const strategies = body.improve?.strategies;
688
+ if (strategies && Object.hasOwn(strategies, "graph-refresh"))
689
+ delete strategies["graph-refresh"];
698
690
  const keys = [...new Set([...Object.keys(raw), ...Object.keys(body)])]
699
691
  .filter((key) => JSON.stringify(raw[key]) !== JSON.stringify(body[key]))
700
692
  .sort();
@@ -928,23 +920,6 @@ export function resolveSecret(value, resolveFromStore) {
928
920
  return resolved ?? "";
929
921
  });
930
922
  }
931
- /**
932
- * Read a per-pass {@link IndexPassConfig} entry from {@link IndexConfig},
933
- * filtering out the reserved feature-section keys so callers don't mistake
934
- * `metadataEnhance` for a pass.
935
- */
936
- /** Reserved well-known keys on IndexConfig that are NOT per-pass entries. */
937
- const INDEX_RESERVED_KEYS = new Set(["metadataEnhance"]);
938
- export function getIndexPassConfig(config, passName) {
939
- if (!config)
940
- return undefined;
941
- if (INDEX_RESERVED_KEYS.has(passName))
942
- return undefined;
943
- const entry = config[passName];
944
- if (!entry || typeof entry !== "object")
945
- return undefined;
946
- return entry;
947
- }
948
923
  // Re-export source runtime helpers — implementation lives in config-sources.ts.
949
924
  export { bundleComponentConfig, bundleContentRoot, bundleContentRoots, bundleEntryToSourceEntry, bundleKeyForContentRoot, bundlePhysicalContentRoot, bundleSourceId, bundlesToSourceEntries, installedSourceDescriptor, isBundleEnabled, parseSourceSpec, primaryBundlePath, resolveActiveConfiguredSources, resolveConfiguredSources, } from "./config-sources.js";
950
925
  /**
@@ -6,7 +6,6 @@ export const BUILTIN_IMPROVE_STRATEGY_NAMES = [
6
6
  "default",
7
7
  "quick",
8
8
  "thorough",
9
- "graph-refresh",
10
9
  "consolidate",
11
10
  "catchup",
12
11
  "reflect-distill",
@@ -18,7 +17,6 @@ export const IMPROVE_PROCESS_ENGINE_CAPABILITIES = {
18
17
  distill: "llm",
19
18
  consolidate: "llm",
20
19
  memoryInference: "llm",
21
- graphExtraction: "llm",
22
20
  extract: "llm",
23
21
  validation: "llm",
24
22
  triage: "runner",
@@ -10,13 +10,14 @@ import { IMPROVE_PROCESS_ENGINE_CAPABILITIES } from "../engine-semantics.js";
10
10
  import { engineName, LlmInvocationOverridesSchema, nonEmptyString, positiveInt } from "./primitives.js";
11
11
  // ── Improve profile / process ──────────────────────────────────────────────
12
12
  //
13
- // WI-9.6 (§4.2/§10.2): each of the 9 improve processes (reflect, distill,
14
- // consolidate, memoryInference, graphExtraction, extract, validation, triage,
15
- // proactiveMaintenance) gets its OWN schema below — a shared base (engine,
16
- // model, llm, enabled, timeoutMs) extended with only the fields meaningful on
17
- // that process — replacing the prior single ImproveProcessConfigSchema reused
18
- // via `.optional()` for all 9 keys (which accepted, and silently ignored, any
19
- // field on any process). Field→process assignment is derived from each
13
+ // WI-9.6 (§4.2/§10.2): each of the improve processes (reflect, distill,
14
+ // consolidate, memoryInference, extract, validation, triage,
15
+ // proactiveMaintenance — graphExtraction had one too, until the LLM
16
+ // entity-graph it ran was retired in 0.9.17-alpha.9) gets its OWN schema
17
+ // below — a shared base (engine, model, llm, enabled, timeoutMs) extended
18
+ // with only the fields meaningful on that process — replacing the prior
19
+ // single ImproveProcessConfigSchema reused via `.optional()` for all of them
20
+ // (which accepted, and silently ignored, any field on any process). Field→process assignment is derived from each
20
21
  // field's original "only meaningful on X" doc comment, cross-checked against
21
22
  // its actual runtime consumers and the built-in strategy assets
22
23
  // (src/assets/improve-strategies/*.json).
@@ -164,12 +165,6 @@ const CONSOLIDATE_PROCESS_FIELDS = {
164
165
  // entirely (emits `pool_below_min_size`). 0 disables the guard. Default 500.
165
166
  minPoolSize: z.number().int().min(0).optional(),
166
167
  maxChunkSize: z.number().int().min(1).max(50).optional(),
167
- // Narrow candidate pool to memories modified within this duration window
168
- // plus their graph neighbours. Absent = full-pool sweep.
169
- incrementalSince: z.string().optional(),
170
- // Graph neighbours per changed memory during incremental consolidation.
171
- // Default 5. Only meaningful with incrementalSince.
172
- neighborsPerChanged: z.number().int().min(1).optional(),
173
168
  // Fallback p90 wall-clock time per consolidation chunk in seconds, used for
174
169
  // cold-start budget estimation when no telemetry history exists. The actual
175
170
  // p90 is derived from observed run durations once sufficient history
@@ -182,29 +177,6 @@ const MEMORY_INFERENCE_PROCESS_FIELDS = {
182
177
  minPendingCount: z.number().int().min(0).optional(),
183
178
  cls: clsField,
184
179
  };
185
- /**
186
- * GraphExtraction process fields: one strategy's graph extraction scope and
187
- * batching. `includeTypes` and `batchSize` override `index.graph`'s
188
- * `graphExtractionIncludeTypes` and `graphExtractionBatchSize`; unset, the
189
- * pass reads those.
190
- */
191
- const GRAPH_EXTRACTION_PROCESS_FIELDS = {
192
- // #624 P2: when set, rank eligible files by utility_scores DESC and process
193
- // only the top-N per run (incremental high-signal-first sweep). Unset =
194
- // process all eligible (current behavior).
195
- topN: positiveInt.optional(),
196
- includeTypes: z.array(z.string().min(1)).min(1).optional(),
197
- batchSize: positiveInt.optional(),
198
- // Full-corpus scan. When true, graph extraction runs on ALL stash files
199
- // instead of only files touched by actionable refs in the current run.
200
- // Used by the `graph-refresh` built-in profile / a scheduled weekly task.
201
- fullScan: z.boolean().optional(),
202
- // R12b + R20: cap on chunks processed per asset. A body chunked beyond this
203
- // is truncated to the first N chunks instead of paying for unbounded
204
- // per-asset LLM calls; the coverage loss is recorded as truncatedChunks.
205
- // Absent = default 8 (src/llm/graph-extract.ts DEFAULT_MAX_CHUNKS_PER_ASSET).
206
- maxChunksPerAsset: positiveInt.optional(),
207
- };
208
180
  const EXTRACT_PROCESS_FIELDS = {
209
181
  defaultSince: z.string().min(1).optional(),
210
182
  maxTotalChars: positiveInt.optional(),
@@ -264,7 +236,6 @@ export const ImproveProcessConfigSchema = z
264
236
  ...DISTILL_PROCESS_FIELDS,
265
237
  ...CONSOLIDATE_PROCESS_FIELDS,
266
238
  ...MEMORY_INFERENCE_PROCESS_FIELDS,
267
- ...GRAPH_EXTRACTION_PROCESS_FIELDS,
268
239
  ...EXTRACT_PROCESS_FIELDS,
269
240
  ...TRIAGE_PROCESS_FIELDS,
270
241
  ...PROACTIVE_MAINTENANCE_PROCESS_FIELDS,
@@ -288,10 +259,6 @@ export const ConsolidateProcessConfigSchema = z
288
259
  export const MemoryInferenceProcessConfigSchema = z
289
260
  .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...MEMORY_INFERENCE_PROCESS_FIELDS })
290
261
  .passthrough();
291
- /** `processes.graphExtraction` — narrow per-process schema (WI-9.6). */
292
- export const GraphExtractionProcessConfigSchema = z
293
- .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...GRAPH_EXTRACTION_PROCESS_FIELDS })
294
- .passthrough();
295
262
  /** `processes.extract` — narrow per-process schema (WI-9.6). */
296
263
  export const ExtractProcessConfigSchema = z
297
264
  .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...EXTRACT_PROCESS_FIELDS })
@@ -306,13 +273,20 @@ export const TriageProcessConfigSchema = z
306
273
  export const ProactiveMaintenanceProcessConfigSchema = z
307
274
  .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...PROACTIVE_MAINTENANCE_PROCESS_FIELDS })
308
275
  .passthrough();
276
+ /**
277
+ * Process names that once had a dedicated schema and are now gone. Setting
278
+ * one, even with `enabled: true`, is tolerated rather than rejected by the
279
+ * "unknown enabled process" check below — an old config must keep loading
280
+ * (AGENTS.md "Reading persisted data"). `graphExtraction`: the LLM
281
+ * entity-graph extraction it ran was retired in 0.9.17-alpha.9.
282
+ */
283
+ const RETIRED_PROCESS_NAMES = new Set(["graphExtraction"]);
309
284
  const ImproveProfileProcessesSchema = z
310
285
  .object({
311
286
  reflect: ReflectProcessConfigSchema.optional(),
312
287
  distill: DistillProcessConfigSchema.optional(),
313
288
  consolidate: ConsolidateProcessConfigSchema.optional(),
314
289
  memoryInference: MemoryInferenceProcessConfigSchema.optional(),
315
- graphExtraction: GraphExtractionProcessConfigSchema.optional(),
316
290
  extract: ExtractProcessConfigSchema.optional(),
317
291
  validation: ValidationProcessConfigSchema.optional(),
318
292
  triage: TriageProcessConfigSchema.optional(),
@@ -335,6 +309,7 @@ const ImproveProfileProcessesSchema = z
335
309
  }
336
310
  for (const [name, process] of Object.entries(val)) {
337
311
  if (!(name in IMPROVE_PROCESS_ENGINE_CAPABILITIES) &&
312
+ !RETIRED_PROCESS_NAMES.has(name) &&
338
313
  process !== null &&
339
314
  typeof process === "object" &&
340
315
  process.enabled === true) {
@@ -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))
@@ -389,6 +397,9 @@ function validateImprovePlan(value, dryRun, plannedRefNames) {
389
397
  if (value.proactive !== undefined)
390
398
  validateProactivePlan(value.proactive);
391
399
  validateConsolidationPlan(value.consolidation);
400
+ // "graph-extraction" is no longer a stage a fresh plan can name — retired
401
+ // 0.9.17-alpha.9 — but stays here so a historical envelope that captured a
402
+ // dry-run plan before the retirement still decodes.
392
403
  const stageNames = new Set(["consolidation", "extract", "graph-extraction", "memory-inference"]);
393
404
  if (!Array.isArray(value.stages))
394
405
  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.