@evo-dev/core 0.0.1-alpha.2 → 0.0.1-alpha.20

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 (79) hide show
  1. package/assets/skills/coding/knowledge-distillation/SKILL.md +5 -3
  2. package/assets/team/agents/code-reviewer.md +48 -0
  3. package/assets/team/agents/docs-maintainer.md +51 -0
  4. package/assets/team/agents/implementation-engineer.md +51 -0
  5. package/assets/team/agents/product-scope-analyst.md +58 -0
  6. package/assets/team/agents/release-engineer.md +55 -0
  7. package/assets/team/agents/security-boundary-reviewer.md +50 -0
  8. package/assets/team/agents/solution-architect.md +51 -0
  9. package/assets/team/agents/verification-engineer.md +51 -0
  10. package/assets/team/team.md +102 -0
  11. package/dist/assets/index.js +5 -5
  12. package/dist/config/index.js +793 -241
  13. package/dist/index.js +20840 -12908
  14. package/dist/plugins/index.js +13 -13
  15. package/package.json +1 -1
  16. package/src/agents/index.ts +1 -265
  17. package/src/code-agent-traces/index.ts +11 -12
  18. package/src/config/index.ts +2 -0
  19. package/src/config/settings.ts +116 -7
  20. package/src/config/store.ts +1 -1
  21. package/src/daemon/index.ts +1 -41
  22. package/src/evolution/candidates/index.ts +730 -0
  23. package/src/evolution/control/index.ts +20 -0
  24. package/src/evolution/evidence/analysis.ts +533 -0
  25. package/src/evolution/evidence/index.ts +3 -0
  26. package/src/evolution/evidence/session-memory/analysis.ts +287 -0
  27. package/src/evolution/evidence/session-memory/constants.ts +9 -0
  28. package/src/evolution/evidence/session-memory/index.ts +9 -0
  29. package/src/evolution/evidence/session-memory/paths.ts +29 -0
  30. package/src/evolution/evidence/session-memory/policy.ts +39 -0
  31. package/src/evolution/evidence/session-memory/retention.ts +643 -0
  32. package/src/evolution/evidence/session-memory/segment.ts +216 -0
  33. package/src/evolution/evidence/session-memory/semantic-packet.ts +408 -0
  34. package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
  35. package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
  36. package/src/evolution/evidence/session-memory/storage.ts +744 -0
  37. package/src/evolution/evidence/session-memory/types.ts +296 -0
  38. package/src/evolution/evidence/session-memory/updater.ts +199 -0
  39. package/src/evolution/formatters.ts +169 -0
  40. package/src/evolution/imports/apply.ts +435 -0
  41. package/src/evolution/imports/diff.ts +472 -0
  42. package/src/evolution/imports/index.ts +7 -0
  43. package/src/evolution/imports/materialize.ts +640 -0
  44. package/src/evolution/imports/paths.ts +129 -0
  45. package/src/evolution/imports/stage.ts +414 -0
  46. package/src/evolution/imports/storage.ts +952 -0
  47. package/src/evolution/imports/types.ts +226 -0
  48. package/src/evolution/index.ts +19 -2827
  49. package/src/evolution/knowledge/change-store.ts +558 -0
  50. package/src/evolution/knowledge/changes.ts +459 -0
  51. package/src/evolution/knowledge/freshness.ts +69 -0
  52. package/src/{knowledge → evolution/knowledge}/index.ts +1532 -206
  53. package/src/evolution/knowledge/review.ts +446 -0
  54. package/src/evolution/knowledge/support.ts +135 -0
  55. package/src/evolution/paths.ts +44 -0
  56. package/src/evolution/processor/distillation.ts +518 -0
  57. package/src/evolution/processor/index.ts +3 -0
  58. package/src/evolution/processor/process.ts +594 -0
  59. package/src/{learning → evolution/review}/index.ts +10 -14
  60. package/src/evolution/schema.ts +639 -0
  61. package/src/evolution/shared.ts +1053 -0
  62. package/src/evolution/triggers/classification.ts +102 -0
  63. package/src/evolution/triggers/index.ts +295 -0
  64. package/src/hooks/index.ts +281 -197
  65. package/src/index.ts +15 -4
  66. package/src/projects/index.ts +934 -0
  67. package/src/runtime-logs/index.ts +100 -13
  68. package/src/team/index.ts +582 -3
  69. package/src/utils/errors.ts +13 -0
  70. package/src/utils/fs.ts +40 -0
  71. package/src/utils/hash.ts +9 -0
  72. package/src/utils/ids.ts +12 -0
  73. package/src/utils/index.ts +7 -0
  74. package/src/utils/parsing.ts +11 -0
  75. package/src/utils/text.ts +18 -0
  76. package/src/utils/time.ts +5 -0
  77. package/src/workflow/index.ts +3 -21
  78. package/src/project/index.ts +0 -507
  79. package/src/task/index.ts +0 -840
@@ -2,13 +2,54 @@ import { createHash } from "node:crypto";
2
2
  import { existsSync, readFileSync, readdirSync } from "node:fs";
3
3
  import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
4
4
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
5
- import { resolveEvoDevPaths } from "../config/paths.ts";
5
+ import { resolveEvoDevPaths } from "../../config/paths.ts";
6
+ import { readRuntimeInjectionSettings } from "../../config/settings.ts";
6
7
  import {
7
- type EvolutionDistillationBatch,
8
- type EvolutionEvosCase,
9
- type EvolutionKnowledgeRecord,
10
- listEvolutionEvosCases,
11
- } from "../evolution/index.ts";
8
+ canonicalizeProjectKey,
9
+ listEquivalentProjectKeys,
10
+ listProjectAliases,
11
+ } from "../../projects/index.ts";
12
+ import { createStableId, normalizeTimestamp } from "../../utils/index.ts";
13
+ import { listEvolutionEvosCases } from "../candidates/index.ts";
14
+ import {
15
+ detectSessionMemorySensitivity,
16
+ redactSessionMemoryCredentialText,
17
+ redactSessionMemoryCredentials,
18
+ } from "../evidence/session-memory/sensitivity.ts";
19
+ import type {
20
+ EvolutionDistillationBatch,
21
+ EvolutionEvosCase,
22
+ EvolutionKnowledgeRecord,
23
+ } from "../schema.ts";
24
+ import { REVIEW_STATES } from "../shared.ts";
25
+ import {
26
+ type OkfKnowledgeChangeCandidateV1,
27
+ createOkfKnowledgeChangeCandidate,
28
+ readOkfKnowledgeChangeCandidate,
29
+ writeOkfKnowledgeChangeCandidate,
30
+ } from "./change-store.ts";
31
+ import {
32
+ type OkfKnowledgeChangeClassificationResult,
33
+ classifyOkfKnowledgeCandidate,
34
+ createOkfKnowledgeRevision,
35
+ createOkfKnowledgeRuntimeProjectionFromConcept,
36
+ } from "./changes.ts";
37
+ import {
38
+ type OkfKnowledgeFreshness,
39
+ type OkfKnowledgeVerificationSnapshotV1,
40
+ createOkfKnowledgeVerificationSnapshot,
41
+ deriveOkfKnowledgeFreshness,
42
+ } from "./freshness.ts";
43
+ import { type OkfKnowledgeSupportRef, isDirectKnowledgeSupport } from "./support.ts";
44
+
45
+ export type {
46
+ OkfKnowledgeDelivery,
47
+ OkfKnowledgeRuntimeEligibility,
48
+ OkfKnowledgeSourceStatus,
49
+ OkfKnowledgeSupportKind,
50
+ OkfKnowledgeSupportRef,
51
+ } from "./support.ts";
52
+ export { resolveOkfKnowledgeRuntimeEligibility } from "./support.ts";
12
53
 
13
54
  export type OkfKnowledgeDecision =
14
55
  | "auto-accept"
@@ -16,7 +57,8 @@ export type OkfKnowledgeDecision =
16
57
  | "create"
17
58
  | "update"
18
59
  | "skip"
19
- | "needs-human";
60
+ | "needs-human"
61
+ | "revoke";
20
62
  export type OkfKnowledgeTargetStore = "okf" | "repo-asset-proposal" | "evo-eval-set" | "none";
21
63
  export type OkfKnowledgeReviewState =
22
64
  | "auto-accepted"
@@ -64,7 +106,7 @@ export interface OkfKnowledgePrivacyCheck {
64
106
  sourceDumpsStored: false;
65
107
  rawCommandOutputStored: false;
66
108
  secretsStored: false;
67
- internalLinksStored: false;
109
+ internalLinksStored: boolean;
68
110
  }
69
111
 
70
112
  export interface OkfKnowledgeOverlayUpdate {
@@ -128,6 +170,7 @@ export interface OkfKnowledgePlanCandidate {
128
170
  scores: OkfKnowledgeCandidateScores;
129
171
  decisionReason: string;
130
172
  evidenceRefs: string[];
173
+ supportRef?: OkfKnowledgeSupportRef;
131
174
  reviewState: OkfKnowledgeReviewState;
132
175
  verificationNotApplicableReason?: string;
133
176
  evalSetRefs?: string[];
@@ -209,6 +252,7 @@ export interface OkfKnowledgeActivationResult {
209
252
  overlayPaths: string[];
210
253
  skippedCandidates: string[];
211
254
  needsHumanCandidates: string[];
255
+ pendingChangeIds: string[];
212
256
  indexPaths: string[];
213
257
  derivedIndexPaths: string[];
214
258
  logPaths: string[];
@@ -220,6 +264,23 @@ export interface OkfKnowledgeLifecycleMutationResult {
220
264
  indexPaths: string[];
221
265
  }
222
266
 
267
+ export interface LegacyGeneratedKnowledgeCleanupPreview {
268
+ generatedAt: string;
269
+ conceptPaths: string[];
270
+ evosCasePaths: string[];
271
+ overlayPathsToQuarantine: string[];
272
+ overlayPathsToRewrite: string[];
273
+ totalArtifacts: number;
274
+ }
275
+
276
+ export interface LegacyGeneratedKnowledgeCleanupResult
277
+ extends LegacyGeneratedKnowledgeCleanupPreview {
278
+ quarantineDir: string | null;
279
+ manifestPath: string | null;
280
+ okfIndexPaths: string[];
281
+ evosIndexPath: string | null;
282
+ }
283
+
223
284
  export interface OkfKnowledgeConcept {
224
285
  id: string;
225
286
  path: string;
@@ -229,6 +290,8 @@ export interface OkfKnowledgeConcept {
229
290
  reviewState: OkfKnowledgeReviewState;
230
291
  lifecycle: OkfKnowledgeLifecycle;
231
292
  lifecyclePersisted: boolean;
293
+ verificationSnapshot: OkfKnowledgeVerificationSnapshotV1 | null;
294
+ supportRef: OkfKnowledgeSupportRef | null;
232
295
  title: string;
233
296
  description: string;
234
297
  tags: string[];
@@ -265,6 +328,8 @@ export interface OkfKnowledgeContextItem {
265
328
  score?: number;
266
329
  title: string;
267
330
  summary: string;
331
+ freshness: OkfKnowledgeFreshness;
332
+ runtimeExcerpt: string | null;
268
333
  matchReasons: string[];
269
334
  }
270
335
 
@@ -308,6 +373,20 @@ export interface ScopedKnowledgeContextPackItem {
308
373
  section: OkfKnowledgeContextSection;
309
374
  rank: number;
310
375
  title: string;
376
+ freshness: OkfKnowledgeFreshness;
377
+ loader:
378
+ | {
379
+ kind: "knowledge-show";
380
+ conceptId: string;
381
+ }
382
+ | {
383
+ kind: "none";
384
+ conceptId: null;
385
+ };
386
+ delivery: {
387
+ mode: "inline" | "reference";
388
+ excerpt: string | null;
389
+ };
311
390
  matchReasons: string[];
312
391
  }
313
392
 
@@ -324,13 +403,13 @@ export interface ScopedKnowledgeContextPack {
324
403
  id: string;
325
404
  okfIndexRevision: string;
326
405
  scope: ScopedKnowledgeContextPackScope;
327
- queryText?: string;
406
+ queryHash?: string;
328
407
  items: ScopedKnowledgeContextPackItem[];
329
408
  warnings: string[];
330
409
  rawContentStored: false;
331
410
  }
332
411
 
333
- export type ContextInjectionTrigger = "team-startup" | "hook-safe-point";
412
+ export type ContextInjectionTrigger = "team-startup" | "hook-safe-point" | "ordinary-session";
334
413
 
335
414
  export interface ContextInjectionReceipt {
336
415
  version: 1;
@@ -338,9 +417,17 @@ export interface ContextInjectionReceipt {
338
417
  okfIndexRevision: string;
339
418
  scope: ScopedKnowledgeContextPackScope;
340
419
  itemIds: string[];
420
+ queryHash: string | null;
341
421
  injectedAt: string;
342
422
  hookEventId: string | null;
343
423
  trigger: ContextInjectionTrigger;
424
+ outcome: null | {
425
+ status: "verified";
426
+ eventId: string;
427
+ observedAt: string;
428
+ summaryHash: string;
429
+ rawContentStored: false;
430
+ };
344
431
  rawContentStored: false;
345
432
  }
346
433
 
@@ -353,6 +440,7 @@ interface OkfPaths {
353
440
 
354
441
  interface KnowledgeQueryScope {
355
442
  projectKey?: string;
443
+ projectKeys?: string[];
356
444
  roleId?: string;
357
445
  workflowId?: string;
358
446
  paths: string[];
@@ -386,6 +474,7 @@ export interface OkfActiveKnowledgeIndex {
386
474
  roleTags: string[];
387
475
  workflowTags: string[];
388
476
  targetPath: string;
477
+ semanticFingerprint: string;
389
478
  }>;
390
479
  }
391
480
 
@@ -401,12 +490,6 @@ export interface OkfKnowledgePlanContext {
401
490
  }
402
491
 
403
492
  const RESERVED_OKF_FILENAMES = new Set(["index.md", "log.md"]);
404
- const FORBIDDEN_OKF_TEXT =
405
- /\b(secret|token|password|passwd|api[_-]?key|apikey|credential|credentials|secret[\s_-]*token(?:[\s_-]*repro)?|raw[\s_-]*(?:log|logs|output|source|prompt)(?:[\s_-]*repro)?|shell[\s_-]*history|command[\s_-]*history)\b/i;
406
- const FORBIDDEN_OKF_FIELD =
407
- /^\s*(?:commandHistory|commandOutput|credential|credentials|env|memoryBody|password|privateKey|prompt|promptBody|promptText|rawCommand|rawCommandOutput|rawLog|rawLogs|rawOutput|rawPayload|rawPrompt|secret|secretValue|sourceBody|sourceCode|sourceContent|sourceText|stderr|stdout|token|transcript|transcriptBody|transcriptText)\s*:/im;
408
- const PRIVATE_OR_INTERNAL_URL =
409
- /https?:\/\/\S*(?:internal|private|corp|localhost|127\.0\.0\.1)\S*/i;
410
493
  const HUMAN_REVIEW_DOMAIN_PATTERN =
411
494
  /(^|[^a-z0-9])(?:security|privacy|release|architecture|cross[\s_-]*(?:repo|repository)|model[\s_-]*reflection|reflection[\s_-]*based)([^a-z0-9]|$)/i;
412
495
  const OKF_PRIVACY_FLAG_KEYS = [
@@ -418,7 +501,11 @@ const OKF_PRIVACY_FLAG_KEYS = [
418
501
  "internalLinksStored",
419
502
  ] as const;
420
503
  const OKF_QUERY_PRIVACY_FLAG_KEYS = [
421
- ...OKF_PRIVACY_FLAG_KEYS,
504
+ "rawPromptsStored",
505
+ "rawLogsStored",
506
+ "sourceDumpsStored",
507
+ "rawCommandOutputStored",
508
+ "secretsStored",
422
509
  "rawOutputStored",
423
510
  "sourceContentStored",
424
511
  ] as const;
@@ -449,6 +536,7 @@ const OKF_DECISIONS: readonly OkfKnowledgeDecision[] = [
449
536
  "update",
450
537
  "skip",
451
538
  "needs-human",
539
+ "revoke",
452
540
  ];
453
541
  const ACTIVE_OKF_DECISIONS: readonly OkfKnowledgeDecision[] = ["auto-accept", "create", "update"];
454
542
  const BEHAVIOR_CHANGE_KINDS = new Set([
@@ -579,6 +667,69 @@ export function createOkfKnowledgePlanFromDistillationBatch(
579
667
  };
580
668
  }
581
669
 
670
+ /**
671
+ * Re-scores a curator/model-produced plan against the local evidence and active OKF index. Model
672
+ * scores and active decisions are advisory only; this function owns the automatic-write decision.
673
+ */
674
+ export function finalizeCuratedKnowledgePlan(input: {
675
+ homeDir: string;
676
+ plan: OkfKnowledgePlan;
677
+ evidenceWindow: EvolutionDistillationBatch["evidenceWindow"];
678
+ }): OkfKnowledgePlan {
679
+ if (
680
+ input.plan.projectKey !== input.evidenceWindow.projectKey ||
681
+ input.plan.runId !== input.evidenceWindow.runId ||
682
+ input.plan.evidenceWindowId !== input.evidenceWindow.id
683
+ ) {
684
+ throw new Error("Curated knowledge plan identity does not match its evidence window.");
685
+ }
686
+ const batch: EvolutionDistillationBatch = {
687
+ schemaVersion: 1,
688
+ id: createStableId("batch", [input.evidenceWindow.id, input.plan.createdAt, "curation"]),
689
+ projectKey: input.evidenceWindow.projectKey,
690
+ runId: input.evidenceWindow.runId,
691
+ createdAt: input.plan.createdAt,
692
+ evidenceWindow: input.evidenceWindow,
693
+ knowledgeRecords: [],
694
+ evosCases: [],
695
+ repoProposals: [],
696
+ warnings: [],
697
+ };
698
+ const context: OkfKnowledgePlanContext = {
699
+ batch,
700
+ activeIndex: loadActiveOkfKnowledgeIndex({ homeDir: input.homeDir }),
701
+ };
702
+ const candidates = input.plan.candidates.map((candidate) => {
703
+ if (candidate.decision === "no_write" || candidate.decision === "skip") return candidate;
704
+ if (candidate.decision === "needs-human" || candidate.decision === "revoke") return candidate;
705
+ return decideOkfKnowledgeCandidate(
706
+ {
707
+ ...candidate,
708
+ decision: "create",
709
+ reviewState: "auto-stored/unreviewed",
710
+ },
711
+ context,
712
+ );
713
+ });
714
+ const conflicts = candidates.flatMap((candidate) =>
715
+ /conflict|duplicate/i.test(candidate.decisionReason)
716
+ ? [{ candidateId: candidate.id, reason: candidate.decisionReason }]
717
+ : [],
718
+ );
719
+ const finalized = {
720
+ ...input.plan,
721
+ summary:
722
+ candidates.filter((candidate) => ACTIVE_OKF_DECISIONS.includes(candidate.decision)).length ===
723
+ 0
724
+ ? "No reusable OKF knowledge candidate passed local activation gates."
725
+ : input.plan.summary,
726
+ candidates,
727
+ conflicts,
728
+ };
729
+ assertOkfKnowledgePlanContract(finalized, { homeDir: input.homeDir });
730
+ return finalized;
731
+ }
732
+
582
733
  function attachGeneratedEvalSetsForBehaviorChanges(
583
734
  candidates: OkfKnowledgePlanCandidate[],
584
735
  batch: EvolutionDistillationBatch,
@@ -673,13 +824,15 @@ export function parseKnowledgeDistillationOutput(
673
824
  conflicts: normalizeConflicts(output.conflicts ?? []),
674
825
  privacyCheck: normalizePrivacyCheck(output.privacyCheck, "privacyCheck"),
675
826
  };
827
+ plan.privacyCheck.internalLinksStored =
828
+ detectSessionMemorySensitivity(plan).reasons.includes("url");
676
829
  assertOkfKnowledgePlanContract(plan, { homeDir: options.homeDir });
677
830
  return plan;
678
831
  }
679
832
 
680
833
  export function validateOkfKnowledgePlanContract(
681
834
  plan: OkfKnowledgePlan,
682
- _options: { homeDir?: string } = {},
835
+ options: { homeDir?: string; allowReviewedHighRisk?: boolean } = {},
683
836
  ): OkfKnowledgePlanValidationResult {
684
837
  const findings: OkfKnowledgePlanValidationFinding[] = [];
685
838
  const add = (
@@ -731,7 +884,9 @@ export function validateOkfKnowledgePlanContract(
731
884
  add("candidates", "plan.candidates", "Plan candidates must be an array.");
732
885
  } else {
733
886
  plan.candidates.forEach((candidate, index) =>
734
- validateCandidateContract(candidate, `candidates[${index}]`, evalSetIds, add),
887
+ validateCandidateContract(candidate, `candidates[${index}]`, evalSetIds, add, {
888
+ allowReviewedHighRisk: options.allowReviewedHighRisk === true,
889
+ }),
735
890
  );
736
891
  }
737
892
 
@@ -783,7 +938,7 @@ export function validateOkfKnowledgePlanContract(
783
938
 
784
939
  export function assertOkfKnowledgePlanContract(
785
940
  plan: OkfKnowledgePlan,
786
- options: { homeDir?: string } = {},
941
+ options: { homeDir?: string; allowReviewedHighRisk?: boolean } = {},
787
942
  ): void {
788
943
  const result = validateOkfKnowledgePlanContract(plan, options);
789
944
  if (!result.ok) {
@@ -828,6 +983,10 @@ function normalizePlanCandidate(value: unknown, path: string): OkfKnowledgePlanC
828
983
  readRequiredString(input.decisionReason, `${path}.decisionReason`),
829
984
  ),
830
985
  evidenceRefs: readStringArray(input.evidenceRefs, `${path}.evidenceRefs`).map(sanitizeOkfText),
986
+ supportRef:
987
+ input.supportRef === undefined
988
+ ? undefined
989
+ : normalizeKnowledgeSupportRef(input.supportRef, `${path}.supportRef`),
831
990
  reviewState: parseOkfReviewState(readRequiredString(input.reviewState, `${path}.reviewState`)),
832
991
  verificationNotApplicableReason:
833
992
  typeof input.verificationNotApplicableReason === "string"
@@ -844,6 +1003,8 @@ function normalizePlanCandidate(value: unknown, path: string): OkfKnowledgePlanC
844
1003
  bodySections: normalizeBodySections(input.bodySections, `${path}.bodySections`),
845
1004
  privacyCheck: normalizePrivacyCheck(input.privacyCheck, `${path}.privacyCheck`),
846
1005
  };
1006
+ candidate.privacyCheck.internalLinksStored =
1007
+ detectSessionMemorySensitivity(candidate).reasons.includes("url");
847
1008
  return candidate;
848
1009
  }
849
1010
 
@@ -852,6 +1013,7 @@ function validateCandidateContract(
852
1013
  path: string,
853
1014
  evalSetIds: Set<string>,
854
1015
  add: (path: string, code: string, message: string, severity?: "error" | "warning") => void,
1016
+ options: { allowReviewedHighRisk: boolean },
855
1017
  ): void {
856
1018
  if (!isRecord(candidate)) {
857
1019
  add(path, "candidate.object", "Candidate must be an object.");
@@ -909,6 +1071,7 @@ function validateCandidateContract(
909
1071
  validateStringArrayValue(candidate.pathScopes, `${path}.pathScopes`, add);
910
1072
  validateStringArrayValue(candidate.relatedConceptLinks, `${path}.relatedConceptLinks`, add);
911
1073
  validateStringArrayValue(candidate.evidenceRefs, `${path}.evidenceRefs`, add);
1074
+ validateKnowledgeSupportRefValue(candidate.supportRef, `${path}.supportRef`, add);
912
1075
  validateOverlayUpdatesValue(candidate.overlayUpdates, `${path}.overlayUpdates`, add);
913
1076
  validateScoresValue(candidate.scores, `${path}.scores`, add);
914
1077
  validatePrivacyCheckValue(candidate.privacyCheck, `${path}.privacyCheck`, add);
@@ -939,7 +1102,7 @@ function validateCandidateContract(
939
1102
  "Active writes require metadata-only evidence.",
940
1103
  );
941
1104
  }
942
- if (hasHighRiskHumanReviewSignal(candidate)) {
1105
+ if (hasHighRiskHumanReviewSignal(candidate) && !options.allowReviewedHighRisk) {
943
1106
  add(
944
1107
  path,
945
1108
  "candidate.active.highRiskHumanReview",
@@ -998,6 +1161,47 @@ function validateCandidateContract(
998
1161
  );
999
1162
  }
1000
1163
  }
1164
+ if (decision === "revoke") {
1165
+ if (candidate.targetStore !== "okf") {
1166
+ add(
1167
+ `${path}.targetStore`,
1168
+ "candidate.revoke.targetStore",
1169
+ "Knowledge revocation must target OKF.",
1170
+ );
1171
+ }
1172
+ if (candidate.reviewState !== "needs-human") {
1173
+ add(
1174
+ `${path}.reviewState`,
1175
+ "candidate.revoke.reviewState",
1176
+ "Knowledge revocation must enter human review.",
1177
+ );
1178
+ }
1179
+ if (candidate.metadataOnlyEvidence !== true) {
1180
+ add(
1181
+ `${path}.metadataOnlyEvidence`,
1182
+ "candidate.revoke.metadataOnlyEvidence",
1183
+ "Knowledge revocation requires metadata-only evidence.",
1184
+ );
1185
+ }
1186
+ if (!Array.isArray(candidate.evidenceRefs) || candidate.evidenceRefs.length === 0) {
1187
+ add(
1188
+ `${path}.evidenceRefs`,
1189
+ "candidate.revoke.evidenceRefs",
1190
+ "Knowledge revocation requires evidenceRefs.",
1191
+ );
1192
+ }
1193
+ const verification =
1194
+ isRecord(candidate.bodySections) && Array.isArray(candidate.bodySections.verification)
1195
+ ? candidate.bodySections.verification
1196
+ : [];
1197
+ if (verification.length === 0 && !isNonEmptyString(candidate.verificationNotApplicableReason)) {
1198
+ add(
1199
+ `${path}.bodySections.verification`,
1200
+ "candidate.revoke.verification",
1201
+ "Knowledge revocation requires verification or an explicit not-applicable reason.",
1202
+ );
1203
+ }
1204
+ }
1001
1205
  if (noOp && (!isNonEmptyString(candidate.id) || !isNonEmptyString(candidate.decisionReason))) {
1002
1206
  add(path, "candidate.noop.metadata", "No-op candidates require safe skip metadata.");
1003
1207
  }
@@ -1142,7 +1346,7 @@ function normalizePrivacyCheck(value: unknown, path: string): OkfKnowledgePrivac
1142
1346
  `${path}.rawCommandOutputStored`,
1143
1347
  ),
1144
1348
  secretsStored: readRequiredFalse(input.secretsStored, `${path}.secretsStored`),
1145
- internalLinksStored: readRequiredFalse(
1349
+ internalLinksStored: readRequiredBoolean(
1146
1350
  input.internalLinksStored,
1147
1351
  `${path}.internalLinksStored`,
1148
1352
  ),
@@ -1202,6 +1406,33 @@ function normalizeBasis(value: string): "direct" | "inferred" {
1202
1406
  return value;
1203
1407
  }
1204
1408
 
1409
+ function normalizeKnowledgeSupportRef(value: unknown, path: string): OkfKnowledgeSupportRef {
1410
+ const input = assertRecordValue(value, path) as Record<string, unknown>;
1411
+ const kind = readRequiredString(input.kind, `${path}.kind`);
1412
+ if (
1413
+ kind !== "user-declaration" &&
1414
+ kind !== "repo-policy" &&
1415
+ kind !== "state-observation" &&
1416
+ kind !== "verified-outcome" &&
1417
+ kind !== "semantic-inference"
1418
+ ) {
1419
+ throw new Error(`${path}.kind is invalid.`);
1420
+ }
1421
+ const observedAt = readRequiredString(input.observedAt, `${path}.observedAt`);
1422
+ if (!Number.isFinite(Date.parse(observedAt))) throw new Error(`${path}.observedAt is invalid.`);
1423
+ const subjectFingerprint =
1424
+ input.subjectFingerprint === null
1425
+ ? null
1426
+ : sanitizeOkfText(readRequiredString(input.subjectFingerprint, `${path}.subjectFingerprint`));
1427
+ return {
1428
+ id: sanitizeOkfText(readRequiredString(input.id, `${path}.id`)),
1429
+ kind,
1430
+ sourceRefId: sanitizeOkfText(readRequiredString(input.sourceRefId, `${path}.sourceRefId`)),
1431
+ subjectFingerprint,
1432
+ observedAt: new Date(observedAt).toISOString(),
1433
+ };
1434
+ }
1435
+
1205
1436
  function normalizeEvalSetDecision(value: string): OkfKnowledgeEvalSet["decision"] {
1206
1437
  const normalized = value === "no-write" ? "no_write" : value;
1207
1438
  if (
@@ -1371,6 +1602,45 @@ function validateStringArrayValue(
1371
1602
  });
1372
1603
  }
1373
1604
 
1605
+ function validateKnowledgeSupportRefValue(
1606
+ value: unknown,
1607
+ path: string,
1608
+ add: (path: string, code: string, message: string, severity?: "error" | "warning") => void,
1609
+ ): void {
1610
+ if (value === undefined) return;
1611
+ if (!isRecord(value)) {
1612
+ add(path, "supportRef.object", "Candidate supportRef must be an object.");
1613
+ return;
1614
+ }
1615
+ if (!isNonEmptyString(value.id)) add(`${path}.id`, "supportRef.id", "supportRef id is required.");
1616
+ if (
1617
+ value.kind !== "user-declaration" &&
1618
+ value.kind !== "repo-policy" &&
1619
+ value.kind !== "state-observation" &&
1620
+ value.kind !== "verified-outcome" &&
1621
+ value.kind !== "semantic-inference"
1622
+ ) {
1623
+ add(`${path}.kind`, "supportRef.kind", "supportRef kind is invalid.");
1624
+ }
1625
+ if (!isNonEmptyString(value.sourceRefId)) {
1626
+ add(`${path}.sourceRefId`, "supportRef.sourceRefId", "supportRef sourceRefId is required.");
1627
+ }
1628
+ if (value.subjectFingerprint !== null && !isNonEmptyString(value.subjectFingerprint)) {
1629
+ add(
1630
+ `${path}.subjectFingerprint`,
1631
+ "supportRef.subjectFingerprint",
1632
+ "supportRef subjectFingerprint must be null or a non-empty string.",
1633
+ );
1634
+ }
1635
+ if (!isNonEmptyString(value.observedAt) || !Number.isFinite(Date.parse(value.observedAt))) {
1636
+ add(
1637
+ `${path}.observedAt`,
1638
+ "supportRef.observedAt",
1639
+ "supportRef observedAt must be an ISO timestamp.",
1640
+ );
1641
+ }
1642
+ }
1643
+
1374
1644
  function validateOverlayUpdatesValue(
1375
1645
  value: unknown,
1376
1646
  path: string,
@@ -1428,8 +1698,15 @@ function validatePrivacyCheckValue(
1428
1698
  return;
1429
1699
  }
1430
1700
  for (const key of OKF_PRIVACY_FLAG_KEYS) {
1431
- if (value[key] !== false)
1701
+ if (key === "internalLinksStored") {
1702
+ if (typeof value[key] !== "boolean") {
1703
+ add(`${path}.${key}`, "privacy.boolean", "internalLinksStored must be boolean.");
1704
+ }
1705
+ continue;
1706
+ }
1707
+ if (value[key] !== false) {
1432
1708
  add(`${path}.${key}`, "privacy.false", "Privacy fields must be false.");
1709
+ }
1433
1710
  }
1434
1711
  }
1435
1712
 
@@ -1518,6 +1795,9 @@ export function loadActiveOkfKnowledgeIndex(input: { homeDir: string }): OkfActi
1518
1795
  roleTags: parsed.roleTags,
1519
1796
  workflowTags: parsed.workflowTags,
1520
1797
  targetPath: parsed.sourceLink.replace(/^\//u, ""),
1798
+ semanticFingerprint: createSemanticFingerprint(
1799
+ `${parsed.title} ${parsed.description} ${extractSummarySection(parsed.body)}`,
1800
+ ),
1521
1801
  });
1522
1802
  }
1523
1803
  return {
@@ -1532,9 +1812,8 @@ export function findCandidateConflictOrDuplicate(
1532
1812
  const targetPath = candidate.targetPath.replace(/^\/+/u, "");
1533
1813
  const duplicate = index.concepts.find(
1534
1814
  (concept) =>
1535
- concept.stableKey === candidate.stableKey ||
1536
- concept.targetPath === targetPath ||
1537
- concept.sourceLink === `/${targetPath}`,
1815
+ concept.stableKey !== candidate.stableKey &&
1816
+ (concept.targetPath === targetPath || concept.sourceLink === `/${targetPath}`),
1538
1817
  );
1539
1818
  if (duplicate !== undefined) {
1540
1819
  return {
@@ -1544,6 +1823,24 @@ export function findCandidateConflictOrDuplicate(
1544
1823
  };
1545
1824
  }
1546
1825
 
1826
+ const candidateFingerprint = createSemanticFingerprint(
1827
+ `${candidate.title} ${candidate.description} ${candidate.claim}`,
1828
+ );
1829
+ const semanticDuplicate = index.concepts.find(
1830
+ (concept) =>
1831
+ concept.stableKey !== candidate.stableKey &&
1832
+ candidateFingerprint !== "" &&
1833
+ candidateFingerprint === concept.semanticFingerprint &&
1834
+ scopesOverlap(candidate.repoTags, concept.repoTags),
1835
+ );
1836
+ if (semanticDuplicate !== undefined) {
1837
+ return {
1838
+ kind: "duplicate",
1839
+ conceptId: semanticDuplicate.id,
1840
+ reason: `Duplicate active OKF concept ${semanticDuplicate.id} already covers the same normalized claim.`,
1841
+ };
1842
+ }
1843
+
1547
1844
  const candidateTitle = normalizeComparableText(candidate.title);
1548
1845
  const conflict = index.concepts.find((concept) => {
1549
1846
  if (normalizeComparableText(concept.title) !== candidateTitle) return false;
@@ -1574,8 +1871,14 @@ export function scoreOkfKnowledgeCandidate(
1574
1871
  ) &&
1575
1872
  context.batch.evidenceWindow.events.every((event) => event.rawContentStored === false);
1576
1873
  const privacyRisk = hasUnsafeCandidateContent(candidate) ? 5 : metadataOnly ? 1 : 3;
1874
+ const semanticShape = hasReusableSemanticShape(candidate);
1875
+ const ephemeralIdentity = hasEphemeralKnowledgeIdentity(candidate);
1577
1876
  const evidenceStrength =
1578
- candidate.basis === "direct" && candidate.confidence === "high" && metadataOnly
1877
+ candidate.basis === "direct" &&
1878
+ candidate.confidence === "high" &&
1879
+ metadataOnly &&
1880
+ candidate.evidenceRefs.length > 0 &&
1881
+ semanticShape
1579
1882
  ? hasVerification
1580
1883
  ? 5
1581
1884
  : 4
@@ -1583,20 +1886,31 @@ export function scoreOkfKnowledgeCandidate(
1583
1886
  ? 3
1584
1887
  : 2;
1585
1888
  const actionability =
1586
- candidate.howToApply.trim() !== "" &&
1587
- candidate.bodySections.guidance.some((item) => item.trim() !== "")
1889
+ semanticShape &&
1890
+ candidate.howToApply.trim().length >= 16 &&
1891
+ candidate.bodySections.appliesWhen.some((item) => item.trim().length >= 8) &&
1892
+ candidate.bodySections.guidance.some((item) => item.trim().length >= 16)
1588
1893
  ? 4
1589
1894
  : 2;
1590
1895
  const reuseValue =
1591
- candidate.kind === "evos-case" || candidate.roleTags.length > 0 || candidate.repoTags.length > 0
1896
+ semanticShape &&
1897
+ !ephemeralIdentity &&
1898
+ (candidate.roleTags.length > 0 || candidate.repoTags.length > 0)
1592
1899
  ? 4
1593
- : 3;
1900
+ : 2;
1594
1901
  const stability = /hard policy|hard rule|must always|never allow/i.test(
1595
1902
  `${candidate.claim} ${candidate.howToApply}`,
1596
1903
  )
1597
1904
  ? 2
1598
1905
  : 4;
1599
- const duplicationRisk = conflict === null ? 1 : conflict.kind === "duplicate" ? 5 : 4;
1906
+ const duplicationRisk =
1907
+ ephemeralIdentity || isLegacyTemplateKnowledge(candidate)
1908
+ ? 5
1909
+ : conflict === null
1910
+ ? 1
1911
+ : conflict.kind === "duplicate"
1912
+ ? 5
1913
+ : 4;
1600
1914
  return {
1601
1915
  evidenceStrength,
1602
1916
  reuseValue,
@@ -1628,10 +1942,14 @@ export function decideOkfKnowledgeCandidate(
1628
1942
  hasHighRiskHumanReviewSignal(candidate) || hasHighRiskHumanReviewSignal(sanitized);
1629
1943
  const autoAcceptEligible =
1630
1944
  !highRiskRequiresHuman &&
1945
+ !isLegacyTemplateKnowledge(sanitized) &&
1946
+ !hasEphemeralKnowledgeIdentity(sanitized) &&
1947
+ hasReusableSemanticShape(sanitized) &&
1631
1948
  conflict === null &&
1632
1949
  localActiveTarget &&
1633
1950
  sanitized.targetStore === "okf" &&
1634
1951
  sanitized.basis === "direct" &&
1952
+ isDirectKnowledgeSupport(sanitized.supportRef) &&
1635
1953
  sanitized.metadataOnlyEvidence &&
1636
1954
  hasVerification &&
1637
1955
  hasScopeTags &&
@@ -1653,13 +1971,34 @@ export function decideOkfKnowledgeCandidate(
1653
1971
  };
1654
1972
  }
1655
1973
 
1974
+ if (isLegacyTemplateKnowledge(sanitized) || hasEphemeralKnowledgeIdentity(sanitized)) {
1975
+ return {
1976
+ ...sanitized,
1977
+ decision: "no_write",
1978
+ scores,
1979
+ reviewState: "auto-stored/unreviewed",
1980
+ decisionReason:
1981
+ "No write: run-local metadata templates and ephemeral identifiers are not reusable knowledge.",
1982
+ };
1983
+ }
1984
+
1656
1985
  if (highRiskRequiresHuman) {
1986
+ const reviewEligible =
1987
+ !originalUnsafe &&
1988
+ hasReusableSemanticShape(sanitized) &&
1989
+ localActiveTarget &&
1990
+ sanitized.metadataOnlyEvidence &&
1991
+ hasVerification &&
1992
+ hasScopeTags &&
1993
+ scores.privacyRisk <= 2;
1657
1994
  return {
1658
1995
  ...sanitized,
1659
- decision: "needs-human",
1996
+ decision: reviewEligible ? "needs-human" : "no_write",
1660
1997
  scores,
1661
- reviewState: "needs-human",
1662
- decisionReason: "Needs human review: high-risk domain requires review.",
1998
+ reviewState: reviewEligible ? "needs-human" : "auto-stored/unreviewed",
1999
+ decisionReason: reviewEligible
2000
+ ? "Needs human review: the candidate changes a sensitive engineering domain."
2001
+ : "No write: high-risk knowledge did not pass the privacy, verification, or scope gates.",
1663
2002
  };
1664
2003
  }
1665
2004
 
@@ -1677,12 +2016,12 @@ export function decideOkfKnowledgeCandidate(
1677
2016
  if (reusable) {
1678
2017
  return {
1679
2018
  ...sanitized,
1680
- decision: "needs-human",
2019
+ decision: "no_write",
1681
2020
  scores,
1682
- reviewState: "needs-human",
2021
+ reviewState: "auto-stored/unreviewed",
1683
2022
  decisionReason:
1684
2023
  conflict?.reason ??
1685
- explainNeedsHumanDecision(sanitized, scores, hasVerification, hasScopeTags),
2024
+ explainAutomaticNoWriteDecision(sanitized, scores, hasVerification, hasScopeTags),
1686
2025
  };
1687
2026
  }
1688
2027
 
@@ -1695,19 +2034,103 @@ export function decideOkfKnowledgeCandidate(
1695
2034
  };
1696
2035
  }
1697
2036
 
2037
+ async function readAuthorizedReviewedKnowledgeChange(input: {
2038
+ homeDir: string;
2039
+ plan: OkfKnowledgePlan;
2040
+ changeId: string;
2041
+ }): Promise<OkfKnowledgeChangeCandidateV1> {
2042
+ const { change } = await readOkfKnowledgeChangeCandidate({
2043
+ homeDir: input.homeDir,
2044
+ changeId: input.changeId,
2045
+ });
2046
+ if (change.state !== "accepted" || change.decision?.state !== "accepted") {
2047
+ throw new Error("Reviewed knowledge change must be accepted before activation.");
2048
+ }
2049
+ if (change.operation === "revoke") {
2050
+ throw new Error("Reviewed knowledge revocation must use the lifecycle decision path.");
2051
+ }
2052
+ if (
2053
+ input.plan.projectKey !== change.projectKey ||
2054
+ input.plan.runId !== change.runId ||
2055
+ input.plan.evidenceWindowId !== change.provenance.evidenceWindowId
2056
+ ) {
2057
+ throw new Error("Reviewed knowledge change identity does not match the activation plan.");
2058
+ }
2059
+ if (input.plan.candidates.length !== 1) {
2060
+ throw new Error("Reviewed knowledge activation must contain exactly one candidate.");
2061
+ }
2062
+ const candidate = input.plan.candidates[0] as OkfKnowledgePlanCandidate;
2063
+ const expectedCandidate: OkfKnowledgePlanCandidate = {
2064
+ ...change.candidate.planCandidate,
2065
+ decision: change.operation === "update" ? "update" : "create",
2066
+ reviewState: "accepted",
2067
+ targetPath:
2068
+ change.operation === "update" && change.base !== null
2069
+ ? change.base.sourceLink.replace(/^\/+/u, "")
2070
+ : change.targetPath,
2071
+ };
2072
+ if (stableJsonStringify(candidate) !== stableJsonStringify(expectedCandidate)) {
2073
+ throw new Error("Reviewed knowledge candidate does not match the accepted change.");
2074
+ }
2075
+ if (
2076
+ stableJsonStringify(input.plan.evoEvalSets) !== stableJsonStringify(change.candidate.evalSets)
2077
+ ) {
2078
+ throw new Error("Reviewed knowledge eval sets do not match the accepted change.");
2079
+ }
2080
+ const expectedEvidenceRefs = change.provenance.evidenceRefs.map((id) => ({
2081
+ id,
2082
+ kind: "knowledge-change-evidence",
2083
+ source: `knowledge-change:${change.id}`,
2084
+ rawContentStored: false,
2085
+ externalContentCopied: false,
2086
+ }));
2087
+ if (
2088
+ stableJsonStringify(input.plan.evidenceRefs) !== stableJsonStringify(expectedEvidenceRefs) ||
2089
+ stableJsonStringify(input.plan.privacyCheck) !==
2090
+ stableJsonStringify(expectedCandidate.privacyCheck)
2091
+ ) {
2092
+ throw new Error("Reviewed knowledge provenance does not match the accepted change.");
2093
+ }
2094
+ return change;
2095
+ }
2096
+
2097
+ function assertReviewedKnowledgeClassification(
2098
+ change: OkfKnowledgeChangeCandidateV1,
2099
+ classification: OkfKnowledgeChangeClassificationResult,
2100
+ ): void {
2101
+ if (
2102
+ classification.classification !== change.operation ||
2103
+ classification.baseRevision !== (change.base?.revision ?? null) ||
2104
+ classification.candidateRevision !== change.candidate.revision
2105
+ ) {
2106
+ throw new Error("Reviewed knowledge classification changed before activation.");
2107
+ }
2108
+ }
2109
+
1698
2110
  export async function activateOkfKnowledgePlan(input: {
1699
2111
  homeDir: string;
1700
2112
  plan: OkfKnowledgePlan;
1701
2113
  overwrite?: boolean;
1702
2114
  evidenceWindowPath?: string | null;
2115
+ reviewedChangeId?: string;
1703
2116
  }): Promise<OkfKnowledgeActivationResult> {
1704
- const validation = validateOkfKnowledgePlanContract(input.plan);
1705
2117
  const projectKey =
1706
2118
  isRecord(input.plan) && isNonEmptyString(input.plan.projectKey)
1707
2119
  ? input.plan.projectKey
1708
2120
  : "unknown";
1709
2121
  const runId =
1710
2122
  isRecord(input.plan) && isNonEmptyString(input.plan.runId) ? input.plan.runId : "unknown";
2123
+ const reviewedChange =
2124
+ input.reviewedChangeId === undefined
2125
+ ? null
2126
+ : await readAuthorizedReviewedKnowledgeChange({
2127
+ homeDir: input.homeDir,
2128
+ plan: input.plan,
2129
+ changeId: input.reviewedChangeId,
2130
+ });
2131
+ const validation = validateOkfKnowledgePlanContract(input.plan, {
2132
+ allowReviewedHighRisk: reviewedChange !== null,
2133
+ });
1711
2134
  if (!validation.ok) {
1712
2135
  await writeFailedOkfKnowledgePlanArtifact({
1713
2136
  homeDir: input.homeDir,
@@ -1720,7 +2143,7 @@ export async function activateOkfKnowledgePlan(input: {
1720
2143
  });
1721
2144
  throw new Error("OKF knowledge plan contract validation failed.");
1722
2145
  }
1723
- validateOkfKnowledgePlan(input.plan);
2146
+ validateOkfKnowledgePlan(input.plan, { allowReviewedHighRisk: reviewedChange !== null });
1724
2147
  const paths = resolveOkfKnowledgePaths(input.homeDir);
1725
2148
  const tmpRunDir = join(paths.tmpDir, `${input.plan.projectKey}-${input.plan.runId}`);
1726
2149
  const planPath = join(tmpRunDir, "knowledge-plan.json");
@@ -1737,35 +2160,149 @@ export async function activateOkfKnowledgePlan(input: {
1737
2160
  const overlayPaths: string[] = [];
1738
2161
  const skippedCandidates: string[] = [];
1739
2162
  const needsHumanCandidates: string[] = [];
2163
+ const pendingChangeIds: string[] = [];
1740
2164
  const affectedDirectories = new Set<string>([paths.okfDir]);
1741
2165
 
1742
2166
  try {
2167
+ const settings = await readRuntimeInjectionSettings(input.homeDir);
2168
+ const existingConcepts = await listOkfKnowledgeConcepts({ homeDir: input.homeDir });
2169
+ const existingByStableKey = new Map(
2170
+ existingConcepts
2171
+ .filter(
2172
+ (concept) =>
2173
+ isActiveOkfReviewState(concept.reviewState) && concept.lifecycle.status === "active",
2174
+ )
2175
+ .map((concept) => [concept.stableKey, concept]),
2176
+ );
2177
+ const writeCandidates: OkfKnowledgePlanCandidate[] = [];
2178
+ const existingByCandidateId = new Map<string, OkfKnowledgeConcept>();
1743
2179
  for (const candidate of input.plan.candidates) {
1744
2180
  if (candidate.decision === "no_write" || candidate.decision === "skip") {
1745
2181
  skippedCandidates.push(candidate.id);
1746
2182
  continue;
1747
2183
  }
1748
- if (candidate.decision === "needs-human") {
1749
- await writeNeedsHumanKnowledgeCandidate({
1750
- homeDir: input.homeDir,
1751
- plan: input.plan,
2184
+ if (candidate.targetStore !== "okf") {
2185
+ skippedCandidates.push(candidate.id);
2186
+ continue;
2187
+ }
2188
+ const existing = existingByStableKey.get(candidate.stableKey) ?? null;
2189
+ const classification = classifyOkfKnowledgeCandidate({
2190
+ candidate,
2191
+ existingConcept: existing,
2192
+ });
2193
+ if (reviewedChange !== null) {
2194
+ assertReviewedKnowledgeClassification(reviewedChange, classification);
2195
+ }
2196
+ const needsReview =
2197
+ reviewedChange === null &&
2198
+ (candidate.decision === "needs-human" ||
2199
+ classification.classification === "revoke" ||
2200
+ classification.classification === "supersede" ||
2201
+ (classification.classification === "update" && settings.reviewKnowledgeUpdates));
2202
+ if (needsReview) {
2203
+ const operation =
2204
+ classification.classification === "supersede" ||
2205
+ classification.classification === "revoke"
2206
+ ? classification.classification
2207
+ : existing === null
2208
+ ? "create"
2209
+ : "update";
2210
+ const beforeFreshness =
2211
+ existing === null ? null : deriveOkfKnowledgeFreshness({ concept: existing }).freshness;
2212
+ const verificationSnapshot = createOkfKnowledgeVerificationSnapshot({
1752
2213
  candidate,
2214
+ verifiedAt: input.plan.createdAt,
2215
+ projectKey: input.plan.projectKey,
2216
+ });
2217
+ const change = createOkfKnowledgeChangeCandidate({
2218
+ projectKey: input.plan.projectKey,
2219
+ runId: input.plan.runId,
2220
+ operation,
2221
+ stableKey: candidate.stableKey,
2222
+ targetPath: candidate.targetPath,
2223
+ base:
2224
+ existing === null || classification.baseProjection === null
2225
+ ? null
2226
+ : {
2227
+ conceptId: existing.id,
2228
+ sourceLink: existing.sourceLink,
2229
+ revision:
2230
+ classification.baseRevision ??
2231
+ createOkfKnowledgeRevision(
2232
+ createOkfKnowledgeRuntimeProjectionFromConcept(existing),
2233
+ ),
2234
+ runtimeProjection: classification.baseProjection,
2235
+ },
2236
+ candidateRevision: classification.candidateRevision,
2237
+ planCandidate: candidate,
2238
+ evalSets: input.plan.evoEvalSets.filter((evalSet) =>
2239
+ (candidate.evalSetRefs ?? []).includes(evalSet.id),
2240
+ ),
2241
+ diff: classification.diff,
2242
+ freshness: {
2243
+ before: beforeFreshness,
2244
+ after:
2245
+ classification.classification === "revoke"
2246
+ ? "stale"
2247
+ : verificationSnapshot === null
2248
+ ? "unknown"
2249
+ : "verified",
2250
+ reason:
2251
+ candidate.decision === "needs-human" || candidate.decision === "revoke"
2252
+ ? candidate.decisionReason
2253
+ : classification.reason,
2254
+ },
2255
+ evidenceWindowId: input.plan.evidenceWindowId,
2256
+ evidenceRefs: candidate.evidenceRefs,
2257
+ createdAt: input.plan.createdAt,
1753
2258
  });
1754
- needsHumanCandidates.push(candidate.id);
2259
+ await writeOkfKnowledgeChangeCandidate({ homeDir: input.homeDir, change });
2260
+ pendingChangeIds.push(change.id);
2261
+ if (candidate.decision === "needs-human" || candidate.decision === "revoke") {
2262
+ needsHumanCandidates.push(candidate.id);
2263
+ }
1755
2264
  continue;
1756
2265
  }
1757
- if (candidate.targetStore !== "okf") {
2266
+ if (classification.classification === "no_write") {
1758
2267
  skippedCandidates.push(candidate.id);
1759
2268
  continue;
1760
2269
  }
1761
-
1762
- const targetPath = resolveOkfTargetPath(paths.okfDir, candidate.targetPath);
1763
- const exists = await pathExists(targetPath);
2270
+ if (classification.classification === "supersede") {
2271
+ writeCandidates.push({
2272
+ ...candidate,
2273
+ decision: "create",
2274
+ reviewState: "accepted",
2275
+ });
2276
+ continue;
2277
+ }
2278
+ if (classification.classification === "revoke") {
2279
+ throw new Error("Knowledge revocation must be applied through its reviewed change.");
2280
+ }
1764
2281
  if (
1765
- exists &&
1766
- input.overwrite !== true &&
1767
- (candidate.decision === "create" || candidate.decision === "auto-accept")
2282
+ classification.classification === "update" ||
2283
+ classification.classification === "refresh"
1768
2284
  ) {
2285
+ if (existing === null) {
2286
+ throw new Error(`Knowledge ${classification.classification} requires an active concept.`);
2287
+ }
2288
+ const effectiveCandidate: OkfKnowledgePlanCandidate = {
2289
+ ...candidate,
2290
+ decision: "update",
2291
+ targetPath: existing.sourceLink.replace(/^\/+/u, ""),
2292
+ reviewState: candidate.reviewState === "auto-accepted" ? "auto-accepted" : "accepted",
2293
+ };
2294
+ writeCandidates.push(effectiveCandidate);
2295
+ existingByCandidateId.set(effectiveCandidate.id, existing);
2296
+ continue;
2297
+ }
2298
+ writeCandidates.push(candidate);
2299
+ }
2300
+
2301
+ const targetStates = await preflightOkfConceptTargets(paths.okfDir, writeCandidates);
2302
+ for (const candidate of writeCandidates) {
2303
+ const targetPath = resolveOkfTargetPath(paths.okfDir, candidate.targetPath);
2304
+ const targetState = targetStates.get(targetPath);
2305
+ if (targetState?.action === "skip-existing") {
1769
2306
  skippedCandidates.push(candidate.id);
1770
2307
  await appendOkfLog(
1771
2308
  paths.okfDir,
@@ -1775,7 +2312,11 @@ export async function activateOkfKnowledgePlan(input: {
1775
2312
  }
1776
2313
 
1777
2314
  await mkdir(dirname(targetPath), { recursive: true });
1778
- await writeFile(targetPath, renderOkfConcept(candidate, input.plan), "utf8");
2315
+ await writeFile(
2316
+ targetPath,
2317
+ renderOkfConcept(candidate, input.plan, existingByCandidateId.get(candidate.id)),
2318
+ "utf8",
2319
+ );
1779
2320
  conceptPaths.push(targetPath);
1780
2321
  affectedDirectories.add(dirname(targetPath));
1781
2322
 
@@ -1816,6 +2357,7 @@ export async function activateOkfKnowledgePlan(input: {
1816
2357
  overlayPaths,
1817
2358
  skippedCandidates,
1818
2359
  needsHumanCandidates,
2360
+ pendingChangeIds,
1819
2361
  indexPaths,
1820
2362
  derivedIndexPaths,
1821
2363
  logPaths,
@@ -1847,6 +2389,68 @@ export async function activateOkfKnowledgePlan(input: {
1847
2389
  }
1848
2390
  }
1849
2391
 
2392
+ async function preflightOkfConceptTargets(
2393
+ okfDir: string,
2394
+ candidates: OkfKnowledgePlanCandidate[],
2395
+ ): Promise<Map<string, { action: "write" | "skip-existing" }>> {
2396
+ const targets = new Map<string, { action: "write" | "skip-existing"; candidateId: string }>();
2397
+ for (const candidate of candidates) {
2398
+ if (
2399
+ candidate.targetStore !== "okf" ||
2400
+ candidate.decision === "no_write" ||
2401
+ candidate.decision === "skip" ||
2402
+ candidate.decision === "needs-human"
2403
+ ) {
2404
+ continue;
2405
+ }
2406
+ const targetPath = resolveOkfTargetPath(okfDir, candidate.targetPath);
2407
+ const sibling = targets.get(targetPath);
2408
+ if (sibling !== undefined) {
2409
+ throw new Error(
2410
+ `OKF candidates ${sibling.candidateId} and ${candidate.id} resolve to the same concept path.`,
2411
+ );
2412
+ }
2413
+
2414
+ if (!(await pathExists(targetPath))) {
2415
+ if (candidate.decision === "update") {
2416
+ throw new Error(`OKF update target does not exist: ${candidate.targetPath}`);
2417
+ }
2418
+ targets.set(targetPath, { action: "write", candidateId: candidate.id });
2419
+ continue;
2420
+ }
2421
+
2422
+ const existing = parseOkfConceptFile(okfDir, targetPath, await readFile(targetPath, "utf8"));
2423
+ if (existing === null) {
2424
+ throw new Error(
2425
+ `OKF concept target is occupied by an unrecognized file: ${candidate.targetPath}`,
2426
+ );
2427
+ }
2428
+ if (existing.stableKey !== candidate.stableKey) {
2429
+ throw new Error(
2430
+ `OKF concept target ${candidate.targetPath} belongs to stable key ${existing.stableKey}; refusing overwrite from ${candidate.stableKey}.`,
2431
+ );
2432
+ }
2433
+ if (
2434
+ existing.lifecycle.status === "deprecated" ||
2435
+ existing.lifecycle.status === "revoked" ||
2436
+ existing.lifecycle.status === "superseded" ||
2437
+ existing.reviewState === "deprecated" ||
2438
+ existing.reviewState === "revoked" ||
2439
+ existing.reviewState === "superseded" ||
2440
+ existing.reviewState === "rejected"
2441
+ ) {
2442
+ throw new Error(
2443
+ `OKF concept target ${candidate.targetPath} retains inactive lifecycle history and cannot be overwritten.`,
2444
+ );
2445
+ }
2446
+ targets.set(targetPath, {
2447
+ action: candidate.decision === "update" ? "write" : "skip-existing",
2448
+ candidateId: candidate.id,
2449
+ });
2450
+ }
2451
+ return new Map([...targets.entries()].map(([path, state]) => [path, { action: state.action }]));
2452
+ }
2453
+
1850
2454
  export async function readFailedOkfKnowledgePlan(input: {
1851
2455
  homeDir: string;
1852
2456
  projectKey: string;
@@ -2028,6 +2632,134 @@ export async function rebuildOkfKnowledgeIndexes(input: { homeDir: string }): Pr
2028
2632
  return pathsWritten;
2029
2633
  }
2030
2634
 
2635
+ /**
2636
+ * Finds only the run-local templates emitted by EvoDev's pre-semantic distiller. The preview is
2637
+ * intentionally exact and read-only so user-authored knowledge is never selected by resemblance.
2638
+ */
2639
+ export async function previewLegacyGeneratedKnowledgeCleanup(input: {
2640
+ homeDir: string;
2641
+ now?: string | Date;
2642
+ }): Promise<LegacyGeneratedKnowledgeCleanupPreview> {
2643
+ return toLegacyCleanupPreview(
2644
+ await createLegacyGeneratedKnowledgeCleanupPlan(input.homeDir, normalizeTimestamp(input.now)),
2645
+ );
2646
+ }
2647
+
2648
+ /**
2649
+ * Moves legacy generated templates into local state instead of deleting them. A manifest and the
2650
+ * original bytes are retained under state/evolution/knowledge-quarantine for recovery.
2651
+ */
2652
+ export async function quarantineLegacyGeneratedKnowledge(input: {
2653
+ homeDir: string;
2654
+ now?: string | Date;
2655
+ }): Promise<LegacyGeneratedKnowledgeCleanupResult> {
2656
+ const generatedAt = normalizeTimestamp(input.now);
2657
+ const evoDevPaths = resolveEvoDevPaths(input.homeDir);
2658
+ const processLockPath = join(evoDevPaths.stateDir, "evolution", ".process.lock");
2659
+ await mkdir(dirname(processLockPath), { recursive: true });
2660
+ try {
2661
+ await writeFile(
2662
+ processLockPath,
2663
+ `${JSON.stringify({ version: 1, kind: "knowledge-cleanup", acquiredAt: generatedAt, pid: process.pid })}\n`,
2664
+ { encoding: "utf8", flag: "wx" },
2665
+ );
2666
+ } catch (error) {
2667
+ if (isAlreadyExistsError(error)) {
2668
+ throw new Error("Evolution processing is active; legacy knowledge cleanup was not started.");
2669
+ }
2670
+ throw error;
2671
+ }
2672
+
2673
+ try {
2674
+ const plan = await createLegacyGeneratedKnowledgeCleanupPlan(input.homeDir, generatedAt);
2675
+ const preview = toLegacyCleanupPreview(plan);
2676
+ if (preview.totalArtifacts === 0) {
2677
+ return {
2678
+ ...preview,
2679
+ quarantineDir: null,
2680
+ manifestPath: null,
2681
+ okfIndexPaths: [],
2682
+ evosIndexPath: null,
2683
+ };
2684
+ }
2685
+
2686
+ const quarantineDir = join(
2687
+ evoDevPaths.stateDir,
2688
+ "evolution",
2689
+ "knowledge-quarantine",
2690
+ generatedAt.replace(/[:.]/gu, "-"),
2691
+ );
2692
+ if (await pathExists(quarantineDir)) {
2693
+ throw new Error(`Knowledge quarantine already exists: ${quarantineDir}`);
2694
+ }
2695
+ const manifestPath = join(quarantineDir, "manifest.json");
2696
+ const manifestItems = [
2697
+ ...plan.concepts.map((artifact) => createLegacyCleanupManifestItem(artifact, quarantineDir)),
2698
+ ...plan.evosCases.map((artifact) => createLegacyCleanupManifestItem(artifact, quarantineDir)),
2699
+ ...plan.overlaysToQuarantine.map((artifact) =>
2700
+ createLegacyCleanupManifestItem(artifact, quarantineDir),
2701
+ ),
2702
+ ...plan.overlaysToRewrite.map((artifact) =>
2703
+ createLegacyCleanupManifestItem(artifact, quarantineDir),
2704
+ ),
2705
+ ];
2706
+ const manifestBase = {
2707
+ schemaVersion: 1,
2708
+ kind: "legacy-generated-knowledge-quarantine",
2709
+ createdAt: generatedAt,
2710
+ rootDir: evoDevPaths.rootDir,
2711
+ items: manifestItems,
2712
+ } as const;
2713
+ await writeJson(
2714
+ manifestPath,
2715
+ { ...manifestBase, status: "planned", completedAt: null },
2716
+ { overwrite: false },
2717
+ );
2718
+
2719
+ for (const artifact of [...plan.concepts, ...plan.evosCases, ...plan.overlaysToQuarantine]) {
2720
+ const targetPath = resolveLegacyCleanupQuarantinePath(quarantineDir, artifact);
2721
+ await mkdir(dirname(targetPath), { recursive: true });
2722
+ await rename(artifact.sourcePath, targetPath);
2723
+ }
2724
+ for (const overlay of plan.overlaysToRewrite) {
2725
+ const targetPath = resolveLegacyCleanupQuarantinePath(quarantineDir, overlay);
2726
+ await mkdir(dirname(targetPath), { recursive: true });
2727
+ await writeFile(targetPath, overlay.content, { encoding: "utf8", flag: "wx" });
2728
+ await writeFile(overlay.sourcePath, overlay.cleanedContent, "utf8");
2729
+ }
2730
+
2731
+ const okfPaths = resolveOkfKnowledgePaths(input.homeDir);
2732
+ await prependLogEntry(
2733
+ join(okfPaths.okfDir, "log.md"),
2734
+ `**Cleanup**: Quarantined ${plan.concepts.length} legacy generated concept(s), ${plan.evosCases.length} evos case(s), and adjusted ${plan.overlaysToQuarantine.length + plan.overlaysToRewrite.length} overlay(s).`,
2735
+ );
2736
+ const directoryIndexPaths = await regenerateOkfDirectoryIndexes(okfPaths.okfDir);
2737
+ const derivedIndexPaths = await rebuildOkfKnowledgeIndexes({ homeDir: input.homeDir });
2738
+ const evosIndexPath = await rebuildEvolutionEvosIndexForCleanup(input.homeDir, generatedAt);
2739
+ await writeJson(
2740
+ manifestPath,
2741
+ {
2742
+ ...manifestBase,
2743
+ status: "completed",
2744
+ completedAt: generatedAt,
2745
+ okfIndexPaths: [...directoryIndexPaths, ...derivedIndexPaths],
2746
+ evosIndexPath,
2747
+ },
2748
+ { overwrite: true },
2749
+ );
2750
+
2751
+ return {
2752
+ ...preview,
2753
+ quarantineDir,
2754
+ manifestPath,
2755
+ okfIndexPaths: [...directoryIndexPaths, ...derivedIndexPaths],
2756
+ evosIndexPath,
2757
+ };
2758
+ } finally {
2759
+ await rm(processLockPath, { force: true });
2760
+ }
2761
+ }
2762
+
2031
2763
  export async function listOkfKnowledgeConcepts(input: {
2032
2764
  homeDir: string;
2033
2765
  projectKey?: string;
@@ -2037,6 +2769,13 @@ export async function listOkfKnowledgeConcepts(input: {
2037
2769
  }): Promise<OkfKnowledgeConcept[]> {
2038
2770
  const okfDir = resolveOkfKnowledgePaths(input.homeDir).okfDir;
2039
2771
  if (!(await pathExists(okfDir))) return [];
2772
+ const equivalentProjectKeys =
2773
+ input.projectKey === undefined
2774
+ ? undefined
2775
+ : await listEquivalentProjectKeys({
2776
+ homeDir: input.homeDir,
2777
+ projectKey: input.projectKey,
2778
+ });
2040
2779
  const files = await listMarkdownFiles(okfDir);
2041
2780
  const concepts: OkfKnowledgeConcept[] = [];
2042
2781
  for (const file of files) {
@@ -2046,7 +2785,7 @@ export async function listOkfKnowledgeConcepts(input: {
2046
2785
  if (parsed !== null) concepts.push(parsed);
2047
2786
  }
2048
2787
  return concepts
2049
- .filter((concept) => matchesConceptFilters(concept, input))
2788
+ .filter((concept) => matchesConceptFilters(concept, input, equivalentProjectKeys))
2050
2789
  .sort((left, right) => left.id.localeCompare(right.id));
2051
2790
  }
2052
2791
 
@@ -2076,7 +2815,7 @@ export async function queryOkfKnowledge(input: {
2076
2815
  now?: string | Date;
2077
2816
  }): Promise<OkfKnowledgeQueryResult> {
2078
2817
  const okfDir = resolveOkfKnowledgePaths(input.homeDir).okfDir;
2079
- const scope = normalizeKnowledgeQueryScope(input);
2818
+ const scope = await normalizeKnowledgeQueryScope(input);
2080
2819
  const queryText = normalizeKnowledgeQueryText(input.queryText);
2081
2820
  const now = normalizeQueryNow(input.now);
2082
2821
  const warnings: string[] = [];
@@ -2157,17 +2896,26 @@ export async function queryOkfKnowledge(input: {
2157
2896
  return left.concept.id.localeCompare(right.concept.id);
2158
2897
  })
2159
2898
  .slice(0, limit)
2160
- .map<OkfKnowledgeContextItem>((candidate, index) => ({
2161
- id: candidate.concept.id,
2162
- sourceType: "okf",
2163
- sourceLink: candidate.concept.sourceLink,
2164
- section: candidate.section,
2165
- rank: index + 1,
2166
- score: candidate.score,
2167
- title: candidate.concept.title,
2168
- summary: candidate.concept.description || candidate.concept.title,
2169
- matchReasons: candidate.matchReasons.length === 0 ? ["generic"] : candidate.matchReasons,
2170
- }));
2899
+ .map<OkfKnowledgeContextItem>((candidate, index) => {
2900
+ const freshness = deriveOkfKnowledgeFreshness({
2901
+ concept: candidate.concept,
2902
+ now,
2903
+ }).freshness;
2904
+ return {
2905
+ id: candidate.concept.id,
2906
+ sourceType: "okf",
2907
+ sourceLink: candidate.concept.sourceLink,
2908
+ section: candidate.section,
2909
+ rank: index + 1,
2910
+ score: candidate.score,
2911
+ title: candidate.concept.title,
2912
+ summary: candidate.concept.description || candidate.concept.title,
2913
+ freshness,
2914
+ runtimeExcerpt:
2915
+ freshness === "verified" ? createBoundedOkfRuntimeExcerpt(candidate.concept) : null,
2916
+ matchReasons: candidate.matchReasons.length === 0 ? ["generic"] : candidate.matchReasons,
2917
+ };
2918
+ });
2171
2919
 
2172
2920
  return {
2173
2921
  projectKey: scope.projectKey,
@@ -2375,17 +3123,39 @@ export async function queryScopedOkfKnowledgeContext(input: {
2375
3123
  now?: string | Date;
2376
3124
  }): Promise<OkfKnowledgeQueryResult> {
2377
3125
  const result = await queryOkfKnowledge(input);
2378
- const evosCases = await listEvolutionEvosCases({
2379
- homeDir: input.homeDir,
2380
- projectKey: input.projectKey,
2381
- roleId: input.roleId,
2382
- reviewStates: ["accepted", "auto-accepted"],
2383
- });
3126
+ const equivalentProjectKeys =
3127
+ result.projectKey === undefined
3128
+ ? [undefined]
3129
+ : await listEquivalentProjectKeys({
3130
+ homeDir: input.homeDir,
3131
+ projectKey: result.projectKey,
3132
+ });
3133
+ const evosResults = await Promise.all(
3134
+ equivalentProjectKeys.map((projectKey) =>
3135
+ listEvolutionEvosCases({
3136
+ homeDir: input.homeDir,
3137
+ ...(projectKey === undefined ? {} : { projectKey }),
3138
+ roleId: input.roleId,
3139
+ reviewStates: ["accepted", "auto-accepted"],
3140
+ }),
3141
+ ),
3142
+ );
3143
+ const evosCases = {
3144
+ cases: [
3145
+ ...new Map(
3146
+ evosResults
3147
+ .flatMap((entry) => entry.cases)
3148
+ .map((evosCase) => [`${evosCase.projectKey}\0${evosCase.id}`, evosCase]),
3149
+ ).values(),
3150
+ ],
3151
+ warnings: [...new Set(evosResults.flatMap((entry) => entry.warnings))],
3152
+ };
2384
3153
  return mergeAcceptedEvosCasesIntoKnowledgeQuery(
2385
3154
  result,
2386
3155
  evosCases.cases,
2387
3156
  evosCases.warnings,
2388
3157
  input.limit,
3158
+ equivalentProjectKeys.flatMap((projectKey) => (projectKey === undefined ? [] : [projectKey])),
2389
3159
  );
2390
3160
  }
2391
3161
 
@@ -2394,6 +3164,7 @@ export function mergeAcceptedEvosCasesIntoKnowledgeQuery(
2394
3164
  cases: EvolutionEvosCase[],
2395
3165
  warnings: string[] = [],
2396
3166
  limit?: number,
3167
+ equivalentProjectKeys: string[] = [],
2397
3168
  ): OkfKnowledgeQueryResult {
2398
3169
  const startRank = result.items.length + 1;
2399
3170
  const evosItems = cases.flatMap<OkfKnowledgeContextItem>((evosCase, index) => {
@@ -2408,8 +3179,10 @@ export function mergeAcceptedEvosCasesIntoKnowledgeQuery(
2408
3179
  : []),
2409
3180
  ];
2410
3181
  const structuredScore =
2411
- (result.projectKey !== undefined && evosCase.projectKey === result.projectKey ? 100 : 0) +
2412
- (result.roleId !== undefined && evosCase.roleTags.includes(result.roleId) ? 100 : 0);
3182
+ (result.projectKey !== undefined &&
3183
+ [result.projectKey, ...equivalentProjectKeys].includes(evosCase.projectKey)
3184
+ ? 100
3185
+ : 0) + (result.roleId !== undefined && evosCase.roleTags.includes(result.roleId) ? 100 : 0);
2413
3186
  return [
2414
3187
  {
2415
3188
  id: evosCase.id,
@@ -2420,6 +3193,8 @@ export function mergeAcceptedEvosCasesIntoKnowledgeQuery(
2420
3193
  score: structuredScore + lexical.score,
2421
3194
  title: evosCase.title,
2422
3195
  summary: evosCase.expectedFutureBehavior || evosCase.result.summary || evosCase.title,
3196
+ freshness: "unknown",
3197
+ runtimeExcerpt: null,
2423
3198
  matchReasons:
2424
3199
  lexical.reasons.length === 0
2425
3200
  ? structuredReasons
@@ -2446,36 +3221,56 @@ export async function createScopedKnowledgeContextPack(input: {
2446
3221
  paths?: string[];
2447
3222
  queryText?: string;
2448
3223
  limit?: number;
3224
+ inlineOnly?: boolean;
2449
3225
  }): Promise<ScopedKnowledgeContextPack | null> {
2450
- const result = await queryScopedOkfKnowledgeContext(input);
2451
- if (result.items.length === 0) return null;
3226
+ const result = await queryScopedOkfKnowledgeContext({
3227
+ ...input,
3228
+ limit: input.inlineOnly === true ? Math.max(input.limit ?? 3, 50) : input.limit,
3229
+ });
3230
+ const selectedItems =
3231
+ input.inlineOnly === true
3232
+ ? result.items.filter((item) => item.runtimeExcerpt !== null).slice(0, input.limit ?? 3)
3233
+ : result.items;
3234
+ if (selectedItems.length === 0) return null;
2452
3235
  const scope: ScopedKnowledgeContextPackScope = {
2453
3236
  ...(result.projectKey === undefined ? {} : { projectKey: result.projectKey }),
2454
3237
  ...(result.roleId === undefined ? {} : { roleId: result.roleId }),
2455
3238
  ...(result.workflowId === undefined ? {} : { workflowId: result.workflowId }),
2456
3239
  paths: result.paths,
2457
3240
  };
2458
- const items = result.items.map<ScopedKnowledgeContextPackItem>((item) => ({
3241
+ const items = selectedItems.map<ScopedKnowledgeContextPackItem>((item) => ({
2459
3242
  id: item.id,
2460
3243
  sourceType: item.sourceType,
2461
3244
  sourceLink: item.sourceLink,
2462
3245
  section: item.section,
2463
3246
  rank: item.rank,
2464
3247
  title: sanitizeOkfText(item.title),
3248
+ freshness: item.freshness,
3249
+ loader:
3250
+ item.sourceType === "okf"
3251
+ ? { kind: "knowledge-show", conceptId: item.id }
3252
+ : { kind: "none", conceptId: null },
3253
+ delivery: {
3254
+ mode: item.runtimeExcerpt === null ? "reference" : "inline",
3255
+ excerpt: item.runtimeExcerpt,
3256
+ },
2465
3257
  matchReasons: uniqueStrings(item.matchReasons.map(sanitizeLexicalReason)),
2466
3258
  }));
2467
3259
  const okfIndexRevision = createKnowledgeContextRevision(items);
2468
3260
  const queryText = normalizeKnowledgeQueryText(result.queryText);
3261
+ const queryHash = queryText === undefined ? undefined : sha256Short(queryText);
2469
3262
  const packSeed = stableJsonStringify({
2470
3263
  okfIndexRevision,
2471
3264
  scope,
2472
- queryText: queryText ?? null,
3265
+ queryHash: queryHash ?? null,
2473
3266
  items: items.map((item) => ({
2474
3267
  id: item.id,
2475
3268
  sourceType: item.sourceType,
2476
3269
  sourceLink: item.sourceLink,
2477
3270
  section: item.section,
2478
- rank: item.rank,
3271
+ freshness: item.freshness,
3272
+ loader: item.loader,
3273
+ delivery: item.delivery,
2479
3274
  matchReasons: item.matchReasons,
2480
3275
  })),
2481
3276
  });
@@ -2485,7 +3280,7 @@ export async function createScopedKnowledgeContextPack(input: {
2485
3280
  id: `ctxpack-${sha256Short(packSeed)}`,
2486
3281
  okfIndexRevision,
2487
3282
  scope,
2488
- ...(queryText === undefined ? {} : { queryText }),
3283
+ ...(queryHash === undefined ? {} : { queryHash }),
2489
3284
  items,
2490
3285
  warnings:
2491
3286
  result.warnings.length === 0
@@ -2504,13 +3299,22 @@ export function formatScopedKnowledgePromptBlock(pack: ScopedKnowledgeContextPac
2504
3299
  `Project: ${pack.scope.projectKey ?? "all"}`,
2505
3300
  `Role: ${pack.scope.roleId ?? "any"}`,
2506
3301
  `Workflow: ${pack.scope.workflowId ?? "any"}`,
2507
- ...(pack.queryText === undefined ? [] : [`Query: ${sanitizeOkfText(pack.queryText)}`]),
2508
3302
  `Paths: ${pack.scope.paths.length === 0 ? "all" : pack.scope.paths.join(", ")}`,
2509
3303
  "Raw content stored: false",
3304
+ "Freshness is determined by EvoDev lifecycle and verification metadata; do not infer validity from timestamps.",
2510
3305
  "",
2511
3306
  "Applicable items:",
2512
3307
  ...pack.items.flatMap((item) => [
2513
- `- ${item.rank}. ${sanitizeOkfText(item.id)} (${item.section}; ${item.sourceType})`,
3308
+ `- ${item.rank}. ${sanitizeOkfText(item.title)} (${item.section}; ${item.sourceType})`,
3309
+ ` Identity: ${sanitizeOkfText(item.id)}`,
3310
+ ` Freshness: ${item.freshness}`,
3311
+ ` Delivery: ${item.delivery.mode}`,
3312
+ ...(item.delivery.excerpt === null
3313
+ ? [" Guidance: load on demand; do not treat this reference as a standing constraint."]
3314
+ : [` Guidance: ${sanitizeOkfText(item.delivery.excerpt)}`]),
3315
+ ...(item.loader.kind === "knowledge-show"
3316
+ ? [` Load: evodev knowledge show ${sanitizeOkfText(item.loader.conceptId)}`]
3317
+ : []),
2514
3318
  ` Source: ${sanitizeOkfText(item.sourceLink)}`,
2515
3319
  ` Match: ${item.matchReasons.length === 0 ? "generic" : item.matchReasons.join(", ")}`,
2516
3320
  ]),
@@ -2560,9 +3364,11 @@ export async function writeContextInjectionReceipt(input: {
2560
3364
  okfIndexRevision: input.pack.okfIndexRevision,
2561
3365
  scope: input.pack.scope,
2562
3366
  itemIds: input.pack.items.map((item) => item.id),
3367
+ queryHash: input.pack.queryHash ?? null,
2563
3368
  injectedAt: input.injectedAt ?? new Date().toISOString(),
2564
3369
  hookEventId: input.hookEventId ?? null,
2565
3370
  trigger: input.trigger,
3371
+ outcome: null,
2566
3372
  rawContentStored: false,
2567
3373
  };
2568
3374
  const path = resolveContextInjectionReceiptPath({
@@ -2574,6 +3380,57 @@ export async function writeContextInjectionReceipt(input: {
2574
3380
  return { path, receipt };
2575
3381
  }
2576
3382
 
3383
+ export async function recordContextInjectionOutcome(input: {
3384
+ homeDir: string;
3385
+ sessionKey: string;
3386
+ eventId: string;
3387
+ observedAt: string;
3388
+ summary: string;
3389
+ }): Promise<string[]> {
3390
+ const stateDir = resolveEvoDevPaths(input.homeDir).stateDir;
3391
+ const directory = join(
3392
+ stateDir,
3393
+ "context-injections",
3394
+ sanitizeReceiptPathSegment(input.sessionKey),
3395
+ );
3396
+ let names: string[];
3397
+ try {
3398
+ names = await readdir(directory);
3399
+ } catch {
3400
+ return [];
3401
+ }
3402
+ const paths: string[] = [];
3403
+ for (const name of names.filter((item) => item.endsWith(".json")).sort()) {
3404
+ const path = join(directory, name);
3405
+ let receipt: ContextInjectionReceipt;
3406
+ try {
3407
+ receipt = JSON.parse(await readFile(path, "utf8")) as ContextInjectionReceipt;
3408
+ } catch {
3409
+ continue;
3410
+ }
3411
+ if (
3412
+ receipt.version !== 1 ||
3413
+ receipt.rawContentStored !== false ||
3414
+ (receipt.outcome !== null && receipt.outcome !== undefined)
3415
+ ) {
3416
+ continue;
3417
+ }
3418
+ const updated: ContextInjectionReceipt = {
3419
+ ...receipt,
3420
+ outcome: {
3421
+ status: "verified",
3422
+ eventId: sanitizeReceiptPathSegment(input.eventId),
3423
+ observedAt: normalizeTimestamp(input.observedAt),
3424
+ summaryHash: sha256Short(input.summary),
3425
+ rawContentStored: false,
3426
+ },
3427
+ };
3428
+ await writeJson(path, updated, { overwrite: true });
3429
+ paths.push(path);
3430
+ }
3431
+ return paths;
3432
+ }
3433
+
2577
3434
  export async function markOkfKnowledgeConceptStale(input: {
2578
3435
  homeDir: string;
2579
3436
  conceptId: string;
@@ -2795,8 +3652,8 @@ async function appendLifecycleLogs(input: {
2795
3652
  function validateLifecycleReason(value: string): string {
2796
3653
  const reason = value.trim();
2797
3654
  if (reason === "") throw new Error("Lifecycle revoke requires a non-empty reason.");
2798
- if (FORBIDDEN_OKF_TEXT.test(reason) || PRIVATE_OR_INTERNAL_URL.test(reason)) {
2799
- throw new Error("Lifecycle revoke reason contains unsafe raw or sensitive text.");
3655
+ if (detectSessionMemorySensitivity(reason).classification === "credential") {
3656
+ throw new Error("Lifecycle revoke reason contains a credential.");
2800
3657
  }
2801
3658
  return sanitizeOkfText(reason);
2802
3659
  }
@@ -2868,8 +3725,8 @@ export async function lintOkfKnowledge(input: {
2868
3725
  if (reviewState === "auto-stored/unreviewed") {
2869
3726
  warnings.push(`Concept missing active lifecycle reviewState: ${rel}`);
2870
3727
  }
2871
- if (FORBIDDEN_OKF_TEXT.test(content))
2872
- errors.push(`Concept contains forbidden sensitive text: ${rel}`);
3728
+ if (detectSessionMemorySensitivity(content).classification === "credential")
3729
+ errors.push(`Concept contains a credential: ${rel}`);
2873
3730
  if (input.stale === true) {
2874
3731
  const concept = parseOkfConceptFile(paths.okfDir, file.path, content);
2875
3732
  if (concept !== null) {
@@ -2925,7 +3782,7 @@ export function formatOkfKnowledgeQuery(result: OkfKnowledgeQueryResult): string
2925
3782
  ? ["- none"]
2926
3783
  : items.map(
2927
3784
  (item) =>
2928
- `- ${item.summary}\n Source: ${item.sourceLink}\n Match: ${item.matchReasons.join(", ")}\n Rank: ${item.rank}`,
3785
+ `- ${item.summary}\n Freshness: ${item.freshness}\n ${item.sourceType === "okf" ? `Load: evodev knowledge show ${item.id}\n ` : ""}Source: ${item.sourceLink}\n Match: ${item.matchReasons.join(", ")}\n Rank: ${item.rank}`,
2929
3786
  )),
2930
3787
  "",
2931
3788
  ];
@@ -3165,97 +4022,37 @@ function createOverlayUpdates(
3165
4022
  ];
3166
4023
  }
3167
4024
 
3168
- async function writeNeedsHumanKnowledgeCandidate(input: {
3169
- homeDir: string;
3170
- plan: OkfKnowledgePlan;
3171
- candidate: OkfKnowledgePlanCandidate;
3172
- }): Promise<string> {
3173
- const root = join(resolveEvoDevPaths(input.homeDir).stateDir, "evolution");
3174
- const targetDir = join(
3175
- root,
3176
- sanitizePlanStorageId("projectKey", input.plan.projectKey),
3177
- sanitizePlanStorageId("runId", input.plan.runId),
3178
- "review-candidates",
3179
- );
3180
- const targetPath = join(
3181
- targetDir,
3182
- `${sanitizePlanStorageId("candidateId", input.candidate.id)}.json`,
3183
- );
3184
- assertPathDescendant(root, targetPath, "reviewCandidatePath");
3185
- const candidateSnapshot = sanitizeReviewQueueValue(input.candidate);
3186
- await writeJson(
3187
- targetPath,
3188
- {
3189
- schemaVersion: 1,
3190
- kind: "evolution-review-candidate",
3191
- id: input.candidate.id,
3192
- projectKey: input.plan.projectKey,
3193
- runId: input.plan.runId,
3194
- createdAt: input.plan.createdAt,
3195
- candidateKind: input.candidate.kind,
3196
- title: sanitizeOkfText(input.candidate.title),
3197
- targetStore: input.candidate.targetStore,
3198
- targetPath: sanitizeOkfText(input.candidate.targetPath),
3199
- stableKey: sanitizeOkfText(input.candidate.stableKey),
3200
- reviewState: "needs-human",
3201
- reasons: [sanitizeOkfText(input.candidate.decisionReason)],
3202
- candidate: candidateSnapshot,
3203
- provenance: {
3204
- runId: input.plan.runId,
3205
- evidenceWindowId: input.plan.evidenceWindowId,
3206
- evidenceRefs: input.candidate.evidenceRefs.map(sanitizeOkfText),
3207
- createdBy: "evodev",
3208
- rawLogsStored: false,
3209
- rawPromptsStored: false,
3210
- sourceDumpsStored: false,
3211
- rawCommandOutputStored: false,
3212
- },
3213
- privacy: {
3214
- classification: "local-private",
3215
- rawPromptsStored: false,
3216
- rawLogsStored: false,
3217
- sourceDumpsStored: false,
3218
- rawCommandOutputStored: false,
3219
- secretsStored: false,
3220
- internalLinksStored: false,
3221
- },
3222
- },
3223
- { overwrite: true },
3224
- );
3225
- return targetPath;
3226
- }
3227
-
3228
4025
  function sanitizeOkfPlanCandidate(candidate: OkfKnowledgePlanCandidate): OkfKnowledgePlanCandidate {
3229
4026
  return sanitizeReviewQueueValue(candidate) as OkfKnowledgePlanCandidate;
3230
4027
  }
3231
4028
 
3232
4029
  function sanitizeReviewQueueValue(value: unknown): unknown {
4030
+ return boundReviewQueueValue(redactSessionMemoryCredentials(value).value);
4031
+ }
4032
+
4033
+ function boundReviewQueueValue(value: unknown): unknown {
3233
4034
  if (typeof value === "string") return sanitizeOkfText(value);
3234
4035
  if (value === null || value === undefined) return value;
3235
- if (Array.isArray(value)) return value.map(sanitizeReviewQueueValue);
4036
+ if (Array.isArray(value)) return value.map(boundReviewQueueValue);
3236
4037
  if (typeof value !== "object") return value;
3237
4038
  return Object.fromEntries(
3238
- Object.entries(value).map(([key, child]) => [key, sanitizeReviewQueueValue(child)]),
4039
+ Object.entries(value).map(([key, child]) => [key, boundReviewQueueValue(child)]),
3239
4040
  );
3240
4041
  }
3241
4042
 
3242
4043
  function sanitizeOkfText(value: string): string {
3243
- return value
3244
- .replace(new RegExp(PRIVATE_OR_INTERNAL_URL.source, "gi"), "[redacted]")
3245
- .replace(new RegExp(FORBIDDEN_OKF_TEXT.source, "gi"), "[redacted]")
3246
- .replace(/\s+/gu, " ")
3247
- .trim()
3248
- .slice(0, 800);
4044
+ return redactSessionMemoryCredentialText(value).value.replace(/\s+/gu, " ").trim().slice(0, 800);
3249
4045
  }
3250
4046
 
3251
4047
  function hasUnsafeCandidateContent(candidate: OkfKnowledgePlanCandidate): boolean {
3252
- const content = JSON.stringify(candidate);
3253
4048
  return (
3254
- FORBIDDEN_OKF_TEXT.test(content) ||
3255
- FORBIDDEN_OKF_FIELD.test(content) ||
3256
- PRIVATE_OR_INTERNAL_URL.test(content) ||
4049
+ detectSessionMemorySensitivity(candidate).classification === "credential" ||
3257
4050
  (candidate.privacyCheck !== undefined &&
3258
- Object.values(candidate.privacyCheck).some((value) => value !== false))
4051
+ (candidate.privacyCheck.rawPromptsStored !== false ||
4052
+ candidate.privacyCheck.rawLogsStored !== false ||
4053
+ candidate.privacyCheck.sourceDumpsStored !== false ||
4054
+ candidate.privacyCheck.rawCommandOutputStored !== false ||
4055
+ candidate.privacyCheck.secretsStored !== false))
3259
4056
  );
3260
4057
  }
3261
4058
 
@@ -3292,7 +4089,7 @@ function hasHighRiskHumanReviewSignal(candidate: unknown): boolean {
3292
4089
  return HUMAN_REVIEW_DOMAIN_PATTERN.test(values.join(" "));
3293
4090
  }
3294
4091
 
3295
- function explainNeedsHumanDecision(
4092
+ function explainAutomaticNoWriteDecision(
3296
4093
  candidate: OkfKnowledgePlanCandidate,
3297
4094
  scores: OkfKnowledgeCandidateScores,
3298
4095
  hasVerification: boolean,
@@ -3301,6 +4098,9 @@ function explainNeedsHumanDecision(
3301
4098
  const reasons: string[] = [];
3302
4099
  if (candidate.targetStore !== "okf") reasons.push("target is not user-local OKF");
3303
4100
  if (candidate.basis !== "direct") reasons.push("basis is inferred");
4101
+ if (!isDirectKnowledgeSupport(candidate.supportRef)) {
4102
+ reasons.push("structured direct support is missing");
4103
+ }
3304
4104
  if (!candidate.metadataOnlyEvidence) reasons.push("evidence is not metadata-only");
3305
4105
  if (!hasVerification) reasons.push("verification evidence is missing");
3306
4106
  if (!hasScopeTags) reasons.push("repo or role tags are missing");
@@ -3308,7 +4108,7 @@ function explainNeedsHumanDecision(
3308
4108
  if (scores.privacyRisk > 2) reasons.push("privacy risk is above auto-accept");
3309
4109
  if (scores.stability < 3) reasons.push("stability is below auto-accept");
3310
4110
  if (scores.duplicationRisk > 2) reasons.push("duplication risk is above auto-accept");
3311
- return `Needs human review: ${reasons.join("; ") || "auto-accept gates were not all satisfied"}.`;
4111
+ return `No write: ${reasons.join("; ") || "auto-accept gates were not all satisfied"}.`;
3312
4112
  }
3313
4113
 
3314
4114
  function resolveCandidateReviewStateForWrite(
@@ -3332,6 +4132,83 @@ function normalizeComparableText(value: string): string {
3332
4132
  return sanitizeSlug(value).replace(/[._/-]+/gu, "-");
3333
4133
  }
3334
4134
 
4135
+ function isLegacyTemplateKnowledge(candidate: OkfKnowledgePlanCandidate): boolean {
4136
+ const text = [
4137
+ candidate.title,
4138
+ candidate.description,
4139
+ candidate.claim,
4140
+ candidate.howToApply,
4141
+ candidate.bodySections.summary,
4142
+ ...candidate.bodySections.guidance,
4143
+ ].join(" ");
4144
+ return /(?:Project run segment-run-|Run segment-run-.*evolution case|redacted (?:metadata )?evidence event|No failure signal was detected|Evolution distillation used (?:conditional|strong) trigger evidence|Future role agents should inspect accepted knowledge first|This record is derived from metadata-only evidence)/i.test(
4145
+ text,
4146
+ );
4147
+ }
4148
+
4149
+ function hasEphemeralKnowledgeIdentity(candidate: OkfKnowledgePlanCandidate): boolean {
4150
+ return /(?:segment-(?:run|evidence)-[a-f0-9]{8,}|(?:^|:)run-[a-f0-9]{8,}|(?:^|:)(?:evo|k)-[a-f0-9]{8,})/i.test(
4151
+ `${candidate.stableKey} ${candidate.targetPath} ${candidate.title}`,
4152
+ );
4153
+ }
4154
+
4155
+ function hasReusableSemanticShape(candidate: OkfKnowledgePlanCandidate): boolean {
4156
+ if (isLegacyTemplateKnowledge(candidate)) return false;
4157
+ if (candidate.title.trim().length < 8 || candidate.claim.trim().length < 20) return false;
4158
+ if (candidate.bodySections.summary.trim().length < 20) return false;
4159
+ if (!candidate.bodySections.appliesWhen.some((item) => item.trim().length >= 8)) return false;
4160
+ if (!candidate.bodySections.guidance.some((item) => item.trim().length >= 16)) return false;
4161
+ return true;
4162
+ }
4163
+
4164
+ function createSemanticFingerprint(value: string): string {
4165
+ const tokens = value
4166
+ .toLowerCase()
4167
+ .replace(/segment-(?:run|evidence)-[a-f0-9]+/gu, " ")
4168
+ .replace(/[^a-z0-9\p{L}\p{N}]+/gu, " ")
4169
+ .split(/\s+/u)
4170
+ .filter((token) => token.length >= 3)
4171
+ .filter(
4172
+ (token) =>
4173
+ !new Set([
4174
+ "the",
4175
+ "and",
4176
+ "for",
4177
+ "with",
4178
+ "this",
4179
+ "that",
4180
+ "when",
4181
+ "from",
4182
+ "should",
4183
+ "knowledge",
4184
+ "evodev",
4185
+ ]).has(token),
4186
+ );
4187
+ return [...new Set(tokens)].sort().join(" ").slice(0, 600);
4188
+ }
4189
+
4190
+ function extractSummarySection(body: string): string {
4191
+ return body.match(/(?:^|\n)# Summary\s*\n+([\s\S]*?)(?=\n# |$)/u)?.[1]?.trim() ?? "";
4192
+ }
4193
+
4194
+ function createBoundedOkfRuntimeExcerpt(concept: OkfKnowledgeConcept): string {
4195
+ const projection = createOkfKnowledgeRuntimeProjectionFromConcept(concept);
4196
+ return sanitizeOkfText(
4197
+ [
4198
+ projection.summary || concept.description,
4199
+ ...projection.guidance.map((item) => `- ${item}`),
4200
+ ...projection.verification.map((item) => `Verification: ${item}`),
4201
+ ]
4202
+ .filter((item) => item.trim() !== "")
4203
+ .join("\n"),
4204
+ ).slice(0, 1_200);
4205
+ }
4206
+
4207
+ function scopesOverlap(left: string[], right: string[]): boolean {
4208
+ if (left.length === 0 || right.length === 0) return true;
4209
+ return left.some((tag) => right.includes(tag));
4210
+ }
4211
+
3335
4212
  function listMarkdownFilesSync(root: string): Array<{ path: string; name: string }> {
3336
4213
  if (!existsSync(root)) return [];
3337
4214
  const entries = readdirSync(root, { withFileTypes: true });
@@ -3455,8 +4332,41 @@ async function ensureOverlayConcept(input: {
3455
4332
  );
3456
4333
  }
3457
4334
 
3458
- function renderOkfConcept(candidate: OkfKnowledgePlanCandidate, plan: OkfKnowledgePlan): string {
4335
+ function renderOkfConcept(
4336
+ candidate: OkfKnowledgePlanCandidate,
4337
+ plan: OkfKnowledgePlan,
4338
+ existing?: OkfKnowledgeConcept,
4339
+ ): string {
3459
4340
  const scores = resolveCandidateScoresForWrite(candidate);
4341
+ const reviewState = resolveCandidateReviewStateForWrite(candidate);
4342
+ const nextLifecycle = createDefaultOkfLifecycle({
4343
+ type: candidate.okfType,
4344
+ path: candidate.targetPath,
4345
+ tags: [
4346
+ "evodev",
4347
+ candidate.kind,
4348
+ ...candidate.roleTags.map((tag) => `role:${tag}`),
4349
+ ...candidate.repoTags.map((tag) => `repo:${tag}`),
4350
+ ...candidate.workflowTags.map((tag) => `workflow:${tag}`),
4351
+ ],
4352
+ title: candidate.title,
4353
+ reviewState,
4354
+ createdAt: plan.createdAt,
4355
+ });
4356
+ const lifecycle =
4357
+ existing === undefined
4358
+ ? nextLifecycle
4359
+ : {
4360
+ ...nextLifecycle,
4361
+ createdAt: existing.lifecycle.createdAt,
4362
+ supersedes: existing.lifecycle.supersedes,
4363
+ supersededBy: existing.lifecycle.supersededBy,
4364
+ };
4365
+ const verificationSnapshot = createOkfKnowledgeVerificationSnapshot({
4366
+ candidate,
4367
+ verifiedAt: plan.createdAt,
4368
+ projectKey: plan.projectKey,
4369
+ });
3460
4370
  return [
3461
4371
  "---",
3462
4372
  `type: ${yamlString(candidate.okfType)}`,
@@ -3475,24 +4385,10 @@ function renderOkfConcept(candidate: OkfKnowledgePlanCandidate, plan: OkfKnowled
3475
4385
  "evodev:",
3476
4386
  ` schema: ${yamlString("knowledge/v1")}`,
3477
4387
  ` stableKey: ${yamlString(candidate.stableKey)}`,
3478
- ` reviewState: ${yamlString(resolveCandidateReviewStateForWrite(candidate))}`,
3479
- renderLifecycleYaml(
3480
- " ",
3481
- createDefaultOkfLifecycle({
3482
- type: candidate.okfType,
3483
- path: candidate.targetPath,
3484
- tags: [
3485
- "evodev",
3486
- candidate.kind,
3487
- ...candidate.roleTags.map((tag) => `role:${tag}`),
3488
- ...candidate.repoTags.map((tag) => `repo:${tag}`),
3489
- ...candidate.workflowTags.map((tag) => `workflow:${tag}`),
3490
- ],
3491
- title: candidate.title,
3492
- reviewState: resolveCandidateReviewStateForWrite(candidate),
3493
- createdAt: plan.createdAt,
3494
- }),
3495
- ),
4388
+ ` reviewState: ${yamlString(reviewState)}`,
4389
+ renderLifecycleYaml(" ", lifecycle),
4390
+ renderKnowledgeSupportRefYaml(" ", candidate.supportRef ?? null),
4391
+ renderVerificationSnapshotYaml(" ", verificationSnapshot),
3496
4392
  " source:",
3497
4393
  ` kind: ${yamlString("trace-distillation")}`,
3498
4394
  ` projectKey: ${yamlString(plan.projectKey)}`,
@@ -3504,7 +4400,7 @@ function renderOkfConcept(candidate: OkfKnowledgePlanCandidate, plan: OkfKnowled
3504
4400
  renderYamlList(" roleTags", candidate.roleTags),
3505
4401
  renderYamlList(" workflowTags", candidate.workflowTags),
3506
4402
  renderYamlList(" pathScopes", candidate.pathScopes),
3507
- renderPrivacyYaml(" "),
4403
+ renderPrivacyYaml(" ", candidate.privacyCheck),
3508
4404
  " organizer:",
3509
4405
  ` lastAction: ${yamlString(candidate.decision)}`,
3510
4406
  ` lastRunId: ${yamlString(plan.runId)}`,
@@ -3523,17 +4419,27 @@ function renderOkfConcept(candidate: OkfKnowledgePlanCandidate, plan: OkfKnowled
3523
4419
  "",
3524
4420
  candidate.bodySections.summary,
3525
4421
  "",
4422
+ "# Claim",
4423
+ "",
4424
+ candidate.claim,
4425
+ "",
3526
4426
  "# Applies When",
3527
4427
  "",
3528
4428
  renderMarkdownList(candidate.bodySections.appliesWhen),
3529
4429
  "",
4430
+ "# How to Apply",
4431
+ "",
4432
+ candidate.howToApply,
4433
+ "",
3530
4434
  "# Guidance",
3531
4435
  "",
3532
4436
  renderMarkdownList(candidate.bodySections.guidance),
3533
4437
  "",
3534
4438
  "# Anti-Criteria",
3535
4439
  "",
3536
- renderMarkdownList(candidate.bodySections.antiCriteria),
4440
+ renderMarkdownList(
4441
+ uniqueStrings([...candidate.antiCriteria, ...candidate.bodySections.antiCriteria]),
4442
+ ),
3537
4443
  "",
3538
4444
  "# Verification",
3539
4445
  "",
@@ -3759,6 +4665,8 @@ function parseOkfConceptFile(
3759
4665
  reviewState,
3760
4666
  lifecycle: lifecycleParsed.lifecycle,
3761
4667
  lifecyclePersisted: lifecycleParsed.persisted,
4668
+ verificationSnapshot: parseOkfVerificationSnapshot(frontmatter),
4669
+ supportRef: parseOkfKnowledgeSupportRef(frontmatter),
3762
4670
  title,
3763
4671
  description,
3764
4672
  tags,
@@ -3770,15 +4678,56 @@ function parseOkfConceptFile(
3770
4678
  };
3771
4679
  }
3772
4680
 
4681
+ function parseOkfKnowledgeSupportRef(frontmatter: string): OkfKnowledgeSupportRef | null {
4682
+ const block = extractNestedYamlBlock(frontmatter, "evodev", "supportRef");
4683
+ if (block === null) return null;
4684
+ try {
4685
+ return normalizeKnowledgeSupportRef(
4686
+ {
4687
+ id: readIndentedYamlScalar(block, "id"),
4688
+ kind: readIndentedYamlScalar(block, "kind"),
4689
+ sourceRefId: readIndentedYamlScalar(block, "sourceRefId"),
4690
+ subjectFingerprint: readNullableLifecycleString(
4691
+ readIndentedYamlScalar(block, "subjectFingerprint"),
4692
+ ),
4693
+ observedAt: readIndentedYamlScalar(block, "observedAt"),
4694
+ },
4695
+ "evodev.supportRef",
4696
+ );
4697
+ } catch {
4698
+ return null;
4699
+ }
4700
+ }
4701
+
4702
+ function parseOkfVerificationSnapshot(
4703
+ frontmatter: string,
4704
+ ): OkfKnowledgeVerificationSnapshotV1 | null {
4705
+ const block = extractNestedYamlBlock(frontmatter, "evodev", "verificationSnapshot");
4706
+ if (block === null) return null;
4707
+ const schemaVersion = readIndentedYamlScalar(block, "schemaVersion");
4708
+ const verifiedAt = normalizeIsoDateString(readIndentedYamlScalar(block, "verifiedAt"));
4709
+ if (schemaVersion !== "1" || verifiedAt === null) return null;
4710
+ return {
4711
+ schemaVersion: 1,
4712
+ verifiedAt,
4713
+ evidenceRefs: readYamlList(block, "evidenceRefs"),
4714
+ repository: null,
4715
+ };
4716
+ }
4717
+
3773
4718
  function matchesConceptFilters(
3774
4719
  concept: OkfKnowledgeConcept,
3775
4720
  input: { projectKey?: string; roleId?: string; workflowId?: string; paths?: string[] },
4721
+ equivalentProjectKeys?: string[],
3776
4722
  ): boolean {
4723
+ const projectKeys = (equivalentProjectKeys ?? []).map(sanitizeSlug);
3777
4724
  if (
3778
4725
  input.projectKey !== undefined &&
3779
4726
  concept.repoTags.length > 0 &&
3780
- !concept.repoTags.includes(sanitizeSlug(input.projectKey)) &&
3781
- !concept.tags.includes(`repo:${sanitizeSlug(input.projectKey)}`)
4727
+ ![sanitizeSlug(input.projectKey), ...projectKeys].some(
4728
+ (projectKey) =>
4729
+ concept.repoTags.includes(projectKey) || concept.tags.includes(`repo:${projectKey}`),
4730
+ )
3782
4731
  ) {
3783
4732
  return false;
3784
4733
  }
@@ -3806,14 +4755,40 @@ function matchesConceptFilters(
3806
4755
  return true;
3807
4756
  }
3808
4757
 
3809
- function normalizeKnowledgeQueryScope(input: {
4758
+ async function normalizeKnowledgeQueryScope(input: {
4759
+ homeDir: string;
3810
4760
  projectKey?: string;
3811
4761
  roleId?: string;
3812
4762
  workflowId?: string;
3813
4763
  paths?: string[];
3814
- }): KnowledgeQueryScope {
4764
+ }): Promise<KnowledgeQueryScope> {
4765
+ const aliases =
4766
+ input.projectKey === undefined
4767
+ ? {}
4768
+ : await listProjectAliases({
4769
+ homeDir: input.homeDir,
4770
+ });
4771
+ const requestedProjectKey =
4772
+ input.projectKey === undefined ? undefined : sanitizeSlug(input.projectKey);
4773
+ const projectKey =
4774
+ requestedProjectKey === undefined
4775
+ ? undefined
4776
+ : canonicalizeProjectKey(requestedProjectKey, aliases);
4777
+ const projectKeys =
4778
+ projectKey === undefined
4779
+ ? undefined
4780
+ : [
4781
+ ...new Set([
4782
+ projectKey,
4783
+ requestedProjectKey ?? projectKey,
4784
+ ...Object.keys(aliases).filter(
4785
+ (alias) => canonicalizeProjectKey(alias, aliases) === projectKey,
4786
+ ),
4787
+ ]),
4788
+ ].map(sanitizeSlug);
3815
4789
  return {
3816
- projectKey: input.projectKey === undefined ? undefined : sanitizeSlug(input.projectKey),
4790
+ projectKey,
4791
+ projectKeys,
3817
4792
  roleId: input.roleId === undefined ? undefined : sanitizeSlug(input.roleId),
3818
4793
  workflowId: input.workflowId === undefined ? undefined : sanitizeSlug(input.workflowId),
3819
4794
  paths: (input.paths ?? []).flatMap((path) => {
@@ -3839,6 +4814,9 @@ function createKnowledgeContextRevision(items: ScopedKnowledgeContextPackItem[])
3839
4814
  sourceLink: item.sourceLink,
3840
4815
  section: item.section,
3841
4816
  title: item.title,
4817
+ freshness: item.freshness,
4818
+ loader: item.loader,
4819
+ delivery: item.delivery,
3842
4820
  matchReasons: item.matchReasons,
3843
4821
  })),
3844
4822
  ),
@@ -4011,10 +4989,8 @@ function summarizeLexicalBody(body: string): string {
4011
4989
  }
4012
4990
 
4013
4991
  function sanitizeLexicalReason(value: string): string {
4014
- return value
4015
- .toLowerCase()
4016
- .replace(new RegExp(PRIVATE_OR_INTERNAL_URL.source, "gi"), "redacted")
4017
- .replace(new RegExp(FORBIDDEN_OKF_TEXT.source, "gi"), "redacted")
4992
+ return redactSessionMemoryCredentialText(value)
4993
+ .value.toLowerCase()
4018
4994
  .replace(/[^a-z0-9:._/-]+/gu, "-")
4019
4995
  .replace(/-+/gu, "-")
4020
4996
  .replace(/^-+|-+$/gu, "")
@@ -4036,8 +5012,10 @@ function matchOkfConceptForContext(
4036
5012
 
4037
5013
  const repoTags = collectScopedTags(concept, "repo");
4038
5014
  if (scope.projectKey !== undefined) {
4039
- if (repoTags.length > 0 && !repoTags.includes(scope.projectKey)) return null;
4040
- if (repoTags.includes(scope.projectKey)) {
5015
+ const projectKeys = scope.projectKeys ?? [scope.projectKey];
5016
+ const matchedProjectKey = projectKeys.find((projectKey) => repoTags.includes(projectKey));
5017
+ if (repoTags.length > 0 && matchedProjectKey === undefined) return null;
5018
+ if (matchedProjectKey !== undefined) {
4041
5019
  reasons.push(`repo:${scope.projectKey}`);
4042
5020
  exactMatches += 1;
4043
5021
  }
@@ -4109,7 +5087,12 @@ function matchOverlayScope(
4109
5087
  ): "repo" | "role" | "workflow" | "none" | null {
4110
5088
  const [root, id] = conceptId.split("/");
4111
5089
  if (root === "repos") {
4112
- if (scope.projectKey !== undefined && id !== scope.projectKey) return null;
5090
+ if (
5091
+ scope.projectKey !== undefined &&
5092
+ !(scope.projectKeys ?? [scope.projectKey]).includes(id ?? "")
5093
+ ) {
5094
+ return null;
5095
+ }
4113
5096
  return scope.projectKey === undefined ? "none" : "repo";
4114
5097
  }
4115
5098
  if (root === "roles") {
@@ -4175,10 +5158,7 @@ function isUnsafeOkfQueryContent(content: string): boolean {
4175
5158
  if (OKF_QUERY_PRIVACY_FLAG_KEYS.some((key) => readOkfPrivacyBoolean(frontmatter, key) === true)) {
4176
5159
  return true;
4177
5160
  }
4178
- if (FORBIDDEN_OKF_FIELD.test(content)) return true;
4179
- if (FORBIDDEN_OKF_TEXT.test(content)) return true;
4180
- if (PRIVATE_OR_INTERNAL_URL.test(content)) return true;
4181
- return false;
5161
+ return detectSessionMemorySensitivity(content).classification === "credential";
4182
5162
  }
4183
5163
 
4184
5164
  function isActiveOkfConcept(concept: OkfKnowledgeConcept): boolean {
@@ -4264,8 +5244,11 @@ function readIndentedYamlBoolean(frontmatter: string, key: string): boolean | nu
4264
5244
  return null;
4265
5245
  }
4266
5246
 
4267
- function validateOkfKnowledgePlan(plan: OkfKnowledgePlan): void {
4268
- assertOkfKnowledgePlanContract(plan);
5247
+ function validateOkfKnowledgePlan(
5248
+ plan: OkfKnowledgePlan,
5249
+ options: { allowReviewedHighRisk?: boolean } = {},
5250
+ ): void {
5251
+ assertOkfKnowledgePlanContract(plan, options);
4269
5252
  }
4270
5253
 
4271
5254
  function validateFailedPlanArtifact(artifact: OkfKnowledgeFailedPlanArtifact): void {
@@ -4323,10 +5306,12 @@ function assertOkfPrivacy(privacy: OkfKnowledgePrivacyCheck): void {
4323
5306
  privacy.rawLogsStored !== false ||
4324
5307
  privacy.sourceDumpsStored !== false ||
4325
5308
  privacy.rawCommandOutputStored !== false ||
4326
- privacy.secretsStored !== false ||
4327
- privacy.internalLinksStored !== false
5309
+ privacy.secretsStored !== false
4328
5310
  ) {
4329
- throw new Error("OKF knowledge privacy fields must all be false.");
5311
+ throw new Error("OKF knowledge privacy fields must forbid raw content and credentials.");
5312
+ }
5313
+ if (typeof privacy.internalLinksStored !== "boolean") {
5314
+ throw new Error("OKF knowledge internalLinksStored must be boolean.");
4330
5315
  }
4331
5316
  }
4332
5317
 
@@ -4481,6 +5466,35 @@ function renderLifecycleYaml(indent: string, lifecycle: OkfKnowledgeLifecycle):
4481
5466
  ].join("\n");
4482
5467
  }
4483
5468
 
5469
+ function renderVerificationSnapshotYaml(
5470
+ indent: string,
5471
+ snapshot: OkfKnowledgeVerificationSnapshotV1 | null,
5472
+ ): string {
5473
+ if (snapshot === null) return `${indent}verificationSnapshot: null`;
5474
+ return [
5475
+ `${indent}verificationSnapshot:`,
5476
+ `${indent} schemaVersion: 1`,
5477
+ `${indent} verifiedAt: ${yamlString(snapshot.verifiedAt)}`,
5478
+ renderYamlList(`${indent} evidenceRefs`, snapshot.evidenceRefs),
5479
+ `${indent} repository: null`,
5480
+ ].join("\n");
5481
+ }
5482
+
5483
+ function renderKnowledgeSupportRefYaml(
5484
+ indent: string,
5485
+ supportRef: OkfKnowledgeSupportRef | null,
5486
+ ): string {
5487
+ if (supportRef === null) return `${indent}supportRef: null`;
5488
+ return [
5489
+ `${indent}supportRef:`,
5490
+ `${indent} id: ${yamlString(supportRef.id)}`,
5491
+ `${indent} kind: ${yamlString(supportRef.kind)}`,
5492
+ `${indent} sourceRefId: ${yamlString(supportRef.sourceRefId)}`,
5493
+ `${indent} subjectFingerprint: ${yamlNullableString(supportRef.subjectFingerprint)}`,
5494
+ `${indent} observedAt: ${yamlString(supportRef.observedAt)}`,
5495
+ ].join("\n");
5496
+ }
5497
+
4484
5498
  function lifecycleStatusFromReviewState(
4485
5499
  reviewState: OkfKnowledgeReviewState,
4486
5500
  ): OkfKnowledgeLifecycleStatus {
@@ -4531,7 +5545,10 @@ function yamlNullableString(value: string | null): string {
4531
5545
  return value === null ? "null" : yamlString(value);
4532
5546
  }
4533
5547
 
4534
- function renderPrivacyYaml(indent: string): string {
5548
+ function renderPrivacyYaml(
5549
+ indent: string,
5550
+ privacy: OkfKnowledgePrivacyCheck = createOkfPrivacyCheck(),
5551
+ ): string {
4535
5552
  return [
4536
5553
  `${indent}privacy:`,
4537
5554
  `${indent} classification: ${yamlString("local-private")}`,
@@ -4540,7 +5557,7 @@ function renderPrivacyYaml(indent: string): string {
4540
5557
  `${indent} sourceDumpsStored: false`,
4541
5558
  `${indent} rawCommandOutputStored: false`,
4542
5559
  `${indent} secretsStored: false`,
4543
- `${indent} internalLinksStored: false`,
5560
+ `${indent} internalLinksStored: ${privacy.internalLinksStored ? "true" : "false"}`,
4544
5561
  ].join("\n");
4545
5562
  }
4546
5563
 
@@ -4655,6 +5672,300 @@ function parseYamlValue(value: string): string {
4655
5672
  return trimmed.replace(/^['"]|['"]$/gu, "");
4656
5673
  }
4657
5674
 
5675
+ type LegacyCleanupArtifactKind = "okf-concept" | "evos-case" | "scope-overlay";
5676
+ type LegacyCleanupArtifactAction = "quarantine" | "rewrite";
5677
+
5678
+ interface LegacyCleanupArtifact {
5679
+ kind: LegacyCleanupArtifactKind;
5680
+ action: LegacyCleanupArtifactAction;
5681
+ sourcePath: string;
5682
+ relativePath: string;
5683
+ content: string;
5684
+ sha256: string;
5685
+ }
5686
+
5687
+ interface LegacyCleanupOverlayArtifact extends LegacyCleanupArtifact {
5688
+ kind: "scope-overlay";
5689
+ cleanedContent: string;
5690
+ }
5691
+
5692
+ interface LegacyGeneratedKnowledgeCleanupPlan {
5693
+ generatedAt: string;
5694
+ concepts: LegacyCleanupArtifact[];
5695
+ evosCases: LegacyCleanupArtifact[];
5696
+ overlaysToQuarantine: LegacyCleanupOverlayArtifact[];
5697
+ overlaysToRewrite: LegacyCleanupOverlayArtifact[];
5698
+ }
5699
+
5700
+ async function createLegacyGeneratedKnowledgeCleanupPlan(
5701
+ homeDir: string,
5702
+ generatedAt: string,
5703
+ ): Promise<LegacyGeneratedKnowledgeCleanupPlan> {
5704
+ const evoDevPaths = resolveEvoDevPaths(homeDir);
5705
+ const okfPaths = resolveOkfKnowledgePaths(homeDir);
5706
+ const concepts: LegacyCleanupArtifact[] = [];
5707
+ const conceptRoot = join(okfPaths.okfDir, "concepts", "evos");
5708
+ for (const file of await listMarkdownFiles(conceptRoot)) {
5709
+ if (RESERVED_OKF_FILENAMES.has(file.name)) continue;
5710
+ const content = await readFile(file.path, "utf8");
5711
+ if (!isLegacyGeneratedOkfConcept(file.name, content)) continue;
5712
+ concepts.push(
5713
+ createLegacyCleanupArtifact({
5714
+ kind: "okf-concept",
5715
+ action: "quarantine",
5716
+ sourcePath: file.path,
5717
+ rootDir: evoDevPaths.rootDir,
5718
+ content,
5719
+ }),
5720
+ );
5721
+ }
5722
+
5723
+ const evosCases: LegacyCleanupArtifact[] = [];
5724
+ for (const file of await listJsonFiles(evoDevPaths.evosCasesDir)) {
5725
+ const content = await readFile(file.path, "utf8");
5726
+ if (!isLegacyGeneratedEvosCase(content)) continue;
5727
+ evosCases.push(
5728
+ createLegacyCleanupArtifact({
5729
+ kind: "evos-case",
5730
+ action: "quarantine",
5731
+ sourcePath: file.path,
5732
+ rootDir: evoDevPaths.rootDir,
5733
+ content,
5734
+ }),
5735
+ );
5736
+ }
5737
+
5738
+ const conceptLinks = new Set(
5739
+ concepts.map((concept) => {
5740
+ const okfRelative = relative(okfPaths.okfDir, concept.sourcePath).replace(/\\/gu, "/");
5741
+ return `/${okfRelative}`;
5742
+ }),
5743
+ );
5744
+ const overlaysToQuarantine: LegacyCleanupOverlayArtifact[] = [];
5745
+ const overlaysToRewrite: LegacyCleanupOverlayArtifact[] = [];
5746
+ for (const root of ["repos", "roles", "workflows"]) {
5747
+ for (const file of await listMarkdownFiles(join(okfPaths.okfDir, root))) {
5748
+ if (RESERVED_OKF_FILENAMES.has(file.name)) continue;
5749
+ const content = await readFile(file.path, "utf8");
5750
+ const cleanedContent = removeLegacyOverlayLinks(content, conceptLinks);
5751
+ if (cleanedContent === content) continue;
5752
+ const action = isGeneratedEmptyScopeOverlay(cleanedContent) ? "quarantine" : "rewrite";
5753
+ const artifact = {
5754
+ ...createLegacyCleanupArtifact({
5755
+ kind: "scope-overlay",
5756
+ action,
5757
+ sourcePath: file.path,
5758
+ rootDir: evoDevPaths.rootDir,
5759
+ content,
5760
+ }),
5761
+ kind: "scope-overlay" as const,
5762
+ cleanedContent,
5763
+ };
5764
+ if (action === "quarantine") overlaysToQuarantine.push(artifact);
5765
+ else overlaysToRewrite.push(artifact);
5766
+ }
5767
+ }
5768
+
5769
+ const byPath = (left: LegacyCleanupArtifact, right: LegacyCleanupArtifact) =>
5770
+ left.sourcePath.localeCompare(right.sourcePath);
5771
+ return {
5772
+ generatedAt,
5773
+ concepts: concepts.sort(byPath),
5774
+ evosCases: evosCases.sort(byPath),
5775
+ overlaysToQuarantine: overlaysToQuarantine.sort(byPath),
5776
+ overlaysToRewrite: overlaysToRewrite.sort(byPath),
5777
+ };
5778
+ }
5779
+
5780
+ function createLegacyCleanupArtifact(input: {
5781
+ kind: LegacyCleanupArtifactKind;
5782
+ action: LegacyCleanupArtifactAction;
5783
+ sourcePath: string;
5784
+ rootDir: string;
5785
+ content: string;
5786
+ }): LegacyCleanupArtifact {
5787
+ const relativePath = relative(input.rootDir, input.sourcePath).replace(/\\/gu, "/");
5788
+ if (relativePath === "" || relativePath.startsWith("../") || isAbsolute(relativePath)) {
5789
+ throw new Error(`Legacy cleanup path escaped EvoDev root: ${input.sourcePath}`);
5790
+ }
5791
+ return {
5792
+ kind: input.kind,
5793
+ action: input.action,
5794
+ sourcePath: input.sourcePath,
5795
+ relativePath,
5796
+ content: input.content,
5797
+ sha256: createHash("sha256").update(input.content).digest("hex"),
5798
+ };
5799
+ }
5800
+
5801
+ function toLegacyCleanupPreview(
5802
+ plan: LegacyGeneratedKnowledgeCleanupPlan,
5803
+ ): LegacyGeneratedKnowledgeCleanupPreview {
5804
+ return {
5805
+ generatedAt: plan.generatedAt,
5806
+ conceptPaths: plan.concepts.map((artifact) => artifact.sourcePath),
5807
+ evosCasePaths: plan.evosCases.map((artifact) => artifact.sourcePath),
5808
+ overlayPathsToQuarantine: plan.overlaysToQuarantine.map((artifact) => artifact.sourcePath),
5809
+ overlayPathsToRewrite: plan.overlaysToRewrite.map((artifact) => artifact.sourcePath),
5810
+ totalArtifacts:
5811
+ plan.concepts.length +
5812
+ plan.evosCases.length +
5813
+ plan.overlaysToQuarantine.length +
5814
+ plan.overlaysToRewrite.length,
5815
+ };
5816
+ }
5817
+
5818
+ function createLegacyCleanupManifestItem(
5819
+ artifact: LegacyCleanupArtifact,
5820
+ quarantineDir: string,
5821
+ ): {
5822
+ kind: LegacyCleanupArtifactKind;
5823
+ action: LegacyCleanupArtifactAction;
5824
+ originalRelativePath: string;
5825
+ quarantineRelativePath: string;
5826
+ sha256: string;
5827
+ } {
5828
+ const targetPath = resolveLegacyCleanupQuarantinePath(quarantineDir, artifact);
5829
+ return {
5830
+ kind: artifact.kind,
5831
+ action: artifact.action,
5832
+ originalRelativePath: artifact.relativePath,
5833
+ quarantineRelativePath: relative(quarantineDir, targetPath).replace(/\\/gu, "/"),
5834
+ sha256: artifact.sha256,
5835
+ };
5836
+ }
5837
+
5838
+ function resolveLegacyCleanupQuarantinePath(
5839
+ quarantineDir: string,
5840
+ artifact: LegacyCleanupArtifact,
5841
+ ): string {
5842
+ const filesDir = join(quarantineDir, "files");
5843
+ const targetPath = join(filesDir, artifact.relativePath);
5844
+ const targetRelative = relative(filesDir, targetPath);
5845
+ if (targetRelative === "" || targetRelative.startsWith("..") || isAbsolute(targetRelative)) {
5846
+ throw new Error(`Unsafe legacy cleanup quarantine path: ${artifact.relativePath}`);
5847
+ }
5848
+ return targetPath;
5849
+ }
5850
+
5851
+ function isLegacyGeneratedOkfConcept(fileName: string, content: string): boolean {
5852
+ const id = fileName.match(/^((?:k|evo)-[a-f0-9]{16})\.md$/u)?.[1];
5853
+ if (id === undefined) return false;
5854
+ if (!/^type: "EvoDev Evolution Case"$/mu.test(content)) return false;
5855
+ if (!/^ {2}reviewState: "auto-accepted"$/mu.test(content)) return false;
5856
+ if (!/^ {4}kind: "trace-distillation"$/mu.test(content)) return false;
5857
+ if (!content.includes(`resource: "evodev://concepts/evos/${id}"`)) return false;
5858
+ if (id.startsWith("k-")) {
5859
+ return (
5860
+ /^title: "Project run segment-run-[a-f0-9]{16} evidence summary"$/mu.test(content) &&
5861
+ /^ {2}stableKey: "run-summary:[^"]+:k-[a-f0-9]{16}"$/mu.test(content) &&
5862
+ content.includes(
5863
+ "This record is derived from metadata-only evidence; active use depends on OKF plan review state.",
5864
+ )
5865
+ );
5866
+ }
5867
+ return (
5868
+ /^title: "Run segment-run-[a-f0-9]{16} evolution case"$/mu.test(content) &&
5869
+ /^ {2}stableKey: "evos:[^"]+:evo-[a-f0-9]{16}"$/mu.test(content) &&
5870
+ content.includes(
5871
+ "Future role agents should inspect accepted knowledge first and treat unreviewed cases as contextual evidence only.",
5872
+ )
5873
+ );
5874
+ }
5875
+
5876
+ function isLegacyGeneratedEvosCase(content: string): boolean {
5877
+ let value: unknown;
5878
+ try {
5879
+ value = JSON.parse(content) as unknown;
5880
+ } catch {
5881
+ return false;
5882
+ }
5883
+ if (!isRecord(value) || value.schemaVersion !== 1 || value.kind !== "evos-case") return false;
5884
+ if (typeof value.id !== "string" || !/^evo-[a-f0-9]{16}$/u.test(value.id)) return false;
5885
+ if (!isRecord(value.trigger) || typeof value.trigger.summary !== "string") return false;
5886
+ if (!isRecord(value.intervention) || typeof value.intervention.summary !== "string") return false;
5887
+ if (!isRecord(value.result) || typeof value.result.summary !== "string") return false;
5888
+ if (!isRecord(value.provenance) || typeof value.provenance.runId !== "string") return false;
5889
+ return (
5890
+ /^Evolution distillation used (?:conditional|strong) trigger evidence: .+\.$/u.test(
5891
+ value.trigger.summary,
5892
+ ) &&
5893
+ /^Collected \d+ metadata event\(s\) and \d+ episode\(s\) from EvoDev execution logs\.$/u.test(
5894
+ value.intervention.summary,
5895
+ ) &&
5896
+ /^(?:No failure signal was detected in the redacted evidence summaries|\d+ potential failure or issue signal\(s\) were detected)\.$/u.test(
5897
+ value.result.summary,
5898
+ ) &&
5899
+ value.expectedFutureBehavior ===
5900
+ "Future role agents should inspect accepted knowledge first and treat unreviewed cases as contextual evidence only." &&
5901
+ /^segment-run-[a-f0-9]{16}$/u.test(value.provenance.runId)
5902
+ );
5903
+ }
5904
+
5905
+ function removeLegacyOverlayLinks(content: string, conceptLinks: Set<string>): string {
5906
+ if (conceptLinks.size === 0) return content;
5907
+ const trailingNewline = content.endsWith("\n");
5908
+ const lines = content.split("\n");
5909
+ const filtered = lines.filter(
5910
+ (line) =>
5911
+ ![...conceptLinks].some(
5912
+ (link) => line.trimStart().startsWith("- [") && line.includes(`](${link})`),
5913
+ ),
5914
+ );
5915
+ const cleaned = filtered.join("\n");
5916
+ return trailingNewline && !cleaned.endsWith("\n") ? `${cleaned}\n` : cleaned;
5917
+ }
5918
+
5919
+ function isGeneratedEmptyScopeOverlay(content: string): boolean {
5920
+ const frontmatter = extractFrontmatter(content);
5921
+ if (frontmatter === null) return false;
5922
+ if (!/type: "EvoDev (?:Repo|Role|Workflow) Attention"/u.test(frontmatter.frontmatter)) {
5923
+ return false;
5924
+ }
5925
+ if (!/^ {4}kind: "okf-organizer"$/mu.test(frontmatter.frontmatter)) return false;
5926
+ return (
5927
+ frontmatter.body.replace(/\s+/gu, " ").trim() ===
5928
+ "# Attention This overlay links scope-specific attention to canonical EvoDev knowledge. # Linked Core Knowledge"
5929
+ );
5930
+ }
5931
+
5932
+ async function rebuildEvolutionEvosIndexForCleanup(
5933
+ homeDir: string,
5934
+ updatedAt: string,
5935
+ ): Promise<string> {
5936
+ const paths = resolveEvoDevPaths(homeDir);
5937
+ const result = await listEvolutionEvosCases({
5938
+ homeDir,
5939
+ reviewStates: [...REVIEW_STATES],
5940
+ });
5941
+ const grouped = new Map<
5942
+ string,
5943
+ Array<{ id: string; title: string; reviewState: EvolutionEvosCase["reviewState"] }>
5944
+ >();
5945
+ for (const evosCase of result.cases) {
5946
+ grouped.set(evosCase.projectKey, [
5947
+ ...(grouped.get(evosCase.projectKey) ?? []),
5948
+ { id: evosCase.id, title: evosCase.title, reviewState: evosCase.reviewState },
5949
+ ]);
5950
+ }
5951
+ await writeJson(
5952
+ paths.evosIndexPath,
5953
+ {
5954
+ schemaVersion: 1,
5955
+ updatedAt,
5956
+ projects: [...grouped.entries()]
5957
+ .sort(([left], [right]) => left.localeCompare(right))
5958
+ .map(([projectKey, cases]) => ({ projectKey, cases })),
5959
+ },
5960
+ { overwrite: true },
5961
+ );
5962
+ return paths.evosIndexPath;
5963
+ }
5964
+
5965
+ function isAlreadyExistsError(error: unknown): boolean {
5966
+ return error instanceof Error && "code" in error && error.code === "EEXIST";
5967
+ }
5968
+
4658
5969
  async function listMarkdownFiles(root: string): Promise<Array<{ path: string; name: string }>> {
4659
5970
  if (!(await pathExists(root))) return [];
4660
5971
  const entries = await readdir(root, { withFileTypes: true });
@@ -4670,6 +5981,21 @@ async function listMarkdownFiles(root: string): Promise<Array<{ path: string; na
4670
5981
  return files;
4671
5982
  }
4672
5983
 
5984
+ async function listJsonFiles(root: string): Promise<Array<{ path: string; name: string }>> {
5985
+ if (!(await pathExists(root))) return [];
5986
+ const entries = await readdir(root, { withFileTypes: true });
5987
+ const files: Array<{ path: string; name: string }> = [];
5988
+ for (const entry of entries) {
5989
+ const path = join(root, entry.name);
5990
+ if (entry.isDirectory()) {
5991
+ files.push(...(await listJsonFiles(path)));
5992
+ } else if (entry.isFile() && entry.name.endsWith(".json")) {
5993
+ files.push({ path, name: entry.name });
5994
+ }
5995
+ }
5996
+ return files;
5997
+ }
5998
+
4673
5999
  async function listDirectories(root: string): Promise<string[]> {
4674
6000
  if (!(await pathExists(root))) return [];
4675
6001
  const entries = await readdir(root, { withFileTypes: true });