@evo-dev/core 0.0.1-alpha.1 → 0.0.1-alpha.3

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.
@@ -0,0 +1,4784 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
3
+ import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
4
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
5
+ import { resolveEvoDevPaths } from "../config/paths.ts";
6
+ import {
7
+ type EvolutionDistillationBatch,
8
+ type EvolutionEvosCase,
9
+ type EvolutionKnowledgeRecord,
10
+ listEvolutionEvosCases,
11
+ } from "../evolution/index.ts";
12
+
13
+ export type OkfKnowledgeDecision =
14
+ | "auto-accept"
15
+ | "no_write"
16
+ | "create"
17
+ | "update"
18
+ | "skip"
19
+ | "needs-human";
20
+ export type OkfKnowledgeTargetStore = "okf" | "repo-asset-proposal" | "evo-eval-set" | "none";
21
+ export type OkfKnowledgeReviewState =
22
+ | "auto-accepted"
23
+ | "accepted"
24
+ | "needs-human"
25
+ | "rejected"
26
+ | "deferred"
27
+ | "stale"
28
+ | "deprecated"
29
+ | "revoked"
30
+ | "superseded"
31
+ | "auto-stored/unreviewed";
32
+
33
+ export type OkfKnowledgeLifecycleStatus =
34
+ | "active"
35
+ | "stale"
36
+ | "deprecated"
37
+ | "revoked"
38
+ | "superseded";
39
+
40
+ export interface OkfKnowledgeLifecycle {
41
+ status: OkfKnowledgeLifecycleStatus;
42
+ createdAt: string;
43
+ lastVerifiedAt: string;
44
+ reviewAfter: string;
45
+ staleAfter: string;
46
+ supersedes: string[];
47
+ supersededBy: string | null;
48
+ revokedAt: string | null;
49
+ revokedReason: string | null;
50
+ }
51
+
52
+ export interface OkfKnowledgeCandidateScores {
53
+ evidenceStrength: number;
54
+ reuseValue: number;
55
+ actionability: number;
56
+ stability: number;
57
+ privacyRisk: number;
58
+ duplicationRisk: number;
59
+ }
60
+
61
+ export interface OkfKnowledgePrivacyCheck {
62
+ rawPromptsStored: false;
63
+ rawLogsStored: false;
64
+ sourceDumpsStored: false;
65
+ rawCommandOutputStored: false;
66
+ secretsStored: false;
67
+ internalLinksStored: false;
68
+ }
69
+
70
+ export interface OkfKnowledgeOverlayUpdate {
71
+ targetPath: string;
72
+ operation: "append-link";
73
+ link: string;
74
+ }
75
+
76
+ export interface OkfKnowledgeEvidenceRef {
77
+ id: string;
78
+ kind: string;
79
+ source: string;
80
+ rawContentStored: false;
81
+ externalContentCopied: false;
82
+ }
83
+
84
+ export interface OkfKnowledgeEvalSet {
85
+ id: string;
86
+ target: {
87
+ kind: string;
88
+ id: string;
89
+ };
90
+ purpose: string;
91
+ roleTags: string[];
92
+ cases: Array<{
93
+ id: string;
94
+ inputRefs: string[];
95
+ assertions: string[];
96
+ expectedReviewState: OkfKnowledgeReviewState;
97
+ }>;
98
+ privacy: {
99
+ usesRawPrompt: false;
100
+ usesSourceDump: false;
101
+ usesRawCommandOutput: false;
102
+ };
103
+ decision: "create" | "needs-human" | "no_write" | "skip";
104
+ }
105
+
106
+ export interface OkfKnowledgePlanCandidate {
107
+ id: string;
108
+ decision: OkfKnowledgeDecision;
109
+ kind: string;
110
+ okfType: string;
111
+ targetStore: OkfKnowledgeTargetStore;
112
+ targetPath: string;
113
+ stableKey: string;
114
+ confidence: "low" | "medium" | "high";
115
+ title: string;
116
+ description: string;
117
+ claim: string;
118
+ basis: "direct" | "inferred";
119
+ metadataOnlyEvidence: boolean;
120
+ howToApply: string;
121
+ antiCriteria: string[];
122
+ roleTags: string[];
123
+ repoTags: string[];
124
+ workflowTags: string[];
125
+ pathScopes: string[];
126
+ relatedConceptLinks: string[];
127
+ overlayUpdates: OkfKnowledgeOverlayUpdate[];
128
+ scores: OkfKnowledgeCandidateScores;
129
+ decisionReason: string;
130
+ evidenceRefs: string[];
131
+ reviewState: OkfKnowledgeReviewState;
132
+ verificationNotApplicableReason?: string;
133
+ evalSetRefs?: string[];
134
+ evalNotApplicableReason?: string;
135
+ bodySections: {
136
+ summary: string;
137
+ appliesWhen: string[];
138
+ guidance: string[];
139
+ antiCriteria: string[];
140
+ verification: string[];
141
+ citations: string[];
142
+ };
143
+ privacyCheck: OkfKnowledgePrivacyCheck;
144
+ }
145
+
146
+ export interface OkfKnowledgePlan {
147
+ schemaVersion: 1;
148
+ kind: "evodev-knowledge-plan";
149
+ projectKey: string;
150
+ runId: string;
151
+ createdAt: string;
152
+ evidenceWindowId: string;
153
+ summary: string;
154
+ evidenceRefs: OkfKnowledgeEvidenceRef[];
155
+ evoEvalSets: OkfKnowledgeEvalSet[];
156
+ candidates: OkfKnowledgePlanCandidate[];
157
+ droppedSignals: Array<{ evidenceRef: string; reason: string }>;
158
+ conflicts: Array<{ candidateId: string; reason: string }>;
159
+ privacyCheck: OkfKnowledgePrivacyCheck;
160
+ }
161
+
162
+ export interface KnowledgeDistillationOutputV1 {
163
+ schemaVersion: 1;
164
+ kind: "knowledge-distillation-output";
165
+ projectKey: string;
166
+ runId: string;
167
+ createdAt: string;
168
+ evidenceWindowId: string;
169
+ summary?: string;
170
+ evidenceRefs?: OkfKnowledgeEvidenceRef[];
171
+ evoEvalSets?: OkfKnowledgeEvalSet[];
172
+ knowledgeCandidates: Array<Omit<OkfKnowledgePlanCandidate, "decision"> & { decision: string }>;
173
+ droppedSignals?: Array<{ evidenceRef: string; reason: string }>;
174
+ conflicts?: Array<{ candidateId: string; reason: string }>;
175
+ privacyCheck: OkfKnowledgePrivacyCheck;
176
+ }
177
+
178
+ export interface OkfKnowledgePlanValidationFinding {
179
+ path: string;
180
+ code: string;
181
+ severity: "error" | "warning";
182
+ message: string;
183
+ }
184
+
185
+ export interface OkfKnowledgePlanValidationResult {
186
+ ok: boolean;
187
+ findings: OkfKnowledgePlanValidationFinding[];
188
+ }
189
+
190
+ export interface OkfKnowledgeFailedPlanArtifact {
191
+ schemaVersion: 1;
192
+ kind: "okf-knowledge-failed-plan";
193
+ failureKind: "validation" | "organizer";
194
+ projectKey: string;
195
+ runId: string;
196
+ createdAt: string;
197
+ resumable: boolean;
198
+ redactedPlan?: unknown;
199
+ plan?: OkfKnowledgePlan;
200
+ findings: OkfKnowledgePlanValidationFinding[];
201
+ error: string;
202
+ }
203
+
204
+ export interface OkfKnowledgeActivationResult {
205
+ planPath: string;
206
+ failedPlanPath: string;
207
+ evidenceWindowPath: string | null;
208
+ conceptPaths: string[];
209
+ overlayPaths: string[];
210
+ skippedCandidates: string[];
211
+ needsHumanCandidates: string[];
212
+ indexPaths: string[];
213
+ derivedIndexPaths: string[];
214
+ logPaths: string[];
215
+ }
216
+
217
+ export interface OkfKnowledgeLifecycleMutationResult {
218
+ conceptPaths: string[];
219
+ logPaths: string[];
220
+ indexPaths: string[];
221
+ }
222
+
223
+ export interface OkfKnowledgeConcept {
224
+ id: string;
225
+ path: string;
226
+ sourceLink: string;
227
+ type: string;
228
+ stableKey: string;
229
+ reviewState: OkfKnowledgeReviewState;
230
+ lifecycle: OkfKnowledgeLifecycle;
231
+ lifecyclePersisted: boolean;
232
+ title: string;
233
+ description: string;
234
+ tags: string[];
235
+ repoTags: string[];
236
+ roleTags: string[];
237
+ workflowTags: string[];
238
+ pathScopes: string[];
239
+ body: string;
240
+ }
241
+
242
+ export interface OkfKnowledgeLintResult {
243
+ ok: boolean;
244
+ errors: string[];
245
+ warnings: string[];
246
+ conceptCount: number;
247
+ }
248
+
249
+ export type OkfKnowledgeContextSection =
250
+ | "applicable-knowledge"
251
+ | "role-attention"
252
+ | "repo-attention"
253
+ | "workflow-attention"
254
+ | "verification"
255
+ | "accepted-evos-cases";
256
+
257
+ export type OkfKnowledgeContextSourceType = "okf" | "evos-case";
258
+
259
+ export interface OkfKnowledgeContextItem {
260
+ id: string;
261
+ sourceType: OkfKnowledgeContextSourceType;
262
+ sourceLink: string;
263
+ section: OkfKnowledgeContextSection;
264
+ rank: number;
265
+ score?: number;
266
+ title: string;
267
+ summary: string;
268
+ matchReasons: string[];
269
+ }
270
+
271
+ export interface OkfKnowledgeQueryResult {
272
+ projectKey?: string;
273
+ roleId?: string;
274
+ workflowId?: string;
275
+ queryText?: string;
276
+ paths: string[];
277
+ items: OkfKnowledgeContextItem[];
278
+ warnings: string[];
279
+ }
280
+
281
+ export interface LexicalKnowledgeDocument {
282
+ id: string;
283
+ sourceType: OkfKnowledgeContextSourceType;
284
+ sourceLink: string;
285
+ section: OkfKnowledgeContextSection;
286
+ title: string;
287
+ description: string;
288
+ tags: string[];
289
+ repoTags: string[];
290
+ roleTags: string[];
291
+ workflowTags: string[];
292
+ pathScopes: string[];
293
+ stableKey: string;
294
+ headings: string[];
295
+ bodySummary: string;
296
+ safeTokens: string[];
297
+ }
298
+
299
+ export interface LexicalKnowledgeScore {
300
+ score: number;
301
+ reasons: string[];
302
+ }
303
+
304
+ export interface ScopedKnowledgeContextPackItem {
305
+ id: string;
306
+ sourceType: OkfKnowledgeContextSourceType;
307
+ sourceLink: string;
308
+ section: OkfKnowledgeContextSection;
309
+ rank: number;
310
+ title: string;
311
+ matchReasons: string[];
312
+ }
313
+
314
+ export interface ScopedKnowledgeContextPackScope {
315
+ projectKey?: string;
316
+ roleId?: string;
317
+ workflowId?: string;
318
+ paths: string[];
319
+ }
320
+
321
+ export interface ScopedKnowledgeContextPack {
322
+ version: 1;
323
+ kind: "evodev-scoped-knowledge-context-pack";
324
+ id: string;
325
+ okfIndexRevision: string;
326
+ scope: ScopedKnowledgeContextPackScope;
327
+ queryText?: string;
328
+ items: ScopedKnowledgeContextPackItem[];
329
+ warnings: string[];
330
+ rawContentStored: false;
331
+ }
332
+
333
+ export type ContextInjectionTrigger = "team-startup" | "hook-safe-point";
334
+
335
+ export interface ContextInjectionReceipt {
336
+ version: 1;
337
+ contextPackId: string;
338
+ okfIndexRevision: string;
339
+ scope: ScopedKnowledgeContextPackScope;
340
+ itemIds: string[];
341
+ injectedAt: string;
342
+ hookEventId: string | null;
343
+ trigger: ContextInjectionTrigger;
344
+ rawContentStored: false;
345
+ }
346
+
347
+ interface OkfPaths {
348
+ knowledgeDir: string;
349
+ okfDir: string;
350
+ indexesDir: string;
351
+ tmpDir: string;
352
+ }
353
+
354
+ interface KnowledgeQueryScope {
355
+ projectKey?: string;
356
+ roleId?: string;
357
+ workflowId?: string;
358
+ paths: string[];
359
+ hasPathFilter: boolean;
360
+ }
361
+
362
+ interface OkfContextMatch {
363
+ reasons: string[];
364
+ exactMatches: number;
365
+ overlayMatch: boolean;
366
+ pathMatch: boolean;
367
+ }
368
+
369
+ interface RankedOkfContextCandidate {
370
+ concept: OkfKnowledgeConcept;
371
+ section: OkfKnowledgeContextSection;
372
+ score: number;
373
+ matchReasons: string[];
374
+ }
375
+
376
+ export interface OkfActiveKnowledgeIndex {
377
+ concepts: Array<{
378
+ id: string;
379
+ sourceLink: string;
380
+ stableKey: string;
381
+ reviewState: OkfKnowledgeReviewState;
382
+ title: string;
383
+ description: string;
384
+ type: string;
385
+ repoTags: string[];
386
+ roleTags: string[];
387
+ workflowTags: string[];
388
+ targetPath: string;
389
+ }>;
390
+ }
391
+
392
+ export interface OkfCandidateConflictOrDuplicate {
393
+ kind: "conflict" | "duplicate";
394
+ conceptId: string;
395
+ reason: string;
396
+ }
397
+
398
+ export interface OkfKnowledgePlanContext {
399
+ batch: EvolutionDistillationBatch;
400
+ activeIndex: OkfActiveKnowledgeIndex;
401
+ }
402
+
403
+ 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
+ const HUMAN_REVIEW_DOMAIN_PATTERN =
411
+ /(^|[^a-z0-9])(?:security|privacy|release|architecture|cross[\s_-]*(?:repo|repository)|model[\s_-]*reflection|reflection[\s_-]*based)([^a-z0-9]|$)/i;
412
+ const OKF_PRIVACY_FLAG_KEYS = [
413
+ "rawPromptsStored",
414
+ "rawLogsStored",
415
+ "sourceDumpsStored",
416
+ "rawCommandOutputStored",
417
+ "secretsStored",
418
+ "internalLinksStored",
419
+ ] as const;
420
+ const OKF_QUERY_PRIVACY_FLAG_KEYS = [
421
+ ...OKF_PRIVACY_FLAG_KEYS,
422
+ "rawOutputStored",
423
+ "sourceContentStored",
424
+ ] as const;
425
+ const ACTIVE_OKF_REVIEW_STATES: readonly OkfKnowledgeReviewState[] = ["accepted", "auto-accepted"];
426
+ const OKF_REVIEW_STATES: readonly OkfKnowledgeReviewState[] = [
427
+ "auto-accepted",
428
+ "accepted",
429
+ "needs-human",
430
+ "rejected",
431
+ "deferred",
432
+ "stale",
433
+ "deprecated",
434
+ "revoked",
435
+ "superseded",
436
+ "auto-stored/unreviewed",
437
+ ];
438
+ const OKF_LIFECYCLE_STATUSES: readonly OkfKnowledgeLifecycleStatus[] = [
439
+ "active",
440
+ "stale",
441
+ "deprecated",
442
+ "revoked",
443
+ "superseded",
444
+ ];
445
+ const OKF_DECISIONS: readonly OkfKnowledgeDecision[] = [
446
+ "auto-accept",
447
+ "no_write",
448
+ "create",
449
+ "update",
450
+ "skip",
451
+ "needs-human",
452
+ ];
453
+ const ACTIVE_OKF_DECISIONS: readonly OkfKnowledgeDecision[] = ["auto-accept", "create", "update"];
454
+ const BEHAVIOR_CHANGE_KINDS = new Set([
455
+ "skill-improvement",
456
+ "role-agent-suggestion",
457
+ "team-suggestion",
458
+ "workflow-improvement",
459
+ "task-split-improvement",
460
+ "tool-use-improvement",
461
+ "repo-asset-suggestion",
462
+ ]);
463
+ const DEFAULT_OKF_CANDIDATE_SCORES: OkfKnowledgeCandidateScores = {
464
+ evidenceStrength: 1,
465
+ reuseValue: 1,
466
+ actionability: 1,
467
+ stability: 1,
468
+ privacyRisk: 5,
469
+ duplicationRisk: 1,
470
+ };
471
+
472
+ export function resolveOkfKnowledgePaths(homeDir: string): OkfPaths {
473
+ const paths = resolveEvoDevPaths(homeDir);
474
+ return {
475
+ knowledgeDir: paths.knowledgeDir,
476
+ okfDir: join(paths.knowledgeDir, "okf"),
477
+ indexesDir: join(paths.knowledgeDir, "indexes"),
478
+ tmpDir: join(paths.knowledgeDir, "tmp"),
479
+ };
480
+ }
481
+
482
+ export async function ensureOkfKnowledgeBase(homeDir: string): Promise<void> {
483
+ const paths = resolveOkfKnowledgePaths(homeDir);
484
+ await ensureLocalKnowledgeGitRepository(paths.knowledgeDir);
485
+ await mkdir(paths.indexesDir, { recursive: true });
486
+ await mkdir(paths.tmpDir, { recursive: true });
487
+
488
+ const directories = [
489
+ {
490
+ path: "",
491
+ title: "EvoDev Knowledge",
492
+ description: "User-local active engineering knowledge.",
493
+ },
494
+ { path: "concepts", title: "Core Concepts", description: "Canonical core knowledge." },
495
+ { path: "concepts/rules", title: "Rules", description: "Normative engineering rules." },
496
+ {
497
+ path: "concepts/decisions",
498
+ title: "Decisions",
499
+ description: "Durable engineering decisions.",
500
+ },
501
+ { path: "concepts/patterns", title: "Patterns", description: "Reusable engineering patterns." },
502
+ { path: "concepts/warnings", title: "Warnings", description: "Risks and anti-patterns." },
503
+ { path: "concepts/checklists", title: "Checklists", description: "Verification checklists." },
504
+ {
505
+ path: "concepts/verification",
506
+ title: "Verification",
507
+ description: "Repeatable verification patterns.",
508
+ },
509
+ { path: "concepts/evos", title: "Evolution Cases", description: "Reviewed evolution cases." },
510
+ { path: "concepts/glossary", title: "Glossary", description: "Terms and taxonomy notes." },
511
+ { path: "repos", title: "Repositories", description: "Repository attention overlays." },
512
+ { path: "roles", title: "Roles", description: "Role attention overlays." },
513
+ { path: "workflows", title: "Workflows", description: "Workflow attention overlays." },
514
+ { path: "references", title: "References", description: "Cited references." },
515
+ ];
516
+
517
+ for (const directory of directories) {
518
+ await ensureOkfDirectory(paths.okfDir, directory.path, directory.title, directory.description);
519
+ }
520
+ await rebuildOkfKnowledgeIndexes({ homeDir });
521
+ }
522
+
523
+ export function createOkfKnowledgePlanFromDistillationBatch(
524
+ batch: EvolutionDistillationBatch,
525
+ options: { homeDir?: string } = {},
526
+ ): OkfKnowledgePlan {
527
+ const activeIndex =
528
+ options.homeDir === undefined
529
+ ? { concepts: [] }
530
+ : loadActiveOkfKnowledgeIndex({ homeDir: options.homeDir });
531
+ const planContext: OkfKnowledgePlanContext = { batch, activeIndex };
532
+ const knowledgeCandidates = batch.knowledgeRecords.map((record) =>
533
+ decideOkfKnowledgeCandidate(createCandidateFromKnowledgeRecord(record, batch), planContext),
534
+ );
535
+ const evosCandidates = batch.evosCases.map((evosCase) =>
536
+ decideOkfKnowledgeCandidate(createCandidateFromEvosCase(evosCase), planContext),
537
+ );
538
+ const rawCandidates = [...knowledgeCandidates, ...evosCandidates];
539
+ const { candidates, evoEvalSets } = attachGeneratedEvalSetsForBehaviorChanges(
540
+ rawCandidates,
541
+ batch,
542
+ );
543
+ const evidenceRefs = batch.evidenceWindow.sourceRefs.map<OkfKnowledgeEvidenceRef>(
544
+ (sourceRef) => ({
545
+ id: sourceRef.id,
546
+ kind: sourceRef.kind,
547
+ source: sourceRef.path,
548
+ rawContentStored: false,
549
+ externalContentCopied: sourceRef.externalContentCopied,
550
+ }),
551
+ );
552
+ return {
553
+ schemaVersion: 1,
554
+ kind: "evodev-knowledge-plan",
555
+ projectKey: sanitizeSlug(batch.projectKey),
556
+ runId: sanitizeSlug(batch.runId),
557
+ createdAt: batch.createdAt,
558
+ evidenceWindowId: batch.evidenceWindow.id,
559
+ summary:
560
+ candidates.length === 0
561
+ ? "No reusable OKF knowledge candidate was selected from this evidence window."
562
+ : `Selected ${candidates.length} reusable OKF knowledge candidate(s).`,
563
+ evidenceRefs,
564
+ evoEvalSets,
565
+ candidates,
566
+ droppedSignals: batch.evidenceWindow.events.slice(0, 20).map((event) => ({
567
+ evidenceRef: event.id,
568
+ reason:
569
+ candidates.length === 0
570
+ ? "No reusable verified knowledge candidate met the activation threshold."
571
+ : "Routine metadata retained only as evidence signal.",
572
+ })),
573
+ conflicts: candidates.flatMap((candidate) =>
574
+ /conflict|duplicate/i.test(candidate.decisionReason)
575
+ ? [{ candidateId: candidate.id, reason: candidate.decisionReason }]
576
+ : [],
577
+ ),
578
+ privacyCheck: createOkfPrivacyCheck(),
579
+ };
580
+ }
581
+
582
+ function attachGeneratedEvalSetsForBehaviorChanges(
583
+ candidates: OkfKnowledgePlanCandidate[],
584
+ batch: EvolutionDistillationBatch,
585
+ ): { candidates: OkfKnowledgePlanCandidate[]; evoEvalSets: OkfKnowledgeEvalSet[] } {
586
+ const evoEvalSets: OkfKnowledgeEvalSet[] = [];
587
+ const updatedCandidates = candidates.map((candidate) => {
588
+ if (
589
+ !ACTIVE_OKF_DECISIONS.includes(candidate.decision) ||
590
+ !isBehaviorChangeCandidate(candidate)
591
+ ) {
592
+ return candidate;
593
+ }
594
+ const evalSet = createGeneratedBehaviorChangeEvalSet(candidate, batch);
595
+ evoEvalSets.push(evalSet);
596
+ return {
597
+ ...candidate,
598
+ evalSetRefs: [...new Set([...(candidate.evalSetRefs ?? []), evalSet.id])],
599
+ };
600
+ });
601
+ return { candidates: updatedCandidates, evoEvalSets };
602
+ }
603
+
604
+ function createGeneratedBehaviorChangeEvalSet(
605
+ candidate: OkfKnowledgePlanCandidate,
606
+ batch: EvolutionDistillationBatch,
607
+ ): OkfKnowledgeEvalSet {
608
+ const evalId = `eval-${sanitizeSlug(candidate.id).replace(/[._/-]+/gu, "-")}`;
609
+ const inputRefs =
610
+ candidate.evidenceRefs.length > 0
611
+ ? candidate.evidenceRefs
612
+ : batch.evidenceWindow.sourceRefs.map((sourceRef) => sourceRef.id);
613
+ return {
614
+ id: evalId,
615
+ target: {
616
+ kind: candidate.kind,
617
+ id: candidate.id,
618
+ },
619
+ purpose: `Guard behavior-changing active OKF write for ${candidate.id}.`,
620
+ roleTags: candidate.roleTags,
621
+ cases: [
622
+ {
623
+ id: `${evalId}-case-1`,
624
+ inputRefs,
625
+ assertions: [
626
+ "Candidate uses metadata-only evidence.",
627
+ "Candidate remains scoped and review-state gated before runtime use.",
628
+ ],
629
+ expectedReviewState: candidate.reviewState,
630
+ },
631
+ ],
632
+ privacy: {
633
+ usesRawPrompt: false,
634
+ usesSourceDump: false,
635
+ usesRawCommandOutput: false,
636
+ },
637
+ decision: "create",
638
+ };
639
+ }
640
+
641
+ export function parseKnowledgeDistillationOutput(
642
+ value: unknown,
643
+ options: { homeDir?: string } = {},
644
+ ): OkfKnowledgePlan {
645
+ const output = assertRecordValue(value, "output") as Partial<KnowledgeDistillationOutputV1>;
646
+ if (output.schemaVersion !== 1) {
647
+ throw new Error("Knowledge distillation output schemaVersion must be 1.");
648
+ }
649
+ if (output.kind !== "knowledge-distillation-output") {
650
+ throw new Error("Knowledge distillation output kind is invalid.");
651
+ }
652
+ if (!Array.isArray(output.knowledgeCandidates)) {
653
+ throw new Error("Knowledge distillation output knowledgeCandidates must be an array.");
654
+ }
655
+
656
+ const plan: OkfKnowledgePlan = {
657
+ schemaVersion: 1,
658
+ kind: "evodev-knowledge-plan",
659
+ projectKey: readRequiredString(output.projectKey, "projectKey"),
660
+ runId: readRequiredString(output.runId, "runId"),
661
+ createdAt: readRequiredString(output.createdAt, "createdAt"),
662
+ evidenceWindowId: readRequiredString(output.evidenceWindowId, "evidenceWindowId"),
663
+ summary:
664
+ typeof output.summary === "string"
665
+ ? sanitizeOkfText(output.summary)
666
+ : `Parsed ${output.knowledgeCandidates.length} knowledge candidate(s).`,
667
+ evidenceRefs: normalizeEvidenceRefs(output.evidenceRefs ?? []),
668
+ evoEvalSets: normalizeEvalSets(output.evoEvalSets ?? []),
669
+ candidates: output.knowledgeCandidates.map((candidate, index) =>
670
+ normalizePlanCandidate(candidate, `knowledgeCandidates[${index}]`),
671
+ ),
672
+ droppedSignals: normalizeDroppedSignals(output.droppedSignals ?? []),
673
+ conflicts: normalizeConflicts(output.conflicts ?? []),
674
+ privacyCheck: normalizePrivacyCheck(output.privacyCheck, "privacyCheck"),
675
+ };
676
+ assertOkfKnowledgePlanContract(plan, { homeDir: options.homeDir });
677
+ return plan;
678
+ }
679
+
680
+ export function validateOkfKnowledgePlanContract(
681
+ plan: OkfKnowledgePlan,
682
+ _options: { homeDir?: string } = {},
683
+ ): OkfKnowledgePlanValidationResult {
684
+ const findings: OkfKnowledgePlanValidationFinding[] = [];
685
+ const add = (
686
+ path: string,
687
+ code: string,
688
+ message: string,
689
+ severity: "error" | "warning" = "error",
690
+ ) => {
691
+ findings.push({ path, code, severity, message });
692
+ };
693
+
694
+ if (!isRecord(plan)) {
695
+ add("$", "plan.object", "Plan must be an object.");
696
+ return { ok: false, findings };
697
+ }
698
+ if (plan.schemaVersion !== 1)
699
+ add("schemaVersion", "plan.schemaVersion", "Plan schemaVersion must be 1.");
700
+ if (plan.kind !== "evodev-knowledge-plan") add("kind", "plan.kind", "Plan kind is invalid.");
701
+ if (!isNonEmptyString(plan.projectKey))
702
+ add("projectKey", "plan.projectKey", "Project key is required.");
703
+ if (!isNonEmptyString(plan.runId)) add("runId", "plan.runId", "Run id is required.");
704
+ if (!isNonEmptyString(plan.createdAt))
705
+ add("createdAt", "plan.createdAt", "Created timestamp is required.");
706
+ if (!isNonEmptyString(plan.evidenceWindowId)) {
707
+ add("evidenceWindowId", "plan.evidenceWindowId", "Evidence window id is required.");
708
+ }
709
+ validatePrivacyCheckValue(plan.privacyCheck, "privacyCheck", add);
710
+
711
+ if (!Array.isArray(plan.evidenceRefs)) {
712
+ add("evidenceRefs", "plan.evidenceRefs", "Plan evidenceRefs must be an array.");
713
+ } else {
714
+ plan.evidenceRefs.forEach((evidenceRef, index) =>
715
+ validateEvidenceRefValue(evidenceRef, `evidenceRefs[${index}]`, add),
716
+ );
717
+ }
718
+
719
+ if (!Array.isArray(plan.evoEvalSets)) {
720
+ add("evoEvalSets", "plan.evoEvalSets", "Plan evoEvalSets must be an array.");
721
+ } else {
722
+ plan.evoEvalSets.forEach((evalSet, index) =>
723
+ validateEvalSetValue(evalSet, `evoEvalSets[${index}]`, add),
724
+ );
725
+ }
726
+ const evalSetIds = new Set(
727
+ (Array.isArray(plan.evoEvalSets) ? plan.evoEvalSets : []).map((evalSet) => evalSet.id),
728
+ );
729
+
730
+ if (!Array.isArray(plan.candidates)) {
731
+ add("candidates", "plan.candidates", "Plan candidates must be an array.");
732
+ } else {
733
+ plan.candidates.forEach((candidate, index) =>
734
+ validateCandidateContract(candidate, `candidates[${index}]`, evalSetIds, add),
735
+ );
736
+ }
737
+
738
+ if (Array.isArray(plan.droppedSignals)) {
739
+ plan.droppedSignals.forEach((signal, index) => {
740
+ if (!isRecord(signal))
741
+ add(`droppedSignals[${index}]`, "signal.object", "Dropped signal must be an object.");
742
+ if (!isNonEmptyString(signal?.evidenceRef)) {
743
+ add(
744
+ `droppedSignals[${index}].evidenceRef`,
745
+ "signal.evidenceRef",
746
+ "Dropped signal evidenceRef is required.",
747
+ );
748
+ }
749
+ if (!isNonEmptyString(signal?.reason))
750
+ add(
751
+ `droppedSignals[${index}].reason`,
752
+ "signal.reason",
753
+ "Dropped signal reason is required.",
754
+ );
755
+ });
756
+ } else {
757
+ add("droppedSignals", "plan.droppedSignals", "Plan droppedSignals must be an array.");
758
+ }
759
+
760
+ if (Array.isArray(plan.conflicts)) {
761
+ plan.conflicts.forEach((conflict, index) => {
762
+ if (!isRecord(conflict))
763
+ add(`conflicts[${index}]`, "conflict.object", "Conflict must be an object.");
764
+ if (!isNonEmptyString(conflict?.candidateId)) {
765
+ add(
766
+ `conflicts[${index}].candidateId`,
767
+ "conflict.candidateId",
768
+ "Conflict candidateId is required.",
769
+ );
770
+ }
771
+ if (!isNonEmptyString(conflict?.reason))
772
+ add(`conflicts[${index}].reason`, "conflict.reason", "Conflict reason is required.");
773
+ });
774
+ } else {
775
+ add("conflicts", "plan.conflicts", "Plan conflicts must be an array.");
776
+ }
777
+
778
+ return {
779
+ ok: findings.every((finding) => finding.severity !== "error"),
780
+ findings,
781
+ };
782
+ }
783
+
784
+ export function assertOkfKnowledgePlanContract(
785
+ plan: OkfKnowledgePlan,
786
+ options: { homeDir?: string } = {},
787
+ ): void {
788
+ const result = validateOkfKnowledgePlanContract(plan, options);
789
+ if (!result.ok) {
790
+ const errorCount = result.findings.filter((finding) => finding.severity === "error").length;
791
+ throw new Error(`OKF knowledge plan contract validation failed with ${errorCount} error(s).`);
792
+ }
793
+ }
794
+
795
+ function normalizePlanCandidate(value: unknown, path: string): OkfKnowledgePlanCandidate {
796
+ const input = assertRecordValue(value, path) as Record<string, unknown>;
797
+ const decision = normalizeDecision(readRequiredString(input.decision, `${path}.decision`));
798
+ const candidate: OkfKnowledgePlanCandidate = {
799
+ id: sanitizeOkfText(readRequiredString(input.id, `${path}.id`)),
800
+ decision,
801
+ kind: sanitizeOkfText(readRequiredString(input.kind, `${path}.kind`)),
802
+ okfType: sanitizeOkfText(readRequiredString(input.okfType, `${path}.okfType`)),
803
+ targetStore: normalizeTargetStore(readRequiredString(input.targetStore, `${path}.targetStore`)),
804
+ targetPath: sanitizeOkfText(readRequiredString(input.targetPath, `${path}.targetPath`)),
805
+ stableKey: sanitizeOkfText(readRequiredString(input.stableKey, `${path}.stableKey`)),
806
+ confidence: normalizeConfidence(readRequiredString(input.confidence, `${path}.confidence`)),
807
+ title: sanitizeOkfText(readRequiredString(input.title, `${path}.title`)),
808
+ description: sanitizeOkfText(readRequiredString(input.description, `${path}.description`)),
809
+ claim: sanitizeOkfText(readRequiredString(input.claim, `${path}.claim`)),
810
+ basis: normalizeBasis(readRequiredString(input.basis, `${path}.basis`)),
811
+ metadataOnlyEvidence: readRequiredBoolean(
812
+ input.metadataOnlyEvidence,
813
+ `${path}.metadataOnlyEvidence`,
814
+ ),
815
+ howToApply: sanitizeOkfText(readRequiredString(input.howToApply, `${path}.howToApply`)),
816
+ antiCriteria: readStringArray(input.antiCriteria, `${path}.antiCriteria`).map(sanitizeOkfText),
817
+ roleTags: readStringArray(input.roleTags, `${path}.roleTags`).map(sanitizeOkfText),
818
+ repoTags: readStringArray(input.repoTags, `${path}.repoTags`).map(sanitizeOkfText),
819
+ workflowTags: readStringArray(input.workflowTags, `${path}.workflowTags`).map(sanitizeOkfText),
820
+ pathScopes: readStringArray(input.pathScopes, `${path}.pathScopes`).map(sanitizeOkfText),
821
+ relatedConceptLinks: readStringArray(
822
+ input.relatedConceptLinks,
823
+ `${path}.relatedConceptLinks`,
824
+ ).map(sanitizeOkfText),
825
+ overlayUpdates: readOverlayUpdates(input.overlayUpdates, `${path}.overlayUpdates`),
826
+ scores: normalizeScores(input.scores, `${path}.scores`),
827
+ decisionReason: sanitizeOkfText(
828
+ readRequiredString(input.decisionReason, `${path}.decisionReason`),
829
+ ),
830
+ evidenceRefs: readStringArray(input.evidenceRefs, `${path}.evidenceRefs`).map(sanitizeOkfText),
831
+ reviewState: parseOkfReviewState(readRequiredString(input.reviewState, `${path}.reviewState`)),
832
+ verificationNotApplicableReason:
833
+ typeof input.verificationNotApplicableReason === "string"
834
+ ? sanitizeOkfText(input.verificationNotApplicableReason)
835
+ : undefined,
836
+ evalSetRefs:
837
+ input.evalSetRefs === undefined
838
+ ? undefined
839
+ : readStringArray(input.evalSetRefs, `${path}.evalSetRefs`).map(sanitizeOkfText),
840
+ evalNotApplicableReason:
841
+ typeof input.evalNotApplicableReason === "string"
842
+ ? sanitizeOkfText(input.evalNotApplicableReason)
843
+ : undefined,
844
+ bodySections: normalizeBodySections(input.bodySections, `${path}.bodySections`),
845
+ privacyCheck: normalizePrivacyCheck(input.privacyCheck, `${path}.privacyCheck`),
846
+ };
847
+ return candidate;
848
+ }
849
+
850
+ function validateCandidateContract(
851
+ candidate: unknown,
852
+ path: string,
853
+ evalSetIds: Set<string>,
854
+ add: (path: string, code: string, message: string, severity?: "error" | "warning") => void,
855
+ ): void {
856
+ if (!isRecord(candidate)) {
857
+ add(path, "candidate.object", "Candidate must be an object.");
858
+ return;
859
+ }
860
+ const decision = candidate.decision;
861
+ const activeWrite =
862
+ typeof decision === "string" && ACTIVE_OKF_DECISIONS.includes(decision as OkfKnowledgeDecision);
863
+ const noOp = decision === "no_write" || decision === "skip";
864
+
865
+ for (const key of [
866
+ "id",
867
+ "kind",
868
+ "okfType",
869
+ "targetStore",
870
+ "targetPath",
871
+ "stableKey",
872
+ "confidence",
873
+ "title",
874
+ "description",
875
+ "claim",
876
+ "basis",
877
+ "howToApply",
878
+ "decisionReason",
879
+ "reviewState",
880
+ ]) {
881
+ if (!isNonEmptyString(candidate[key]))
882
+ add(`${path}.${key}`, `candidate.${key}`, `Candidate ${key} is required.`);
883
+ }
884
+ if (!OKF_DECISIONS.includes(decision as OkfKnowledgeDecision)) {
885
+ add(`${path}.decision`, "candidate.decision", "Candidate decision is invalid.");
886
+ }
887
+ if (
888
+ !["okf", "repo-asset-proposal", "evo-eval-set", "none"].includes(String(candidate.targetStore))
889
+ ) {
890
+ add(`${path}.targetStore`, "candidate.targetStore", "Candidate targetStore is invalid.");
891
+ }
892
+ if (!["low", "medium", "high"].includes(String(candidate.confidence))) {
893
+ add(`${path}.confidence`, "candidate.confidence", "Candidate confidence is invalid.");
894
+ }
895
+ if (candidate.basis !== "direct" && candidate.basis !== "inferred") {
896
+ add(`${path}.basis`, "candidate.basis", "Candidate basis is invalid.");
897
+ }
898
+ if (typeof candidate.metadataOnlyEvidence !== "boolean") {
899
+ add(
900
+ `${path}.metadataOnlyEvidence`,
901
+ "candidate.metadataOnlyEvidence",
902
+ "Candidate metadataOnlyEvidence must be boolean.",
903
+ );
904
+ }
905
+ validateStringArrayValue(candidate.antiCriteria, `${path}.antiCriteria`, add);
906
+ validateStringArrayValue(candidate.roleTags, `${path}.roleTags`, add);
907
+ validateStringArrayValue(candidate.repoTags, `${path}.repoTags`, add);
908
+ validateStringArrayValue(candidate.workflowTags, `${path}.workflowTags`, add);
909
+ validateStringArrayValue(candidate.pathScopes, `${path}.pathScopes`, add);
910
+ validateStringArrayValue(candidate.relatedConceptLinks, `${path}.relatedConceptLinks`, add);
911
+ validateStringArrayValue(candidate.evidenceRefs, `${path}.evidenceRefs`, add);
912
+ validateOverlayUpdatesValue(candidate.overlayUpdates, `${path}.overlayUpdates`, add);
913
+ validateScoresValue(candidate.scores, `${path}.scores`, add);
914
+ validatePrivacyCheckValue(candidate.privacyCheck, `${path}.privacyCheck`, add);
915
+ validateBodySectionsValue(candidate.bodySections, `${path}.bodySections`, add);
916
+ validateTargetPathValue(candidate.targetPath, `${path}.targetPath`, activeWrite, add);
917
+
918
+ if (
919
+ candidate.reviewState !== undefined &&
920
+ !OKF_REVIEW_STATES.includes(candidate.reviewState as OkfKnowledgeReviewState)
921
+ ) {
922
+ add(`${path}.reviewState`, "candidate.reviewState", "Candidate reviewState is invalid.");
923
+ }
924
+ if (activeWrite) {
925
+ if (candidate.targetStore !== "okf") {
926
+ add(`${path}.targetStore`, "candidate.active.targetStore", "Active writes must target OKF.");
927
+ }
928
+ if (candidate.reviewState !== "accepted" && candidate.reviewState !== "auto-accepted") {
929
+ add(
930
+ `${path}.reviewState`,
931
+ "candidate.active.reviewState",
932
+ "Active writes require an active-compatible reviewState.",
933
+ );
934
+ }
935
+ if (candidate.metadataOnlyEvidence !== true) {
936
+ add(
937
+ `${path}.metadataOnlyEvidence`,
938
+ "candidate.active.metadataOnlyEvidence",
939
+ "Active writes require metadata-only evidence.",
940
+ );
941
+ }
942
+ if (hasHighRiskHumanReviewSignal(candidate)) {
943
+ add(
944
+ path,
945
+ "candidate.active.highRiskHumanReview",
946
+ "High-risk active writes require human review before OKF writes.",
947
+ );
948
+ }
949
+ if (!Array.isArray(candidate.evidenceRefs) || candidate.evidenceRefs.length === 0) {
950
+ add(
951
+ `${path}.evidenceRefs`,
952
+ "candidate.active.evidenceRefs",
953
+ "Active writes require evidenceRefs.",
954
+ );
955
+ }
956
+ const verification =
957
+ isRecord(candidate.bodySections) && Array.isArray(candidate.bodySections.verification)
958
+ ? candidate.bodySections.verification
959
+ : [];
960
+ if (verification.length === 0 && !isNonEmptyString(candidate.verificationNotApplicableReason)) {
961
+ add(
962
+ `${path}.bodySections.verification`,
963
+ "candidate.active.verification",
964
+ "Active writes require verification or an explicit not-applicable reason.",
965
+ );
966
+ }
967
+ if (isBehaviorChangeCandidate(candidate)) {
968
+ const evalRefs = Array.isArray(candidate.evalSetRefs) ? candidate.evalSetRefs : [];
969
+ if (evalRefs.length === 0) {
970
+ add(
971
+ `${path}.evalSetRefs`,
972
+ "candidate.active.evalSetRefs",
973
+ "Behavior-change active writes require evalSetRefs.",
974
+ );
975
+ }
976
+ for (const evalRef of evalRefs) {
977
+ if (typeof evalRef !== "string" || !evalSetIds.has(evalRef)) {
978
+ add(
979
+ `${path}.evalSetRefs`,
980
+ "candidate.active.evalSetRefs.missing",
981
+ "Behavior-change evalSetRef must point to a provided eval set.",
982
+ );
983
+ }
984
+ }
985
+ }
986
+ }
987
+
988
+ if (decision === "needs-human") {
989
+ if (
990
+ !isNonEmptyString(candidate.id) ||
991
+ !isNonEmptyString(candidate.title) ||
992
+ !isNonEmptyString(candidate.decisionReason)
993
+ ) {
994
+ add(
995
+ path,
996
+ "candidate.needsHuman.metadata",
997
+ "Needs-human candidates require safe queue metadata.",
998
+ );
999
+ }
1000
+ }
1001
+ if (noOp && (!isNonEmptyString(candidate.id) || !isNonEmptyString(candidate.decisionReason))) {
1002
+ add(path, "candidate.noop.metadata", "No-op candidates require safe skip metadata.");
1003
+ }
1004
+ if (hasUnsafeCandidateContent(candidate as unknown as OkfKnowledgePlanCandidate)) {
1005
+ add(path, "candidate.privacy.unsafe", "Candidate contains unsafe raw or sensitive content.");
1006
+ }
1007
+ }
1008
+
1009
+ function normalizeEvidenceRefs(values: unknown): OkfKnowledgeEvidenceRef[] {
1010
+ if (!Array.isArray(values)) throw new Error("evidenceRefs must be an array.");
1011
+ return values.map((value, index) => {
1012
+ const input = assertRecordValue(value, `evidenceRefs[${index}]`) as Record<string, unknown>;
1013
+ return {
1014
+ id: sanitizeOkfText(readRequiredString(input.id, `evidenceRefs[${index}].id`)),
1015
+ kind: sanitizeOkfText(readRequiredString(input.kind, `evidenceRefs[${index}].kind`)),
1016
+ source: sanitizeOkfText(readRequiredString(input.source, `evidenceRefs[${index}].source`)),
1017
+ rawContentStored: readRequiredFalse(
1018
+ input.rawContentStored,
1019
+ `evidenceRefs[${index}].rawContentStored`,
1020
+ ),
1021
+ externalContentCopied: readRequiredFalse(
1022
+ input.externalContentCopied,
1023
+ `evidenceRefs[${index}].externalContentCopied`,
1024
+ ),
1025
+ };
1026
+ });
1027
+ }
1028
+
1029
+ function normalizeEvalSets(values: unknown): OkfKnowledgeEvalSet[] {
1030
+ if (!Array.isArray(values)) throw new Error("evoEvalSets must be an array.");
1031
+ return values.map((value, index) => {
1032
+ const input = assertRecordValue(value, `evoEvalSets[${index}]`) as Record<string, unknown>;
1033
+ const target = assertRecordValue(input.target, `evoEvalSets[${index}].target`) as Record<
1034
+ string,
1035
+ unknown
1036
+ >;
1037
+ const privacy = assertRecordValue(input.privacy, `evoEvalSets[${index}].privacy`) as Record<
1038
+ string,
1039
+ unknown
1040
+ >;
1041
+ const decision = normalizeEvalSetDecision(
1042
+ readRequiredString(input.decision, `evoEvalSets[${index}].decision`),
1043
+ );
1044
+ return {
1045
+ id: sanitizeOkfText(readRequiredString(input.id, `evoEvalSets[${index}].id`)),
1046
+ target: {
1047
+ kind: sanitizeOkfText(readRequiredString(target.kind, `evoEvalSets[${index}].target.kind`)),
1048
+ id: sanitizeOkfText(readRequiredString(target.id, `evoEvalSets[${index}].target.id`)),
1049
+ },
1050
+ purpose: sanitizeOkfText(readRequiredString(input.purpose, `evoEvalSets[${index}].purpose`)),
1051
+ roleTags: readStringArray(input.roleTags, `evoEvalSets[${index}].roleTags`).map(
1052
+ sanitizeOkfText,
1053
+ ),
1054
+ cases: readEvalSetCases(input.cases, `evoEvalSets[${index}].cases`),
1055
+ privacy: {
1056
+ usesRawPrompt: readRequiredFalse(
1057
+ privacy.usesRawPrompt,
1058
+ `evoEvalSets[${index}].privacy.usesRawPrompt`,
1059
+ ),
1060
+ usesSourceDump: readRequiredFalse(
1061
+ privacy.usesSourceDump,
1062
+ `evoEvalSets[${index}].privacy.usesSourceDump`,
1063
+ ),
1064
+ usesRawCommandOutput: readRequiredFalse(
1065
+ privacy.usesRawCommandOutput,
1066
+ `evoEvalSets[${index}].privacy.usesRawCommandOutput`,
1067
+ ),
1068
+ },
1069
+ decision,
1070
+ };
1071
+ });
1072
+ }
1073
+
1074
+ function readEvalSetCases(value: unknown, path: string): OkfKnowledgeEvalSet["cases"] {
1075
+ if (!Array.isArray(value)) throw new Error(`${path} must be an array.`);
1076
+ return value.map((item, index) => {
1077
+ const input = assertRecordValue(item, `${path}[${index}]`) as Record<string, unknown>;
1078
+ return {
1079
+ id: sanitizeOkfText(readRequiredString(input.id, `${path}[${index}].id`)),
1080
+ inputRefs: readStringArray(input.inputRefs, `${path}[${index}].inputRefs`).map(
1081
+ sanitizeOkfText,
1082
+ ),
1083
+ assertions: readStringArray(input.assertions, `${path}[${index}].assertions`).map(
1084
+ sanitizeOkfText,
1085
+ ),
1086
+ expectedReviewState: parseOkfReviewState(
1087
+ readRequiredString(input.expectedReviewState, `${path}[${index}].expectedReviewState`),
1088
+ ),
1089
+ };
1090
+ });
1091
+ }
1092
+
1093
+ function normalizeDroppedSignals(values: unknown): Array<{ evidenceRef: string; reason: string }> {
1094
+ if (!Array.isArray(values)) throw new Error("droppedSignals must be an array.");
1095
+ return values.map((value, index) => {
1096
+ const input = assertRecordValue(value, `droppedSignals[${index}]`) as Record<string, unknown>;
1097
+ return {
1098
+ evidenceRef: sanitizeOkfText(
1099
+ readRequiredString(input.evidenceRef, `droppedSignals[${index}].evidenceRef`),
1100
+ ),
1101
+ reason: sanitizeOkfText(readRequiredString(input.reason, `droppedSignals[${index}].reason`)),
1102
+ };
1103
+ });
1104
+ }
1105
+
1106
+ function normalizeConflicts(values: unknown): Array<{ candidateId: string; reason: string }> {
1107
+ if (!Array.isArray(values)) throw new Error("conflicts must be an array.");
1108
+ return values.map((value, index) => {
1109
+ const input = assertRecordValue(value, `conflicts[${index}]`) as Record<string, unknown>;
1110
+ return {
1111
+ candidateId: sanitizeOkfText(
1112
+ readRequiredString(input.candidateId, `conflicts[${index}].candidateId`),
1113
+ ),
1114
+ reason: sanitizeOkfText(readRequiredString(input.reason, `conflicts[${index}].reason`)),
1115
+ };
1116
+ });
1117
+ }
1118
+
1119
+ function normalizeBodySections(
1120
+ value: unknown,
1121
+ path: string,
1122
+ ): OkfKnowledgePlanCandidate["bodySections"] {
1123
+ const input = assertRecordValue(value, path) as Record<string, unknown>;
1124
+ return {
1125
+ summary: sanitizeOkfText(readRequiredString(input.summary, `${path}.summary`)),
1126
+ appliesWhen: readStringArray(input.appliesWhen, `${path}.appliesWhen`).map(sanitizeOkfText),
1127
+ guidance: readStringArray(input.guidance, `${path}.guidance`).map(sanitizeOkfText),
1128
+ antiCriteria: readStringArray(input.antiCriteria, `${path}.antiCriteria`).map(sanitizeOkfText),
1129
+ verification: readStringArray(input.verification, `${path}.verification`).map(sanitizeOkfText),
1130
+ citations: readStringArray(input.citations, `${path}.citations`).map(sanitizeOkfText),
1131
+ };
1132
+ }
1133
+
1134
+ function normalizePrivacyCheck(value: unknown, path: string): OkfKnowledgePrivacyCheck {
1135
+ const input = assertRecordValue(value, path) as Record<string, unknown>;
1136
+ return {
1137
+ rawPromptsStored: readRequiredFalse(input.rawPromptsStored, `${path}.rawPromptsStored`),
1138
+ rawLogsStored: readRequiredFalse(input.rawLogsStored, `${path}.rawLogsStored`),
1139
+ sourceDumpsStored: readRequiredFalse(input.sourceDumpsStored, `${path}.sourceDumpsStored`),
1140
+ rawCommandOutputStored: readRequiredFalse(
1141
+ input.rawCommandOutputStored,
1142
+ `${path}.rawCommandOutputStored`,
1143
+ ),
1144
+ secretsStored: readRequiredFalse(input.secretsStored, `${path}.secretsStored`),
1145
+ internalLinksStored: readRequiredFalse(
1146
+ input.internalLinksStored,
1147
+ `${path}.internalLinksStored`,
1148
+ ),
1149
+ };
1150
+ }
1151
+
1152
+ function normalizeScores(value: unknown, path: string): OkfKnowledgeCandidateScores {
1153
+ const input = assertRecordValue(value, path) as Record<string, unknown>;
1154
+ return {
1155
+ evidenceStrength: readScore(input.evidenceStrength, `${path}.evidenceStrength`),
1156
+ reuseValue: readScore(input.reuseValue, `${path}.reuseValue`),
1157
+ actionability: readScore(input.actionability, `${path}.actionability`),
1158
+ stability: readScore(input.stability, `${path}.stability`),
1159
+ privacyRisk: readScore(input.privacyRisk, `${path}.privacyRisk`),
1160
+ duplicationRisk: readScore(input.duplicationRisk, `${path}.duplicationRisk`),
1161
+ };
1162
+ }
1163
+
1164
+ function readOverlayUpdates(value: unknown, path: string): OkfKnowledgeOverlayUpdate[] {
1165
+ if (!Array.isArray(value)) throw new Error(`${path} must be an array.`);
1166
+ return value.map((item, index) => {
1167
+ const input = assertRecordValue(item, `${path}[${index}]`) as Record<string, unknown>;
1168
+ return {
1169
+ targetPath: sanitizeOkfText(
1170
+ readRequiredString(input.targetPath, `${path}[${index}].targetPath`),
1171
+ ),
1172
+ operation: "append-link",
1173
+ link: sanitizeOkfText(readRequiredString(input.link, `${path}[${index}].link`)),
1174
+ };
1175
+ });
1176
+ }
1177
+
1178
+ function normalizeDecision(value: string): OkfKnowledgeDecision {
1179
+ const normalized = value === "no-write" ? "no_write" : value;
1180
+ if (!OKF_DECISIONS.includes(normalized as OkfKnowledgeDecision)) {
1181
+ throw new Error("Candidate decision is invalid.");
1182
+ }
1183
+ return normalized as OkfKnowledgeDecision;
1184
+ }
1185
+
1186
+ function normalizeTargetStore(value: string): OkfKnowledgeTargetStore {
1187
+ if (!["okf", "repo-asset-proposal", "evo-eval-set", "none"].includes(value)) {
1188
+ throw new Error("Candidate targetStore is invalid.");
1189
+ }
1190
+ return value as OkfKnowledgeTargetStore;
1191
+ }
1192
+
1193
+ function normalizeConfidence(value: string): "low" | "medium" | "high" {
1194
+ if (value !== "low" && value !== "medium" && value !== "high") {
1195
+ throw new Error("Candidate confidence is invalid.");
1196
+ }
1197
+ return value;
1198
+ }
1199
+
1200
+ function normalizeBasis(value: string): "direct" | "inferred" {
1201
+ if (value !== "direct" && value !== "inferred") throw new Error("Candidate basis is invalid.");
1202
+ return value;
1203
+ }
1204
+
1205
+ function normalizeEvalSetDecision(value: string): OkfKnowledgeEvalSet["decision"] {
1206
+ const normalized = value === "no-write" ? "no_write" : value;
1207
+ if (
1208
+ normalized !== "create" &&
1209
+ normalized !== "needs-human" &&
1210
+ normalized !== "no_write" &&
1211
+ normalized !== "skip"
1212
+ ) {
1213
+ throw new Error("Eval set decision is invalid.");
1214
+ }
1215
+ return normalized as OkfKnowledgeEvalSet["decision"];
1216
+ }
1217
+
1218
+ function assertRecordValue(value: unknown, path: string): Record<string, unknown> {
1219
+ if (!isRecord(value)) throw new Error(`${path} must be an object.`);
1220
+ return value;
1221
+ }
1222
+
1223
+ function readRequiredString(value: unknown, path: string): string {
1224
+ if (!isNonEmptyString(value)) throw new Error(`${path} must be a non-empty string.`);
1225
+ return value;
1226
+ }
1227
+
1228
+ function readRequiredBoolean(value: unknown, path: string): boolean {
1229
+ if (typeof value !== "boolean") throw new Error(`${path} must be boolean.`);
1230
+ return value;
1231
+ }
1232
+
1233
+ function readRequiredFalse(value: unknown, path: string): false {
1234
+ if (value !== false) throw new Error(`${path} must be false.`);
1235
+ return false;
1236
+ }
1237
+
1238
+ function readStringArray(value: unknown, path: string): string[] {
1239
+ if (!Array.isArray(value)) throw new Error(`${path} must be an array.`);
1240
+ return value.map((item, index) => readRequiredString(item, `${path}[${index}]`));
1241
+ }
1242
+
1243
+ function readScore(value: unknown, path: string): number {
1244
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 5) {
1245
+ throw new Error(`${path} must be an integer from 1 to 5.`);
1246
+ }
1247
+ return value;
1248
+ }
1249
+
1250
+ function isRecord(value: unknown): value is Record<string, unknown> {
1251
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1252
+ }
1253
+
1254
+ function isNonEmptyString(value: unknown): value is string {
1255
+ return typeof value === "string" && value.trim() !== "";
1256
+ }
1257
+
1258
+ function validateEvidenceRefValue(
1259
+ evidenceRef: unknown,
1260
+ path: string,
1261
+ add: (path: string, code: string, message: string, severity?: "error" | "warning") => void,
1262
+ ): void {
1263
+ if (!isRecord(evidenceRef)) {
1264
+ add(path, "evidenceRef.object", "Evidence ref must be an object.");
1265
+ return;
1266
+ }
1267
+ if (!isNonEmptyString(evidenceRef.id))
1268
+ add(`${path}.id`, "evidenceRef.id", "Evidence ref id is required.");
1269
+ if (!isNonEmptyString(evidenceRef.kind))
1270
+ add(`${path}.kind`, "evidenceRef.kind", "Evidence ref kind is required.");
1271
+ if (!isNonEmptyString(evidenceRef.source))
1272
+ add(`${path}.source`, "evidenceRef.source", "Evidence ref source is required.");
1273
+ if (evidenceRef.rawContentStored !== false)
1274
+ add(
1275
+ `${path}.rawContentStored`,
1276
+ "evidenceRef.rawContentStored",
1277
+ "Evidence ref rawContentStored must be false.",
1278
+ );
1279
+ if (evidenceRef.externalContentCopied !== false) {
1280
+ add(
1281
+ `${path}.externalContentCopied`,
1282
+ "evidenceRef.externalContentCopied",
1283
+ "Evidence ref externalContentCopied must be false.",
1284
+ );
1285
+ }
1286
+ }
1287
+
1288
+ function validateEvalSetValue(
1289
+ evalSet: unknown,
1290
+ path: string,
1291
+ add: (path: string, code: string, message: string, severity?: "error" | "warning") => void,
1292
+ ): void {
1293
+ if (!isRecord(evalSet)) {
1294
+ add(path, "evalSet.object", "Eval set must be an object.");
1295
+ return;
1296
+ }
1297
+ if (!isNonEmptyString(evalSet.id)) add(`${path}.id`, "evalSet.id", "Eval set id is required.");
1298
+ if (!isRecord(evalSet.target)) {
1299
+ add(`${path}.target`, "evalSet.target", "Eval set target is required.");
1300
+ } else {
1301
+ if (!isNonEmptyString(evalSet.target.kind))
1302
+ add(`${path}.target.kind`, "evalSet.target.kind", "Eval set target kind is required.");
1303
+ if (!isNonEmptyString(evalSet.target.id))
1304
+ add(`${path}.target.id`, "evalSet.target.id", "Eval set target id is required.");
1305
+ }
1306
+ if (!isNonEmptyString(evalSet.purpose))
1307
+ add(`${path}.purpose`, "evalSet.purpose", "Eval set purpose is required.");
1308
+ validateStringArrayValue(evalSet.roleTags, `${path}.roleTags`, add);
1309
+ if (!Array.isArray(evalSet.cases)) {
1310
+ add(`${path}.cases`, "evalSet.cases", "Eval set cases must be an array.");
1311
+ } else {
1312
+ evalSet.cases.forEach((item, index) => {
1313
+ const casePath = `${path}.cases[${index}]`;
1314
+ if (!isRecord(item)) {
1315
+ add(casePath, "evalSet.case.object", "Eval set case must be an object.");
1316
+ return;
1317
+ }
1318
+ if (!isNonEmptyString(item.id))
1319
+ add(`${casePath}.id`, "evalSet.case.id", "Eval set case id is required.");
1320
+ validateStringArrayValue(item.inputRefs, `${casePath}.inputRefs`, add);
1321
+ validateStringArrayValue(item.assertions, `${casePath}.assertions`, add);
1322
+ if (!OKF_REVIEW_STATES.includes(item.expectedReviewState as OkfKnowledgeReviewState)) {
1323
+ add(
1324
+ `${casePath}.expectedReviewState`,
1325
+ "evalSet.case.expectedReviewState",
1326
+ "Eval set case expectedReviewState is invalid.",
1327
+ );
1328
+ }
1329
+ });
1330
+ }
1331
+ if (!isRecord(evalSet.privacy)) {
1332
+ add(`${path}.privacy`, "evalSet.privacy", "Eval set privacy is required.");
1333
+ } else {
1334
+ if (evalSet.privacy.usesRawPrompt !== false)
1335
+ add(
1336
+ `${path}.privacy.usesRawPrompt`,
1337
+ "evalSet.privacy.usesRawPrompt",
1338
+ "Eval set usesRawPrompt must be false.",
1339
+ );
1340
+ if (evalSet.privacy.usesSourceDump !== false)
1341
+ add(
1342
+ `${path}.privacy.usesSourceDump`,
1343
+ "evalSet.privacy.usesSourceDump",
1344
+ "Eval set usesSourceDump must be false.",
1345
+ );
1346
+ if (evalSet.privacy.usesRawCommandOutput !== false) {
1347
+ add(
1348
+ `${path}.privacy.usesRawCommandOutput`,
1349
+ "evalSet.privacy.usesRawCommandOutput",
1350
+ "Eval set usesRawCommandOutput must be false.",
1351
+ );
1352
+ }
1353
+ }
1354
+ if (!["create", "needs-human", "no_write", "skip"].includes(String(evalSet.decision))) {
1355
+ add(`${path}.decision`, "evalSet.decision", "Eval set decision is invalid.");
1356
+ }
1357
+ }
1358
+
1359
+ function validateStringArrayValue(
1360
+ value: unknown,
1361
+ path: string,
1362
+ add: (path: string, code: string, message: string, severity?: "error" | "warning") => void,
1363
+ ): void {
1364
+ if (!Array.isArray(value)) {
1365
+ add(path, "array.required", "Field must be an array.");
1366
+ return;
1367
+ }
1368
+ value.forEach((item, index) => {
1369
+ if (!isNonEmptyString(item))
1370
+ add(`${path}[${index}]`, "array.string", "Array items must be non-empty strings.");
1371
+ });
1372
+ }
1373
+
1374
+ function validateOverlayUpdatesValue(
1375
+ value: unknown,
1376
+ path: string,
1377
+ add: (path: string, code: string, message: string, severity?: "error" | "warning") => void,
1378
+ ): void {
1379
+ if (!Array.isArray(value)) {
1380
+ add(path, "overlayUpdates.array", "Overlay updates must be an array.");
1381
+ return;
1382
+ }
1383
+ value.forEach((item, index) => {
1384
+ const itemPath = `${path}[${index}]`;
1385
+ if (!isRecord(item)) {
1386
+ add(itemPath, "overlayUpdate.object", "Overlay update must be an object.");
1387
+ return;
1388
+ }
1389
+ if (item.operation !== "append-link")
1390
+ add(`${itemPath}.operation`, "overlayUpdate.operation", "Overlay operation is invalid.");
1391
+ validateTargetPathValue(item.targetPath, `${itemPath}.targetPath`, false, add);
1392
+ if (!isNonEmptyString(item.link))
1393
+ add(`${itemPath}.link`, "overlayUpdate.link", "Overlay link is required.");
1394
+ });
1395
+ }
1396
+
1397
+ function validateScoresValue(
1398
+ value: unknown,
1399
+ path: string,
1400
+ add: (path: string, code: string, message: string, severity?: "error" | "warning") => void,
1401
+ ): void {
1402
+ if (!isRecord(value)) {
1403
+ add(path, "scores.object", "Scores must be an object.");
1404
+ return;
1405
+ }
1406
+ for (const key of [
1407
+ "evidenceStrength",
1408
+ "reuseValue",
1409
+ "actionability",
1410
+ "stability",
1411
+ "privacyRisk",
1412
+ "duplicationRisk",
1413
+ ]) {
1414
+ const score = value[key];
1415
+ if (typeof score !== "number" || !Number.isInteger(score) || score < 1 || score > 5) {
1416
+ add(`${path}.${key}`, "scores.range", "Scores must be integers from 1 to 5.");
1417
+ }
1418
+ }
1419
+ }
1420
+
1421
+ function validatePrivacyCheckValue(
1422
+ value: unknown,
1423
+ path: string,
1424
+ add: (path: string, code: string, message: string, severity?: "error" | "warning") => void,
1425
+ ): void {
1426
+ if (!isRecord(value)) {
1427
+ add(path, "privacy.object", "Privacy check must be an object.");
1428
+ return;
1429
+ }
1430
+ for (const key of OKF_PRIVACY_FLAG_KEYS) {
1431
+ if (value[key] !== false)
1432
+ add(`${path}.${key}`, "privacy.false", "Privacy fields must be false.");
1433
+ }
1434
+ }
1435
+
1436
+ function validateBodySectionsValue(
1437
+ value: unknown,
1438
+ path: string,
1439
+ add: (path: string, code: string, message: string, severity?: "error" | "warning") => void,
1440
+ ): void {
1441
+ if (!isRecord(value)) {
1442
+ add(path, "bodySections.object", "Body sections must be an object.");
1443
+ return;
1444
+ }
1445
+ if (!isNonEmptyString(value.summary))
1446
+ add(`${path}.summary`, "bodySections.summary", "Body summary is required.");
1447
+ validateStringArrayValue(value.appliesWhen, `${path}.appliesWhen`, add);
1448
+ validateStringArrayValue(value.guidance, `${path}.guidance`, add);
1449
+ validateStringArrayValue(value.antiCriteria, `${path}.antiCriteria`, add);
1450
+ validateStringArrayValue(value.verification, `${path}.verification`, add);
1451
+ validateStringArrayValue(value.citations, `${path}.citations`, add);
1452
+ }
1453
+
1454
+ function validateTargetPathValue(
1455
+ value: unknown,
1456
+ path: string,
1457
+ requireMarkdown: boolean,
1458
+ add: (path: string, code: string, message: string, severity?: "error" | "warning") => void,
1459
+ ): void {
1460
+ if (!isNonEmptyString(value)) {
1461
+ add(path, "targetPath.required", "Target path is required.");
1462
+ return;
1463
+ }
1464
+ const normalized = value.replace(/^\/+/u, "");
1465
+ if (
1466
+ normalized !== value ||
1467
+ isAbsolute(value) ||
1468
+ normalized.split("/").some((segment) => segment === "" || segment === "." || segment === "..")
1469
+ ) {
1470
+ add(path, "targetPath.relative", "Target path must be a safe relative path.");
1471
+ }
1472
+ if (normalized.includes("index.md") || normalized.includes("log.md")) {
1473
+ add(path, "targetPath.reserved", "Target path cannot use reserved filenames.");
1474
+ }
1475
+ if (requireMarkdown && !normalized.endsWith(".md")) {
1476
+ add(path, "targetPath.markdown", "Active OKF concept target paths must end in .md.");
1477
+ }
1478
+ }
1479
+
1480
+ function isBehaviorChangeCandidate(candidate: unknown): boolean {
1481
+ if (!isRecord(candidate)) return false;
1482
+ const kind = typeof candidate.kind === "string" ? candidate.kind : "";
1483
+ const haystack = [
1484
+ kind,
1485
+ candidate.targetStore,
1486
+ candidate.targetPath,
1487
+ candidate.stableKey,
1488
+ candidate.title,
1489
+ candidate.description,
1490
+ candidate.claim,
1491
+ candidate.howToApply,
1492
+ ].join(" ");
1493
+ return (
1494
+ BEHAVIOR_CHANGE_KINDS.has(kind) ||
1495
+ /role|team|workflow|routing|tool[-_\s]*use|subagent|task[-_\s]*split/i.test(haystack)
1496
+ );
1497
+ }
1498
+
1499
+ export function loadActiveOkfKnowledgeIndex(input: { homeDir: string }): OkfActiveKnowledgeIndex {
1500
+ const okfDir = resolveOkfKnowledgePaths(input.homeDir).okfDir;
1501
+ if (!existsSync(okfDir)) return { concepts: [] };
1502
+ const concepts: OkfActiveKnowledgeIndex["concepts"] = [];
1503
+ for (const file of listMarkdownFilesSync(okfDir)) {
1504
+ if (RESERVED_OKF_FILENAMES.has(file.name)) continue;
1505
+ const content = readFileSync(file.path, "utf8");
1506
+ if (isUnsafeOkfQueryContent(content)) continue;
1507
+ const parsed = parseOkfConceptFile(okfDir, file.path, content);
1508
+ if (parsed === null || !isActiveOkfReviewState(parsed.reviewState)) continue;
1509
+ concepts.push({
1510
+ id: parsed.id,
1511
+ sourceLink: parsed.sourceLink,
1512
+ stableKey: parsed.stableKey,
1513
+ reviewState: parsed.reviewState,
1514
+ title: parsed.title,
1515
+ description: parsed.description,
1516
+ type: parsed.type,
1517
+ repoTags: parsed.repoTags,
1518
+ roleTags: parsed.roleTags,
1519
+ workflowTags: parsed.workflowTags,
1520
+ targetPath: parsed.sourceLink.replace(/^\//u, ""),
1521
+ });
1522
+ }
1523
+ return {
1524
+ concepts: concepts.sort((left, right) => left.id.localeCompare(right.id)),
1525
+ };
1526
+ }
1527
+
1528
+ export function findCandidateConflictOrDuplicate(
1529
+ candidate: OkfKnowledgePlanCandidate,
1530
+ index: OkfActiveKnowledgeIndex,
1531
+ ): OkfCandidateConflictOrDuplicate | null {
1532
+ const targetPath = candidate.targetPath.replace(/^\/+/u, "");
1533
+ const duplicate = index.concepts.find(
1534
+ (concept) =>
1535
+ concept.stableKey === candidate.stableKey ||
1536
+ concept.targetPath === targetPath ||
1537
+ concept.sourceLink === `/${targetPath}`,
1538
+ );
1539
+ if (duplicate !== undefined) {
1540
+ return {
1541
+ kind: "duplicate",
1542
+ conceptId: duplicate.id,
1543
+ reason: `Duplicate active OKF concept ${duplicate.id} already covers stable key ${candidate.stableKey}.`,
1544
+ };
1545
+ }
1546
+
1547
+ const candidateTitle = normalizeComparableText(candidate.title);
1548
+ const conflict = index.concepts.find((concept) => {
1549
+ if (normalizeComparableText(concept.title) !== candidateTitle) return false;
1550
+ if (concept.stableKey === candidate.stableKey) return false;
1551
+ if (candidate.repoTags.length === 0 || concept.repoTags.length === 0) return true;
1552
+ return candidate.repoTags.some((tag) => concept.repoTags.includes(tag));
1553
+ });
1554
+ if (conflict === undefined) return null;
1555
+ return {
1556
+ kind: "conflict",
1557
+ conceptId: conflict.id,
1558
+ reason: `Potential active OKF conflict with ${conflict.id}; title and repo scope overlap.`,
1559
+ };
1560
+ }
1561
+
1562
+ export function scoreOkfKnowledgeCandidate(
1563
+ candidate: OkfKnowledgePlanCandidate,
1564
+ context: OkfKnowledgePlanContext,
1565
+ ): OkfKnowledgeCandidateScores {
1566
+ const conflict = findCandidateConflictOrDuplicate(candidate, context.activeIndex);
1567
+ const hasVerification =
1568
+ candidate.bodySections.verification.length > 0 || candidate.verificationNotApplicableReason;
1569
+ const metadataOnly =
1570
+ candidate.metadataOnlyEvidence &&
1571
+ context.batch.evidenceWindow.sourceRefs.every(
1572
+ (sourceRef) =>
1573
+ sourceRef.rawContentStored === false && sourceRef.externalContentCopied === false,
1574
+ ) &&
1575
+ context.batch.evidenceWindow.events.every((event) => event.rawContentStored === false);
1576
+ const privacyRisk = hasUnsafeCandidateContent(candidate) ? 5 : metadataOnly ? 1 : 3;
1577
+ const evidenceStrength =
1578
+ candidate.basis === "direct" && candidate.confidence === "high" && metadataOnly
1579
+ ? hasVerification
1580
+ ? 5
1581
+ : 4
1582
+ : candidate.confidence === "medium"
1583
+ ? 3
1584
+ : 2;
1585
+ const actionability =
1586
+ candidate.howToApply.trim() !== "" &&
1587
+ candidate.bodySections.guidance.some((item) => item.trim() !== "")
1588
+ ? 4
1589
+ : 2;
1590
+ const reuseValue =
1591
+ candidate.kind === "evos-case" || candidate.roleTags.length > 0 || candidate.repoTags.length > 0
1592
+ ? 4
1593
+ : 3;
1594
+ const stability = /hard policy|hard rule|must always|never allow/i.test(
1595
+ `${candidate.claim} ${candidate.howToApply}`,
1596
+ )
1597
+ ? 2
1598
+ : 4;
1599
+ const duplicationRisk = conflict === null ? 1 : conflict.kind === "duplicate" ? 5 : 4;
1600
+ return {
1601
+ evidenceStrength,
1602
+ reuseValue,
1603
+ actionability,
1604
+ stability,
1605
+ privacyRisk,
1606
+ duplicationRisk,
1607
+ };
1608
+ }
1609
+
1610
+ export function decideOkfKnowledgeCandidate(
1611
+ candidate: OkfKnowledgePlanCandidate,
1612
+ context: OkfKnowledgePlanContext,
1613
+ ): OkfKnowledgePlanCandidate {
1614
+ const originalUnsafe = hasUnsafeCandidateContent(candidate);
1615
+ const sanitized = sanitizeOkfPlanCandidate(candidate);
1616
+ const conflict = findCandidateConflictOrDuplicate(sanitized, context.activeIndex);
1617
+ const scored = scoreOkfKnowledgeCandidate(sanitized, context);
1618
+ const scores = {
1619
+ ...scored,
1620
+ privacyRisk: originalUnsafe ? 5 : scored.privacyRisk,
1621
+ };
1622
+ const hasVerification =
1623
+ sanitized.bodySections.verification.length > 0 ||
1624
+ sanitized.verificationNotApplicableReason !== undefined;
1625
+ const localActiveTarget = sanitized.targetStore === "okf" || sanitized.kind === "evos-case";
1626
+ const hasScopeTags = sanitized.repoTags.length > 0 && sanitized.roleTags.length > 0;
1627
+ const highRiskRequiresHuman =
1628
+ hasHighRiskHumanReviewSignal(candidate) || hasHighRiskHumanReviewSignal(sanitized);
1629
+ const autoAcceptEligible =
1630
+ !highRiskRequiresHuman &&
1631
+ conflict === null &&
1632
+ localActiveTarget &&
1633
+ sanitized.targetStore === "okf" &&
1634
+ sanitized.basis === "direct" &&
1635
+ sanitized.metadataOnlyEvidence &&
1636
+ hasVerification &&
1637
+ hasScopeTags &&
1638
+ scores.evidenceStrength >= 4 &&
1639
+ scores.reuseValue >= 3 &&
1640
+ scores.actionability >= 3 &&
1641
+ scores.stability >= 3 &&
1642
+ scores.privacyRisk <= 2 &&
1643
+ scores.duplicationRisk <= 2;
1644
+
1645
+ if (autoAcceptEligible) {
1646
+ return {
1647
+ ...sanitized,
1648
+ decision: "auto-accept",
1649
+ scores,
1650
+ reviewState: "auto-accepted",
1651
+ decisionReason:
1652
+ "Auto-accepted: high-confidence metadata-only evidence passed verification, privacy, scope, and duplicate checks.",
1653
+ };
1654
+ }
1655
+
1656
+ if (highRiskRequiresHuman) {
1657
+ return {
1658
+ ...sanitized,
1659
+ decision: "needs-human",
1660
+ scores,
1661
+ reviewState: "needs-human",
1662
+ decisionReason: "Needs human review: high-risk domain requires review.",
1663
+ };
1664
+ }
1665
+
1666
+ if (conflict?.kind === "duplicate") {
1667
+ return {
1668
+ ...sanitized,
1669
+ decision: "no_write",
1670
+ scores,
1671
+ reviewState: "auto-stored/unreviewed",
1672
+ decisionReason: conflict.reason,
1673
+ };
1674
+ }
1675
+
1676
+ const reusable = scores.reuseValue >= 3 || scores.actionability >= 3;
1677
+ if (reusable) {
1678
+ return {
1679
+ ...sanitized,
1680
+ decision: "needs-human",
1681
+ scores,
1682
+ reviewState: "needs-human",
1683
+ decisionReason:
1684
+ conflict?.reason ??
1685
+ explainNeedsHumanDecision(sanitized, scores, hasVerification, hasScopeTags),
1686
+ };
1687
+ }
1688
+
1689
+ return {
1690
+ ...sanitized,
1691
+ decision: "no_write",
1692
+ scores,
1693
+ reviewState: "auto-stored/unreviewed",
1694
+ decisionReason: "No write: candidate did not meet reusable knowledge thresholds.",
1695
+ };
1696
+ }
1697
+
1698
+ export async function activateOkfKnowledgePlan(input: {
1699
+ homeDir: string;
1700
+ plan: OkfKnowledgePlan;
1701
+ overwrite?: boolean;
1702
+ evidenceWindowPath?: string | null;
1703
+ }): Promise<OkfKnowledgeActivationResult> {
1704
+ const validation = validateOkfKnowledgePlanContract(input.plan);
1705
+ const projectKey =
1706
+ isRecord(input.plan) && isNonEmptyString(input.plan.projectKey)
1707
+ ? input.plan.projectKey
1708
+ : "unknown";
1709
+ const runId =
1710
+ isRecord(input.plan) && isNonEmptyString(input.plan.runId) ? input.plan.runId : "unknown";
1711
+ if (!validation.ok) {
1712
+ await writeFailedOkfKnowledgePlanArtifact({
1713
+ homeDir: input.homeDir,
1714
+ projectKey,
1715
+ runId,
1716
+ rawPlan: input.plan,
1717
+ findings: validation.findings,
1718
+ error: "OKF knowledge plan contract validation failed.",
1719
+ failureKind: "validation",
1720
+ });
1721
+ throw new Error("OKF knowledge plan contract validation failed.");
1722
+ }
1723
+ validateOkfKnowledgePlan(input.plan);
1724
+ const paths = resolveOkfKnowledgePaths(input.homeDir);
1725
+ const tmpRunDir = join(paths.tmpDir, `${input.plan.projectKey}-${input.plan.runId}`);
1726
+ const planPath = join(tmpRunDir, "knowledge-plan.json");
1727
+ const failedPlanPath = resolveFailedPlanPath(
1728
+ input.homeDir,
1729
+ input.plan.projectKey,
1730
+ input.plan.runId,
1731
+ );
1732
+
1733
+ await ensureOkfKnowledgeBase(input.homeDir);
1734
+ await writeJson(planPath, input.plan, { overwrite: true });
1735
+
1736
+ const conceptPaths: string[] = [];
1737
+ const overlayPaths: string[] = [];
1738
+ const skippedCandidates: string[] = [];
1739
+ const needsHumanCandidates: string[] = [];
1740
+ const affectedDirectories = new Set<string>([paths.okfDir]);
1741
+
1742
+ try {
1743
+ for (const candidate of input.plan.candidates) {
1744
+ if (candidate.decision === "no_write" || candidate.decision === "skip") {
1745
+ skippedCandidates.push(candidate.id);
1746
+ continue;
1747
+ }
1748
+ if (candidate.decision === "needs-human") {
1749
+ await writeNeedsHumanKnowledgeCandidate({
1750
+ homeDir: input.homeDir,
1751
+ plan: input.plan,
1752
+ candidate,
1753
+ });
1754
+ needsHumanCandidates.push(candidate.id);
1755
+ continue;
1756
+ }
1757
+ if (candidate.targetStore !== "okf") {
1758
+ skippedCandidates.push(candidate.id);
1759
+ continue;
1760
+ }
1761
+
1762
+ const targetPath = resolveOkfTargetPath(paths.okfDir, candidate.targetPath);
1763
+ const exists = await pathExists(targetPath);
1764
+ if (
1765
+ exists &&
1766
+ input.overwrite !== true &&
1767
+ (candidate.decision === "create" || candidate.decision === "auto-accept")
1768
+ ) {
1769
+ skippedCandidates.push(candidate.id);
1770
+ await appendOkfLog(
1771
+ paths.okfDir,
1772
+ `**Skip**: Candidate \`${candidate.id}\` matched existing [${candidate.title}](${toOkfLink(paths.okfDir, targetPath)}).`,
1773
+ );
1774
+ continue;
1775
+ }
1776
+
1777
+ await mkdir(dirname(targetPath), { recursive: true });
1778
+ await writeFile(targetPath, renderOkfConcept(candidate, input.plan), "utf8");
1779
+ conceptPaths.push(targetPath);
1780
+ affectedDirectories.add(dirname(targetPath));
1781
+
1782
+ for (const update of candidate.overlayUpdates) {
1783
+ const overlayPath = resolveOkfTargetPath(paths.okfDir, update.targetPath);
1784
+ await ensureOverlayConcept({
1785
+ okfDir: paths.okfDir,
1786
+ overlayPath,
1787
+ candidate,
1788
+ projectKey: input.plan.projectKey,
1789
+ runId: input.plan.runId,
1790
+ link: update.link,
1791
+ });
1792
+ overlayPaths.push(overlayPath);
1793
+ affectedDirectories.add(dirname(overlayPath));
1794
+ }
1795
+ }
1796
+
1797
+ const indexPaths = await regenerateOkfDirectoryIndexes(paths.okfDir);
1798
+ const derivedIndexPaths = await rebuildOkfKnowledgeIndexes({ homeDir: input.homeDir });
1799
+ const logPaths = await appendOrganizerLogs({
1800
+ okfDir: paths.okfDir,
1801
+ affectedDirectories: [...affectedDirectories],
1802
+ plan: input.plan,
1803
+ conceptPaths,
1804
+ overlayPaths,
1805
+ skippedCandidates,
1806
+ needsHumanCandidates,
1807
+ });
1808
+ await rm(tmpRunDir, { recursive: true, force: true });
1809
+ await rm(failedPlanPath, { force: true });
1810
+
1811
+ return {
1812
+ planPath,
1813
+ failedPlanPath,
1814
+ evidenceWindowPath: input.evidenceWindowPath ?? null,
1815
+ conceptPaths,
1816
+ overlayPaths,
1817
+ skippedCandidates,
1818
+ needsHumanCandidates,
1819
+ indexPaths,
1820
+ derivedIndexPaths,
1821
+ logPaths,
1822
+ };
1823
+ } catch (error) {
1824
+ await writeFailedOkfKnowledgePlanArtifact({
1825
+ homeDir: input.homeDir,
1826
+ projectKey: input.plan.projectKey,
1827
+ runId: input.plan.runId,
1828
+ plan: input.plan,
1829
+ findings: [],
1830
+ error: error instanceof Error ? error.message : String(error),
1831
+ failureKind: "organizer",
1832
+ });
1833
+ await rm(tmpRunDir, { recursive: true, force: true });
1834
+ await writeJson(
1835
+ join(dirname(failedPlanPath), "organizer-report.json"),
1836
+ {
1837
+ schemaVersion: 1,
1838
+ kind: "okf-organizer-report",
1839
+ projectKey: input.plan.projectKey,
1840
+ runId: input.plan.runId,
1841
+ status: "failed",
1842
+ error: error instanceof Error ? error.message : String(error),
1843
+ },
1844
+ { overwrite: true },
1845
+ );
1846
+ throw error;
1847
+ }
1848
+ }
1849
+
1850
+ export async function readFailedOkfKnowledgePlan(input: {
1851
+ homeDir: string;
1852
+ projectKey: string;
1853
+ runId: string;
1854
+ }): Promise<{ path: string; plan: OkfKnowledgePlan }> {
1855
+ const artifact = await readFailedOkfKnowledgePlanArtifact(input);
1856
+ if (artifact.artifact.plan === undefined) {
1857
+ throw new Error("Failed OKF knowledge plan is not resumable.");
1858
+ }
1859
+ const plan = artifact.artifact.plan;
1860
+ validateOkfKnowledgePlan(plan);
1861
+ return { path: artifact.path, plan };
1862
+ }
1863
+
1864
+ export async function writeFailedOkfKnowledgePlanArtifact(input: {
1865
+ homeDir: string;
1866
+ projectKey: string;
1867
+ runId: string;
1868
+ rawPlan?: unknown;
1869
+ plan?: OkfKnowledgePlan;
1870
+ findings: OkfKnowledgePlanValidationFinding[];
1871
+ error: string;
1872
+ failureKind: "validation" | "organizer";
1873
+ }): Promise<{ path: string; artifact: OkfKnowledgeFailedPlanArtifact }> {
1874
+ const path = resolveFailedPlanPath(input.homeDir, input.projectKey, input.runId);
1875
+ const artifact: OkfKnowledgeFailedPlanArtifact = {
1876
+ schemaVersion: 1,
1877
+ kind: "okf-knowledge-failed-plan",
1878
+ failureKind: input.failureKind,
1879
+ projectKey: sanitizePlanStorageId("projectKey", input.projectKey),
1880
+ runId: sanitizePlanStorageId("runId", input.runId),
1881
+ createdAt: new Date().toISOString(),
1882
+ resumable: input.failureKind === "organizer" && input.plan !== undefined,
1883
+ findings: input.findings.map((finding) => ({
1884
+ path: sanitizeOkfText(finding.path),
1885
+ code: sanitizeOkfText(finding.code),
1886
+ severity: finding.severity,
1887
+ message: sanitizeOkfText(finding.message),
1888
+ })),
1889
+ error: sanitizeOkfText(input.error),
1890
+ ...(input.rawPlan === undefined
1891
+ ? {}
1892
+ : { redactedPlan: sanitizeReviewQueueValue(input.rawPlan) }),
1893
+ ...(input.failureKind === "organizer" && input.plan !== undefined ? { plan: input.plan } : {}),
1894
+ };
1895
+ await writeJson(path, artifact, { overwrite: true });
1896
+ return { path, artifact };
1897
+ }
1898
+
1899
+ export async function readFailedOkfKnowledgePlanArtifact(input: {
1900
+ homeDir: string;
1901
+ projectKey: string;
1902
+ runId: string;
1903
+ }): Promise<{ path: string; artifact: OkfKnowledgeFailedPlanArtifact }> {
1904
+ const path = resolveFailedPlanPath(input.homeDir, input.projectKey, input.runId);
1905
+ const value = JSON.parse(await readFile(path, "utf8")) as unknown;
1906
+ if (isRecord(value) && value.kind === "okf-knowledge-failed-plan") {
1907
+ const artifact = value as unknown as OkfKnowledgeFailedPlanArtifact;
1908
+ validateFailedPlanArtifact(artifact);
1909
+ return { path, artifact };
1910
+ }
1911
+ const legacyPlan = value as OkfKnowledgePlan;
1912
+ validateOkfKnowledgePlan(legacyPlan);
1913
+ return {
1914
+ path,
1915
+ artifact: {
1916
+ schemaVersion: 1,
1917
+ kind: "okf-knowledge-failed-plan",
1918
+ failureKind: "organizer",
1919
+ projectKey: legacyPlan.projectKey,
1920
+ runId: legacyPlan.runId,
1921
+ createdAt: new Date().toISOString(),
1922
+ resumable: true,
1923
+ plan: legacyPlan,
1924
+ findings: [],
1925
+ error: "Legacy failed OKF knowledge plan.",
1926
+ },
1927
+ };
1928
+ }
1929
+
1930
+ export async function resumeFailedOkfKnowledgePlan(input: {
1931
+ homeDir: string;
1932
+ projectKey: string;
1933
+ runId: string;
1934
+ overwrite?: boolean;
1935
+ }): Promise<OkfKnowledgeActivationResult> {
1936
+ const { plan } = await readFailedOkfKnowledgePlan(input);
1937
+ return await activateOkfKnowledgePlan({
1938
+ homeDir: input.homeDir,
1939
+ plan,
1940
+ overwrite: input.overwrite ?? true,
1941
+ evidenceWindowPath: null,
1942
+ });
1943
+ }
1944
+
1945
+ export async function discardFailedOkfKnowledgePlan(input: {
1946
+ homeDir: string;
1947
+ projectKey: string;
1948
+ runId: string;
1949
+ }): Promise<{ path: string }> {
1950
+ const path = resolveFailedPlanPath(input.homeDir, input.projectKey, input.runId);
1951
+ await rm(path, { force: true });
1952
+ return { path };
1953
+ }
1954
+
1955
+ export async function rebuildOkfKnowledgeIndexes(input: { homeDir: string }): Promise<string[]> {
1956
+ const paths = resolveOkfKnowledgePaths(input.homeDir);
1957
+ await mkdir(paths.indexesDir, { recursive: true });
1958
+ const concepts = (await listOkfKnowledgeConcepts({ homeDir: input.homeDir })).filter(
1959
+ isActiveOkfConcept,
1960
+ );
1961
+ const conceptSummaries = concepts.map((concept) => ({
1962
+ id: concept.id,
1963
+ type: concept.type,
1964
+ title: concept.title,
1965
+ description: concept.description,
1966
+ sourceLink: concept.sourceLink,
1967
+ stableKey: concept.stableKey,
1968
+ reviewState: concept.reviewState,
1969
+ tags: concept.tags,
1970
+ repoTags: concept.repoTags,
1971
+ roleTags: concept.roleTags,
1972
+ workflowTags: concept.workflowTags,
1973
+ pathScopes: concept.pathScopes,
1974
+ }));
1975
+ const pathsWritten = [
1976
+ join(paths.indexesDir, "index.json"),
1977
+ join(paths.indexesDir, "concepts.json"),
1978
+ join(paths.indexesDir, "repos.json"),
1979
+ join(paths.indexesDir, "roles.json"),
1980
+ join(paths.indexesDir, "workflows.json"),
1981
+ ];
1982
+ await writeJson(
1983
+ pathsWritten[0] as string,
1984
+ {
1985
+ schemaVersion: 1,
1986
+ kind: "okf-knowledge-index",
1987
+ conceptCount: concepts.length,
1988
+ updatedAt: new Date().toISOString(),
1989
+ },
1990
+ { overwrite: true },
1991
+ );
1992
+ await writeJson(
1993
+ pathsWritten[1] as string,
1994
+ {
1995
+ schemaVersion: 1,
1996
+ kind: "okf-concepts-index",
1997
+ concepts: conceptSummaries,
1998
+ },
1999
+ { overwrite: true },
2000
+ );
2001
+ await writeJson(
2002
+ pathsWritten[2] as string,
2003
+ {
2004
+ schemaVersion: 1,
2005
+ kind: "okf-repos-index",
2006
+ repos: groupConceptsByTag(concepts, "repoTags"),
2007
+ },
2008
+ { overwrite: true },
2009
+ );
2010
+ await writeJson(
2011
+ pathsWritten[3] as string,
2012
+ {
2013
+ schemaVersion: 1,
2014
+ kind: "okf-roles-index",
2015
+ roles: groupConceptsByTag(concepts, "roleTags"),
2016
+ },
2017
+ { overwrite: true },
2018
+ );
2019
+ await writeJson(
2020
+ pathsWritten[4] as string,
2021
+ {
2022
+ schemaVersion: 1,
2023
+ kind: "okf-workflows-index",
2024
+ workflows: groupConceptsByTag(concepts, "workflowTags"),
2025
+ },
2026
+ { overwrite: true },
2027
+ );
2028
+ return pathsWritten;
2029
+ }
2030
+
2031
+ export async function listOkfKnowledgeConcepts(input: {
2032
+ homeDir: string;
2033
+ projectKey?: string;
2034
+ roleId?: string;
2035
+ workflowId?: string;
2036
+ paths?: string[];
2037
+ }): Promise<OkfKnowledgeConcept[]> {
2038
+ const okfDir = resolveOkfKnowledgePaths(input.homeDir).okfDir;
2039
+ if (!(await pathExists(okfDir))) return [];
2040
+ const files = await listMarkdownFiles(okfDir);
2041
+ const concepts: OkfKnowledgeConcept[] = [];
2042
+ for (const file of files) {
2043
+ if (RESERVED_OKF_FILENAMES.has(file.name)) continue;
2044
+ const content = await readFile(file.path, "utf8");
2045
+ const parsed = parseOkfConceptFile(okfDir, file.path, content);
2046
+ if (parsed !== null) concepts.push(parsed);
2047
+ }
2048
+ return concepts
2049
+ .filter((concept) => matchesConceptFilters(concept, input))
2050
+ .sort((left, right) => left.id.localeCompare(right.id));
2051
+ }
2052
+
2053
+ export async function readOkfKnowledgeConcept(input: {
2054
+ homeDir: string;
2055
+ conceptId: string;
2056
+ }): Promise<OkfKnowledgeConcept> {
2057
+ const id = normalizeConceptId(input.conceptId);
2058
+ const concepts = await listOkfKnowledgeConcepts({ homeDir: input.homeDir });
2059
+ const matches = concepts.filter(
2060
+ (concept) => concept.id === id || concept.sourceLink === `/${id}.md`,
2061
+ );
2062
+ if (matches.length === 0) throw new Error(`OKF knowledge concept not found: ${input.conceptId}`);
2063
+ if (matches.length > 1) throw new Error(`OKF knowledge concept is ambiguous: ${input.conceptId}`);
2064
+ return matches[0] as OkfKnowledgeConcept;
2065
+ }
2066
+
2067
+ export async function queryOkfKnowledge(input: {
2068
+ homeDir: string;
2069
+ projectKey?: string;
2070
+ roleId?: string;
2071
+ workflowId?: string;
2072
+ queryText?: string;
2073
+ paths?: string[];
2074
+ limit?: number;
2075
+ includeStale?: boolean;
2076
+ now?: string | Date;
2077
+ }): Promise<OkfKnowledgeQueryResult> {
2078
+ const okfDir = resolveOkfKnowledgePaths(input.homeDir).okfDir;
2079
+ const scope = normalizeKnowledgeQueryScope(input);
2080
+ const queryText = normalizeKnowledgeQueryText(input.queryText);
2081
+ const now = normalizeQueryNow(input.now);
2082
+ const warnings: string[] = [];
2083
+ if (!(await pathExists(okfDir))) {
2084
+ return {
2085
+ projectKey: scope.projectKey,
2086
+ roleId: scope.roleId,
2087
+ workflowId: scope.workflowId,
2088
+ ...(queryText === undefined ? {} : { queryText }),
2089
+ paths: scope.paths,
2090
+ items: [],
2091
+ warnings,
2092
+ };
2093
+ }
2094
+
2095
+ const files = await listMarkdownFiles(okfDir);
2096
+ const candidates: RankedOkfContextCandidate[] = [];
2097
+ for (const file of files) {
2098
+ if (RESERVED_OKF_FILENAMES.has(file.name)) continue;
2099
+ const content = await readFile(file.path, "utf8");
2100
+ const sourceLink = toOkfLink(okfDir, file.path);
2101
+ if (isUnsafeOkfQueryContent(content)) {
2102
+ warnings.push(`Omitted unsafe OKF knowledge item: ${sourceLink}. Run evodev knowledge lint.`);
2103
+ continue;
2104
+ }
2105
+ const parsed = parseOkfConceptFile(okfDir, file.path, content);
2106
+ if (parsed === null) continue;
2107
+ const lifecycleEligibility = resolveOkfConceptQueryEligibility(parsed, {
2108
+ includeStale: input.includeStale === true,
2109
+ now,
2110
+ });
2111
+ if (lifecycleEligibility.include === false) continue;
2112
+ const match = matchOkfConceptForContext(parsed, scope);
2113
+ if (match === null) continue;
2114
+ const section = resolveContextSection(parsed);
2115
+ const structuredScore = calculateOkfContextScore(parsed, match);
2116
+ const lexical =
2117
+ queryText === undefined
2118
+ ? { score: 0, reasons: [] }
2119
+ : scoreLexicalKnowledgeDocument(createLexicalKnowledgeDocumentFromOkfConcept(parsed), {
2120
+ queryText,
2121
+ });
2122
+ const structuredOverlayExempt =
2123
+ queryText !== undefined &&
2124
+ match.overlayMatch &&
2125
+ (scope.projectKey !== undefined ||
2126
+ scope.roleId !== undefined ||
2127
+ scope.workflowId !== undefined);
2128
+ if (queryText !== undefined && lexical.score <= 0 && !structuredOverlayExempt) continue;
2129
+ if (lifecycleEligibility.stale) {
2130
+ warnings.push(
2131
+ `Included stale OKF knowledge item: ${parsed.sourceLink} (${lifecycleEligibility.reason}).`,
2132
+ );
2133
+ }
2134
+ candidates.push({
2135
+ concept: parsed,
2136
+ section,
2137
+ score: structuredScore + lexical.score + lifecycleEligibility.scoreAdjustment,
2138
+ matchReasons: [
2139
+ ...match.reasons,
2140
+ ...(lifecycleEligibility.stale ? ["lifecycle:stale"] : []),
2141
+ ...lexical.reasons,
2142
+ ...(queryText !== undefined && lexical.score <= 0 && structuredOverlayExempt
2143
+ ? ["q:scope-overlay"]
2144
+ : []),
2145
+ ],
2146
+ });
2147
+ }
2148
+
2149
+ const limit = input.limit ?? 12;
2150
+ const items = candidates
2151
+ .sort((left, right) => {
2152
+ const score = right.score - left.score;
2153
+ if (score !== 0) return score;
2154
+ const section =
2155
+ contextSectionSortWeight(left.section) - contextSectionSortWeight(right.section);
2156
+ if (section !== 0) return section;
2157
+ return left.concept.id.localeCompare(right.concept.id);
2158
+ })
2159
+ .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
+ }));
2171
+
2172
+ return {
2173
+ projectKey: scope.projectKey,
2174
+ roleId: scope.roleId,
2175
+ workflowId: scope.workflowId,
2176
+ ...(queryText === undefined ? {} : { queryText }),
2177
+ paths: scope.paths,
2178
+ items,
2179
+ warnings,
2180
+ };
2181
+ }
2182
+
2183
+ export function createLexicalKnowledgeDocumentFromOkfConcept(
2184
+ concept: OkfKnowledgeConcept,
2185
+ ): LexicalKnowledgeDocument {
2186
+ return createLexicalKnowledgeDocument({
2187
+ id: concept.id,
2188
+ sourceType: "okf",
2189
+ sourceLink: concept.sourceLink,
2190
+ section: resolveContextSection(concept),
2191
+ title: concept.title,
2192
+ description: concept.description,
2193
+ tags: concept.tags,
2194
+ repoTags: concept.repoTags,
2195
+ roleTags: concept.roleTags,
2196
+ workflowTags: concept.workflowTags,
2197
+ pathScopes: concept.pathScopes,
2198
+ stableKey: concept.stableKey,
2199
+ headings: extractMarkdownHeadings(concept.body),
2200
+ bodySummary: summarizeLexicalBody(concept.body),
2201
+ });
2202
+ }
2203
+
2204
+ export function createLexicalKnowledgeDocumentFromEvosCase(
2205
+ evosCase: EvolutionEvosCase,
2206
+ ): LexicalKnowledgeDocument {
2207
+ return createLexicalKnowledgeDocument({
2208
+ id: evosCase.id,
2209
+ sourceType: "evos-case",
2210
+ sourceLink: `cases/${evosCase.projectKey}/${evosCase.id}.json`,
2211
+ section: "accepted-evos-cases",
2212
+ title: evosCase.title,
2213
+ description: evosCase.expectedFutureBehavior || evosCase.result.summary || evosCase.title,
2214
+ tags: evosCase.tags,
2215
+ repoTags: [evosCase.projectKey],
2216
+ roleTags: evosCase.roleTags,
2217
+ workflowTags: evosCase.tags
2218
+ .filter((tag) => tag.startsWith("workflow-") || tag.startsWith("workflow:"))
2219
+ .map((tag) => tag.replace(/^workflow[:-]/u, "")),
2220
+ pathScopes: [],
2221
+ stableKey: `evos:${evosCase.projectKey}:${evosCase.id}`,
2222
+ headings: ["Trigger", "Intervention", "Result", "Expected Future Behavior"],
2223
+ bodySummary: [
2224
+ evosCase.trigger.summary,
2225
+ evosCase.intervention.summary,
2226
+ evosCase.result.summary,
2227
+ evosCase.expectedFutureBehavior,
2228
+ ...evosCase.result.verificationSignals,
2229
+ ].join(" "),
2230
+ });
2231
+ }
2232
+
2233
+ export function scoreLexicalKnowledgeDocument(
2234
+ document: LexicalKnowledgeDocument,
2235
+ input: { queryText?: string },
2236
+ ): LexicalKnowledgeScore {
2237
+ const queryText = normalizeKnowledgeQueryText(input.queryText);
2238
+ if (queryText === undefined) return { score: 0, reasons: [] };
2239
+ const query = parseLexicalQuery(queryText);
2240
+ if (
2241
+ query.tokens.length === 0 &&
2242
+ query.pathAnchors.length === 0 &&
2243
+ query.exactAnchors.length === 0
2244
+ ) {
2245
+ return { score: 0, reasons: [] };
2246
+ }
2247
+
2248
+ let score = 0;
2249
+ const reasons: string[] = [];
2250
+ const add = (value: number, reason: string) => {
2251
+ const safeReason = sanitizeLexicalReason(reason);
2252
+ if (safeReason === "" || reasons.includes(safeReason)) return;
2253
+ score += value;
2254
+ reasons.push(safeReason);
2255
+ };
2256
+
2257
+ const titleTokens = new Set(tokenizeLexicalText(document.title));
2258
+ const descriptionTokens = new Set(tokenizeLexicalText(document.description));
2259
+ const headingTokens = new Set(document.headings.flatMap(tokenizeLexicalText));
2260
+ const tagTokens = new Set(
2261
+ [
2262
+ ...document.tags,
2263
+ ...document.repoTags,
2264
+ ...document.roleTags,
2265
+ ...document.workflowTags,
2266
+ ].flatMap((tag) => [sanitizeSlug(tag), ...tokenizeLexicalText(tag)]),
2267
+ );
2268
+ const pathValues = [
2269
+ document.id,
2270
+ document.sourceLink,
2271
+ document.stableKey,
2272
+ ...document.pathScopes,
2273
+ ...document.headings,
2274
+ ];
2275
+ const pathTokens = new Set(pathValues.flatMap(tokenizeLexicalText));
2276
+ const bodyTokens = new Set(tokenizeLexicalText(document.bodySummary));
2277
+ const safeTokens = new Set(document.safeTokens);
2278
+ const allText = normalizeLexicalComparable(
2279
+ [
2280
+ document.id,
2281
+ document.sourceLink,
2282
+ document.stableKey,
2283
+ document.title,
2284
+ document.description,
2285
+ ...document.tags,
2286
+ ...document.repoTags,
2287
+ ...document.roleTags,
2288
+ ...document.workflowTags,
2289
+ ...document.pathScopes,
2290
+ ...document.headings,
2291
+ document.bodySummary,
2292
+ ].join(" "),
2293
+ );
2294
+
2295
+ for (const anchor of query.pathAnchors) {
2296
+ if (pathAnchorMatches(anchor, pathValues)) add(90, `q:path:${anchor}`);
2297
+ }
2298
+ for (const anchor of query.exactAnchors) {
2299
+ if (allText.includes(normalizeLexicalComparable(anchor))) add(80, `q:exact:${anchor}`);
2300
+ }
2301
+ for (const anchor of query.scriptAnchors) {
2302
+ const scriptName = anchor.replace(/^(?:bun|npm|pnpm|yarn)-run-/u, "");
2303
+ if (
2304
+ allText.includes(normalizeLexicalComparable(anchor)) ||
2305
+ allText.includes(normalizeLexicalComparable(scriptName)) ||
2306
+ safeTokens.has(scriptName)
2307
+ ) {
2308
+ add(75, `q:script:${anchor}`);
2309
+ }
2310
+ }
2311
+
2312
+ for (const token of query.tokens) {
2313
+ if (token.length < 3) continue;
2314
+ if (titleTokens.has(token)) {
2315
+ add(34, `q:title:${token}`);
2316
+ continue;
2317
+ }
2318
+ if (tagTokens.has(token)) {
2319
+ add(32, `q:tag:${token}`);
2320
+ continue;
2321
+ }
2322
+ if (pathTokens.has(token) || pathAnchorMatches(token, pathValues)) {
2323
+ add(30, `q:path:${token}`);
2324
+ continue;
2325
+ }
2326
+ if (headingTokens.has(token)) {
2327
+ add(18, `q:heading:${token}`);
2328
+ continue;
2329
+ }
2330
+ if (descriptionTokens.has(token)) {
2331
+ add(12, `q:description:${token}`);
2332
+ continue;
2333
+ }
2334
+ if (bodyTokens.has(token)) {
2335
+ add(8, `q:body:${token}`);
2336
+ continue;
2337
+ }
2338
+ if (safeTokens.has(token)) {
2339
+ add(3, `q:token:${token}`);
2340
+ }
2341
+ }
2342
+
2343
+ return { score, reasons };
2344
+ }
2345
+
2346
+ export function rankKnowledgeContextItems(
2347
+ items: OkfKnowledgeContextItem[],
2348
+ limit?: number,
2349
+ ): OkfKnowledgeContextItem[] {
2350
+ return [...items]
2351
+ .sort((left, right) => {
2352
+ const score = (right.score ?? 0) - (left.score ?? 0);
2353
+ if (score !== 0) return score;
2354
+ const section =
2355
+ contextSectionSortWeight(left.section) - contextSectionSortWeight(right.section);
2356
+ if (section !== 0) return section;
2357
+ if (left.sourceType !== right.sourceType) return left.sourceType === "okf" ? -1 : 1;
2358
+ const id = left.id.localeCompare(right.id);
2359
+ if (id !== 0) return id;
2360
+ return left.sourceLink.localeCompare(right.sourceLink);
2361
+ })
2362
+ .slice(0, limit ?? items.length)
2363
+ .map((item, index) => ({ ...item, rank: index + 1 }));
2364
+ }
2365
+
2366
+ export async function queryScopedOkfKnowledgeContext(input: {
2367
+ homeDir: string;
2368
+ projectKey?: string;
2369
+ roleId?: string;
2370
+ workflowId?: string;
2371
+ queryText?: string;
2372
+ paths?: string[];
2373
+ limit?: number;
2374
+ includeStale?: boolean;
2375
+ now?: string | Date;
2376
+ }): Promise<OkfKnowledgeQueryResult> {
2377
+ 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
+ });
2384
+ return mergeAcceptedEvosCasesIntoKnowledgeQuery(
2385
+ result,
2386
+ evosCases.cases,
2387
+ evosCases.warnings,
2388
+ input.limit,
2389
+ );
2390
+ }
2391
+
2392
+ export function mergeAcceptedEvosCasesIntoKnowledgeQuery(
2393
+ result: OkfKnowledgeQueryResult,
2394
+ cases: EvolutionEvosCase[],
2395
+ warnings: string[] = [],
2396
+ limit?: number,
2397
+ ): OkfKnowledgeQueryResult {
2398
+ const startRank = result.items.length + 1;
2399
+ const evosItems = cases.flatMap<OkfKnowledgeContextItem>((evosCase, index) => {
2400
+ const document = createLexicalKnowledgeDocumentFromEvosCase(evosCase);
2401
+ const lexical = scoreLexicalKnowledgeDocument(document, { queryText: result.queryText });
2402
+ if (result.queryText !== undefined && lexical.score <= 0) return [];
2403
+ const structuredReasons = [
2404
+ "active-review-state",
2405
+ `repo:${evosCase.projectKey}`,
2406
+ ...(result.roleId !== undefined && evosCase.roleTags.includes(result.roleId)
2407
+ ? [`role:${result.roleId}`]
2408
+ : []),
2409
+ ];
2410
+ const structuredScore =
2411
+ (result.projectKey !== undefined && evosCase.projectKey === result.projectKey ? 100 : 0) +
2412
+ (result.roleId !== undefined && evosCase.roleTags.includes(result.roleId) ? 100 : 0);
2413
+ return [
2414
+ {
2415
+ id: evosCase.id,
2416
+ sourceType: "evos-case",
2417
+ sourceLink: document.sourceLink,
2418
+ section: "accepted-evos-cases",
2419
+ rank: startRank + index,
2420
+ score: structuredScore + lexical.score,
2421
+ title: evosCase.title,
2422
+ summary: evosCase.expectedFutureBehavior || evosCase.result.summary || evosCase.title,
2423
+ matchReasons:
2424
+ lexical.reasons.length === 0
2425
+ ? structuredReasons
2426
+ : [...structuredReasons, ...lexical.reasons],
2427
+ },
2428
+ ];
2429
+ });
2430
+ const items = rankKnowledgeContextItems(
2431
+ [...result.items, ...evosItems],
2432
+ limit ?? result.items.length + evosItems.length,
2433
+ );
2434
+ return {
2435
+ ...result,
2436
+ items,
2437
+ warnings: [...result.warnings, ...warnings],
2438
+ };
2439
+ }
2440
+
2441
+ export async function createScopedKnowledgeContextPack(input: {
2442
+ homeDir: string;
2443
+ projectKey?: string;
2444
+ roleId?: string;
2445
+ workflowId?: string;
2446
+ paths?: string[];
2447
+ queryText?: string;
2448
+ limit?: number;
2449
+ }): Promise<ScopedKnowledgeContextPack | null> {
2450
+ const result = await queryScopedOkfKnowledgeContext(input);
2451
+ if (result.items.length === 0) return null;
2452
+ const scope: ScopedKnowledgeContextPackScope = {
2453
+ ...(result.projectKey === undefined ? {} : { projectKey: result.projectKey }),
2454
+ ...(result.roleId === undefined ? {} : { roleId: result.roleId }),
2455
+ ...(result.workflowId === undefined ? {} : { workflowId: result.workflowId }),
2456
+ paths: result.paths,
2457
+ };
2458
+ const items = result.items.map<ScopedKnowledgeContextPackItem>((item) => ({
2459
+ id: item.id,
2460
+ sourceType: item.sourceType,
2461
+ sourceLink: item.sourceLink,
2462
+ section: item.section,
2463
+ rank: item.rank,
2464
+ title: sanitizeOkfText(item.title),
2465
+ matchReasons: uniqueStrings(item.matchReasons.map(sanitizeLexicalReason)),
2466
+ }));
2467
+ const okfIndexRevision = createKnowledgeContextRevision(items);
2468
+ const queryText = normalizeKnowledgeQueryText(result.queryText);
2469
+ const packSeed = stableJsonStringify({
2470
+ okfIndexRevision,
2471
+ scope,
2472
+ queryText: queryText ?? null,
2473
+ items: items.map((item) => ({
2474
+ id: item.id,
2475
+ sourceType: item.sourceType,
2476
+ sourceLink: item.sourceLink,
2477
+ section: item.section,
2478
+ rank: item.rank,
2479
+ matchReasons: item.matchReasons,
2480
+ })),
2481
+ });
2482
+ return {
2483
+ version: 1,
2484
+ kind: "evodev-scoped-knowledge-context-pack",
2485
+ id: `ctxpack-${sha256Short(packSeed)}`,
2486
+ okfIndexRevision,
2487
+ scope,
2488
+ ...(queryText === undefined ? {} : { queryText }),
2489
+ items,
2490
+ warnings:
2491
+ result.warnings.length === 0
2492
+ ? []
2493
+ : [
2494
+ "Some scoped knowledge items were omitted because they failed safety validation; run evodev knowledge lint.",
2495
+ ],
2496
+ rawContentStored: false,
2497
+ };
2498
+ }
2499
+
2500
+ export function formatScopedKnowledgePromptBlock(pack: ScopedKnowledgeContextPack): string {
2501
+ return [
2502
+ "EvoDev Scoped Knowledge Context",
2503
+ `Context pack: ${sanitizeOkfText(pack.id)}`,
2504
+ `Project: ${pack.scope.projectKey ?? "all"}`,
2505
+ `Role: ${pack.scope.roleId ?? "any"}`,
2506
+ `Workflow: ${pack.scope.workflowId ?? "any"}`,
2507
+ ...(pack.queryText === undefined ? [] : [`Query: ${sanitizeOkfText(pack.queryText)}`]),
2508
+ `Paths: ${pack.scope.paths.length === 0 ? "all" : pack.scope.paths.join(", ")}`,
2509
+ "Raw content stored: false",
2510
+ "",
2511
+ "Applicable items:",
2512
+ ...pack.items.flatMap((item) => [
2513
+ `- ${item.rank}. ${sanitizeOkfText(item.id)} (${item.section}; ${item.sourceType})`,
2514
+ ` Source: ${sanitizeOkfText(item.sourceLink)}`,
2515
+ ` Match: ${item.matchReasons.length === 0 ? "generic" : item.matchReasons.join(", ")}`,
2516
+ ]),
2517
+ ...(pack.warnings.length === 0
2518
+ ? []
2519
+ : [
2520
+ "",
2521
+ "Warnings:",
2522
+ "- Some scoped knowledge items were omitted because they failed safety validation; run evodev knowledge lint.",
2523
+ ]),
2524
+ ].join("\n");
2525
+ }
2526
+
2527
+ export function resolveContextInjectionReceiptPath(input: {
2528
+ homeDir: string;
2529
+ sessionKey: string;
2530
+ contextPackId: string;
2531
+ }): string {
2532
+ const stateDir = resolveEvoDevPaths(input.homeDir).stateDir;
2533
+ return join(
2534
+ stateDir,
2535
+ "context-injections",
2536
+ sanitizeReceiptPathSegment(input.sessionKey),
2537
+ `${sanitizeReceiptPathSegment(input.contextPackId)}.json`,
2538
+ );
2539
+ }
2540
+
2541
+ export async function hasContextInjectionReceipt(input: {
2542
+ homeDir: string;
2543
+ sessionKey: string;
2544
+ contextPackId: string;
2545
+ }): Promise<boolean> {
2546
+ return pathExists(resolveContextInjectionReceiptPath(input));
2547
+ }
2548
+
2549
+ export async function writeContextInjectionReceipt(input: {
2550
+ homeDir: string;
2551
+ sessionKey: string;
2552
+ pack: ScopedKnowledgeContextPack;
2553
+ trigger: ContextInjectionTrigger;
2554
+ hookEventId?: string | null;
2555
+ injectedAt?: string;
2556
+ }): Promise<{ path: string; receipt: ContextInjectionReceipt }> {
2557
+ const receipt: ContextInjectionReceipt = {
2558
+ version: 1,
2559
+ contextPackId: input.pack.id,
2560
+ okfIndexRevision: input.pack.okfIndexRevision,
2561
+ scope: input.pack.scope,
2562
+ itemIds: input.pack.items.map((item) => item.id),
2563
+ injectedAt: input.injectedAt ?? new Date().toISOString(),
2564
+ hookEventId: input.hookEventId ?? null,
2565
+ trigger: input.trigger,
2566
+ rawContentStored: false,
2567
+ };
2568
+ const path = resolveContextInjectionReceiptPath({
2569
+ homeDir: input.homeDir,
2570
+ sessionKey: input.sessionKey,
2571
+ contextPackId: input.pack.id,
2572
+ });
2573
+ await writeJson(path, receipt, { overwrite: true });
2574
+ return { path, receipt };
2575
+ }
2576
+
2577
+ export async function markOkfKnowledgeConceptStale(input: {
2578
+ homeDir: string;
2579
+ conceptId: string;
2580
+ now?: string | Date;
2581
+ }): Promise<OkfKnowledgeLifecycleMutationResult> {
2582
+ const target = await readMutableOkfConcept(input.homeDir, input.conceptId);
2583
+ const lifecycle: OkfKnowledgeLifecycle = {
2584
+ ...target.concept.lifecycle,
2585
+ status: "stale",
2586
+ revokedAt: null,
2587
+ revokedReason: null,
2588
+ };
2589
+ await writeConceptLifecycleFrontmatter({
2590
+ path: target.concept.path,
2591
+ content: target.content,
2592
+ reviewState: "stale",
2593
+ lifecycle,
2594
+ });
2595
+ const logPaths = await appendLifecycleLogs({
2596
+ okfDir: target.paths.okfDir,
2597
+ conceptPaths: [target.concept.path],
2598
+ entry: `**Lifecycle**: Marked \`${target.concept.id}\` stale.`,
2599
+ });
2600
+ const indexPaths = await rebuildOkfKnowledgeIndexes({ homeDir: input.homeDir });
2601
+ return { conceptPaths: [target.concept.path], logPaths, indexPaths };
2602
+ }
2603
+
2604
+ export async function revokeOkfKnowledgeConcept(input: {
2605
+ homeDir: string;
2606
+ conceptId: string;
2607
+ reason: string;
2608
+ now?: string | Date;
2609
+ }): Promise<OkfKnowledgeLifecycleMutationResult> {
2610
+ const reason = validateLifecycleReason(input.reason);
2611
+ const target = await readMutableOkfConcept(input.homeDir, input.conceptId);
2612
+ const timestamp = normalizeQueryNow(input.now).toISOString();
2613
+ const lifecycle: OkfKnowledgeLifecycle = {
2614
+ ...target.concept.lifecycle,
2615
+ status: "revoked",
2616
+ revokedAt: timestamp,
2617
+ revokedReason: reason,
2618
+ };
2619
+ await writeConceptLifecycleFrontmatter({
2620
+ path: target.concept.path,
2621
+ content: target.content,
2622
+ reviewState: "revoked",
2623
+ lifecycle,
2624
+ });
2625
+ const logPaths = await appendLifecycleLogs({
2626
+ okfDir: target.paths.okfDir,
2627
+ conceptPaths: [target.concept.path],
2628
+ entry: `**Lifecycle**: Revoked \`${target.concept.id}\`; reason=${reason}.`,
2629
+ });
2630
+ const indexPaths = await rebuildOkfKnowledgeIndexes({ homeDir: input.homeDir });
2631
+ return { conceptPaths: [target.concept.path], logPaths, indexPaths };
2632
+ }
2633
+
2634
+ export async function supersedeOkfKnowledgeConcept(input: {
2635
+ homeDir: string;
2636
+ oldConceptId: string;
2637
+ newConceptId: string;
2638
+ now?: string | Date;
2639
+ }): Promise<OkfKnowledgeLifecycleMutationResult> {
2640
+ const oldId = normalizeConceptId(input.oldConceptId);
2641
+ const newId = normalizeConceptId(input.newConceptId);
2642
+ if (oldId === newId) throw new Error("Lifecycle supersede requires different concept ids.");
2643
+ const oldTarget = await readMutableOkfConcept(input.homeDir, oldId);
2644
+ const newTarget = await readMutableOkfConcept(input.homeDir, newId);
2645
+ if (
2646
+ newTarget.concept.lifecycle.status === "deprecated" ||
2647
+ newTarget.concept.lifecycle.status === "revoked" ||
2648
+ newTarget.concept.lifecycle.status === "superseded" ||
2649
+ newTarget.concept.reviewState === "deprecated" ||
2650
+ newTarget.concept.reviewState === "revoked" ||
2651
+ newTarget.concept.reviewState === "superseded"
2652
+ ) {
2653
+ throw new Error(
2654
+ "Lifecycle replacement concept must not be deprecated, revoked, or superseded.",
2655
+ );
2656
+ }
2657
+ const oldLifecycle: OkfKnowledgeLifecycle = {
2658
+ ...oldTarget.concept.lifecycle,
2659
+ status: "superseded",
2660
+ supersededBy: newTarget.concept.id,
2661
+ revokedAt: null,
2662
+ revokedReason: null,
2663
+ };
2664
+ const newLifecycle: OkfKnowledgeLifecycle = {
2665
+ ...newTarget.concept.lifecycle,
2666
+ supersedes: uniqueStrings([...newTarget.concept.lifecycle.supersedes, oldTarget.concept.id]),
2667
+ };
2668
+ await writeConceptLifecycleFrontmatter({
2669
+ path: oldTarget.concept.path,
2670
+ content: oldTarget.content,
2671
+ reviewState: "superseded",
2672
+ lifecycle: oldLifecycle,
2673
+ });
2674
+ await writeConceptLifecycleFrontmatter({
2675
+ path: newTarget.concept.path,
2676
+ content: newTarget.content,
2677
+ reviewState: newTarget.concept.reviewState,
2678
+ lifecycle: newLifecycle,
2679
+ });
2680
+ const conceptPaths = uniqueStrings([oldTarget.concept.path, newTarget.concept.path]);
2681
+ const logPaths = await appendLifecycleLogs({
2682
+ okfDir: oldTarget.paths.okfDir,
2683
+ conceptPaths,
2684
+ entry: `**Lifecycle**: Superseded \`${oldTarget.concept.id}\` with \`${newTarget.concept.id}\`.`,
2685
+ });
2686
+ const indexPaths = await rebuildOkfKnowledgeIndexes({ homeDir: input.homeDir });
2687
+ return { conceptPaths, logPaths, indexPaths };
2688
+ }
2689
+
2690
+ async function readMutableOkfConcept(
2691
+ homeDir: string,
2692
+ conceptId: string,
2693
+ ): Promise<{
2694
+ paths: OkfPaths;
2695
+ concept: OkfKnowledgeConcept;
2696
+ content: string;
2697
+ }> {
2698
+ const paths = resolveOkfKnowledgePaths(homeDir);
2699
+ const id = normalizeConceptId(conceptId);
2700
+ const files = await listMarkdownFiles(paths.okfDir);
2701
+ const matches: Array<{ concept: OkfKnowledgeConcept; content: string }> = [];
2702
+ for (const file of files) {
2703
+ if (RESERVED_OKF_FILENAMES.has(file.name)) continue;
2704
+ const content = await readFile(file.path, "utf8");
2705
+ const concept = parseOkfConceptFile(paths.okfDir, file.path, content);
2706
+ if (concept === null) continue;
2707
+ if (concept.id === id || concept.sourceLink === `/${id}.md`) {
2708
+ matches.push({ concept, content });
2709
+ }
2710
+ }
2711
+ if (matches.length === 0) throw new Error(`OKF knowledge concept not found: ${conceptId}`);
2712
+ if (matches.length > 1) throw new Error(`OKF knowledge concept is ambiguous: ${conceptId}`);
2713
+ const match = matches[0] as { concept: OkfKnowledgeConcept; content: string };
2714
+ return { paths, concept: match.concept, content: match.content };
2715
+ }
2716
+
2717
+ async function writeConceptLifecycleFrontmatter(input: {
2718
+ path: string;
2719
+ content: string;
2720
+ reviewState: OkfKnowledgeReviewState;
2721
+ lifecycle: OkfKnowledgeLifecycle;
2722
+ }): Promise<void> {
2723
+ const parsed = extractFrontmatter(input.content);
2724
+ if (parsed === null) throw new Error(`OKF concept missing frontmatter: ${input.path}`);
2725
+ const frontmatter = upsertOkfLifecycleFrontmatter(
2726
+ parsed.frontmatter,
2727
+ input.reviewState,
2728
+ input.lifecycle,
2729
+ );
2730
+ await writeFile(input.path, `---\n${frontmatter.trimEnd()}\n---\n${parsed.body}`, "utf8");
2731
+ }
2732
+
2733
+ function upsertOkfLifecycleFrontmatter(
2734
+ frontmatter: string,
2735
+ reviewState: OkfKnowledgeReviewState,
2736
+ lifecycle: OkfKnowledgeLifecycle,
2737
+ ): string {
2738
+ const lines = frontmatter.split("\n");
2739
+ const evodevIndex = lines.findIndex((line) => line.trim() === "evodev:");
2740
+ const replacement = [
2741
+ ` reviewState: ${yamlString(reviewState)}`,
2742
+ ...renderLifecycleYaml(" ", lifecycle).split("\n"),
2743
+ ];
2744
+ if (evodevIndex < 0) return [...lines, "evodev:", ...replacement].join("\n");
2745
+ const evodevEnd = findYamlBlockEnd(lines, evodevIndex, 0);
2746
+ const evodevBlock = lines.slice(evodevIndex + 1, evodevEnd);
2747
+ const filtered: string[] = [];
2748
+ for (let index = 0; index < evodevBlock.length; index += 1) {
2749
+ const line = evodevBlock[index] ?? "";
2750
+ if (/^ {2}reviewState:/u.test(line)) continue;
2751
+ if (/^ {2}lifecycle:\s*$/u.test(line)) {
2752
+ for (index += 1; index < evodevBlock.length; index += 1) {
2753
+ const candidate = evodevBlock[index] ?? "";
2754
+ if (candidate.trim() === "") continue;
2755
+ const indent = candidate.length - candidate.trimStart().length;
2756
+ if (indent <= 2) {
2757
+ index -= 1;
2758
+ break;
2759
+ }
2760
+ }
2761
+ continue;
2762
+ }
2763
+ filtered.push(line);
2764
+ }
2765
+ const insertAfter = Math.max(
2766
+ filtered.findIndex((line) => /^ {2}stableKey:/u.test(line)),
2767
+ filtered.findIndex((line) => /^ {2}schema:/u.test(line)),
2768
+ );
2769
+ const nextBlock =
2770
+ insertAfter < 0
2771
+ ? [...replacement, ...filtered]
2772
+ : [...filtered.slice(0, insertAfter + 1), ...replacement, ...filtered.slice(insertAfter + 1)];
2773
+ return [...lines.slice(0, evodevIndex + 1), ...nextBlock, ...lines.slice(evodevEnd)].join("\n");
2774
+ }
2775
+
2776
+ async function appendLifecycleLogs(input: {
2777
+ okfDir: string;
2778
+ conceptPaths: string[];
2779
+ entry: string;
2780
+ }): Promise<string[]> {
2781
+ const directories = [
2782
+ ...new Set(
2783
+ input.conceptPaths.flatMap((path) => ancestorDirectories(input.okfDir, dirname(path))),
2784
+ ),
2785
+ ];
2786
+ const logPaths: string[] = [];
2787
+ for (const directory of directories) {
2788
+ const logPath = join(directory, "log.md");
2789
+ await prependLogEntry(logPath, sanitizeOkfText(input.entry));
2790
+ logPaths.push(logPath);
2791
+ }
2792
+ return logPaths;
2793
+ }
2794
+
2795
+ function validateLifecycleReason(value: string): string {
2796
+ const reason = value.trim();
2797
+ 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.");
2800
+ }
2801
+ return sanitizeOkfText(reason);
2802
+ }
2803
+
2804
+ export async function lintOkfKnowledge(input: {
2805
+ homeDir: string;
2806
+ stale?: boolean;
2807
+ now?: string | Date;
2808
+ }): Promise<OkfKnowledgeLintResult> {
2809
+ const paths = resolveOkfKnowledgePaths(input.homeDir);
2810
+ const errors: string[] = [];
2811
+ const warnings: string[] = [];
2812
+ const now = normalizeQueryNow(input.now);
2813
+ if (!(await pathExists(paths.okfDir))) {
2814
+ return {
2815
+ ok: true,
2816
+ errors,
2817
+ warnings: ["OKF knowledge bundle does not exist."],
2818
+ conceptCount: 0,
2819
+ };
2820
+ }
2821
+ const directories = await listDirectories(paths.okfDir);
2822
+ for (const directory of directories) {
2823
+ const indexPath = join(directory, "index.md");
2824
+ const logPath = join(directory, "log.md");
2825
+ if (!(await pathExists(indexPath)))
2826
+ errors.push(`Missing index.md: ${displayOkfPath(paths.okfDir, directory)}`);
2827
+ if (!(await pathExists(logPath)))
2828
+ errors.push(`Missing log.md: ${displayOkfPath(paths.okfDir, directory)}`);
2829
+ }
2830
+
2831
+ let conceptCount = 0;
2832
+ const files = await listMarkdownFiles(paths.okfDir);
2833
+ const knownConceptIds = new Set(
2834
+ files
2835
+ .filter((file) => !RESERVED_OKF_FILENAMES.has(file.name))
2836
+ .map((file) => toOkfRelativePath(paths.okfDir, file.path).replace(/\.md$/, "")),
2837
+ );
2838
+ for (const file of files) {
2839
+ const rel = displayOkfPath(paths.okfDir, file.path);
2840
+ const content = await readFile(file.path, "utf8");
2841
+ if (file.name === "index.md") {
2842
+ if (file.path !== join(paths.okfDir, "index.md") && hasFrontmatter(content)) {
2843
+ errors.push(`Non-root index.md must not have frontmatter: ${rel}`);
2844
+ }
2845
+ continue;
2846
+ }
2847
+ if (file.name === "log.md") {
2848
+ if (hasFrontmatter(content)) errors.push(`log.md must not have frontmatter: ${rel}`);
2849
+ continue;
2850
+ }
2851
+ conceptCount += 1;
2852
+ const parsed = extractFrontmatter(content);
2853
+ if (parsed === null) {
2854
+ errors.push(`Concept missing parseable YAML frontmatter: ${rel}`);
2855
+ continue;
2856
+ }
2857
+ const type = readYamlScalar(parsed.frontmatter, "type");
2858
+ if (type === null || type.trim() === "") errors.push(`Concept missing non-empty type: ${rel}`);
2859
+ if (readYamlScalar(parsed.frontmatter, "title") === null)
2860
+ warnings.push(`Concept missing title: ${rel}`);
2861
+ if (readYamlScalar(parsed.frontmatter, "description") === null)
2862
+ warnings.push(`Concept missing description: ${rel}`);
2863
+ if (!parsed.frontmatter.includes("evodev:"))
2864
+ warnings.push(`Concept missing evodev extension: ${rel}`);
2865
+ const reviewState = parseOkfReviewState(
2866
+ readIndentedYamlScalar(parsed.frontmatter, "reviewState") ?? "auto-stored/unreviewed",
2867
+ );
2868
+ if (reviewState === "auto-stored/unreviewed") {
2869
+ warnings.push(`Concept missing active lifecycle reviewState: ${rel}`);
2870
+ }
2871
+ if (FORBIDDEN_OKF_TEXT.test(content))
2872
+ errors.push(`Concept contains forbidden sensitive text: ${rel}`);
2873
+ if (input.stale === true) {
2874
+ const concept = parseOkfConceptFile(paths.okfDir, file.path, content);
2875
+ if (concept !== null) {
2876
+ lintOkfLifecycle({
2877
+ concept,
2878
+ frontmatter: parsed.frontmatter,
2879
+ knownConceptIds,
2880
+ now,
2881
+ errors,
2882
+ warnings,
2883
+ });
2884
+ }
2885
+ }
2886
+ }
2887
+ return { ok: errors.length === 0, errors, warnings, conceptCount };
2888
+ }
2889
+
2890
+ export function formatOkfKnowledgeList(concepts: OkfKnowledgeConcept[]): string {
2891
+ if (concepts.length === 0) return "EvoDev OKF knowledge concepts: none";
2892
+ return [
2893
+ "EvoDev OKF knowledge concepts",
2894
+ "",
2895
+ ...concepts.map(
2896
+ (concept) => ` - ${concept.id} (${concept.type}): ${concept.title} [${concept.sourceLink}]`,
2897
+ ),
2898
+ ].join("\n");
2899
+ }
2900
+
2901
+ export function formatOkfKnowledgeQuery(result: OkfKnowledgeQueryResult): string {
2902
+ const sectionLabels: Array<[OkfKnowledgeContextSection, string]> = [
2903
+ ["applicable-knowledge", "Applicable Knowledge"],
2904
+ ["role-attention", "Role Attention"],
2905
+ ["repo-attention", "Repo Attention"],
2906
+ ["workflow-attention", "Workflow Attention"],
2907
+ ["verification", "Verification"],
2908
+ ["accepted-evos-cases", "Accepted Evos Cases"],
2909
+ ];
2910
+ return [
2911
+ "# EvoDev Knowledge Context",
2912
+ "",
2913
+ `Project: ${result.projectKey ?? "all"}`,
2914
+ `Role: ${result.roleId ?? "any"}`,
2915
+ `Workflow: ${result.workflowId ?? "any"}`,
2916
+ ...(result.queryText === undefined ? [] : [`Query: ${sanitizeOkfText(result.queryText)}`]),
2917
+ `Scope: ${result.paths.length === 0 ? "all" : result.paths.join(", ")}`,
2918
+ "",
2919
+ ...sectionLabels.flatMap(([section, label]) => {
2920
+ const items = result.items.filter((item) => item.section === section);
2921
+ return [
2922
+ `## ${label}`,
2923
+ "",
2924
+ ...(items.length === 0
2925
+ ? ["- none"]
2926
+ : items.map(
2927
+ (item) =>
2928
+ `- ${item.summary}\n Source: ${item.sourceLink}\n Match: ${item.matchReasons.join(", ")}\n Rank: ${item.rank}`,
2929
+ )),
2930
+ "",
2931
+ ];
2932
+ }),
2933
+ ...(result.warnings.length === 0
2934
+ ? ["Warnings: none"]
2935
+ : ["Warnings:", ...result.warnings.map((warning) => ` - ${warning}`)]),
2936
+ ].join("\n");
2937
+ }
2938
+
2939
+ export function formatOkfKnowledgePlan(plan: OkfKnowledgePlan): string {
2940
+ return [
2941
+ "EvoDev OKF knowledge plan",
2942
+ "",
2943
+ `Project: ${plan.projectKey}`,
2944
+ `Run: ${plan.runId}`,
2945
+ `Candidates: ${plan.candidates.length}`,
2946
+ ...(plan.candidates.length === 0
2947
+ ? [" - none"]
2948
+ : plan.candidates.map((candidate) => {
2949
+ const scores = resolveCandidateScoresForWrite(candidate);
2950
+ return ` - ${sanitizeOkfText(candidate.id)} (${sanitizeOkfText(candidate.kind)}, ${candidate.decision}, ${resolveCandidateReviewStateForWrite(candidate)}): ${sanitizeOkfText(candidate.title)}; scores evidence=${scores.evidenceStrength} reuse=${scores.reuseValue} action=${scores.actionability} stability=${scores.stability} privacy=${scores.privacyRisk} duplicate=${scores.duplicationRisk}; reason=${sanitizeOkfText(candidate.decisionReason ?? "legacy plan")}`;
2951
+ })),
2952
+ `Conflicts: ${plan.conflicts.length}`,
2953
+ ...plan.conflicts.map(
2954
+ (conflict) =>
2955
+ ` - ${sanitizeOkfText(conflict.candidateId)}: ${sanitizeOkfText(conflict.reason)}`,
2956
+ ),
2957
+ ].join("\n");
2958
+ }
2959
+
2960
+ export function formatOkfKnowledgeLint(result: OkfKnowledgeLintResult): string {
2961
+ return [
2962
+ "EvoDev OKF knowledge lint",
2963
+ "",
2964
+ `Status: ${result.ok ? "PASS" : "FAIL"}`,
2965
+ `Concepts: ${result.conceptCount}`,
2966
+ ...(result.errors.length === 0
2967
+ ? ["Errors: none"]
2968
+ : ["Errors:", ...result.errors.map((item) => ` - ${item}`)]),
2969
+ ...(result.warnings.length === 0
2970
+ ? ["Warnings: none"]
2971
+ : ["Warnings:", ...result.warnings.map((item) => ` - ${item}`)]),
2972
+ ].join("\n");
2973
+ }
2974
+
2975
+ function createCandidateFromKnowledgeRecord(
2976
+ record: EvolutionKnowledgeRecord,
2977
+ batch: EvolutionDistillationBatch,
2978
+ ): OkfKnowledgePlanCandidate {
2979
+ const type = knowledgeKindToOkfType(record.kind);
2980
+ const category = knowledgeKindToDirectory(record.kind);
2981
+ const targetPath = `concepts/${category}/${record.id}.md`;
2982
+ const sourceLink = `/${targetPath}`;
2983
+ const hasVerificationEvidence = batch.evidenceWindow.events.some(
2984
+ (event) => event.kind === "verification" || /test|lint|typecheck|build/i.test(event.summary),
2985
+ );
2986
+ return {
2987
+ id: record.id,
2988
+ decision: "create",
2989
+ kind: record.kind,
2990
+ okfType: type,
2991
+ targetStore: "okf",
2992
+ targetPath,
2993
+ stableKey: `${record.kind}:${record.projectKey}:${record.id}`,
2994
+ confidence: record.confidence,
2995
+ title: record.title,
2996
+ description: record.summary,
2997
+ claim: record.summary,
2998
+ basis: "direct",
2999
+ metadataOnlyEvidence: true,
3000
+ howToApply: record.body,
3001
+ antiCriteria: ["Do not treat this knowledge as a hard policy."],
3002
+ roleTags: record.roleTags,
3003
+ repoTags: [record.projectKey],
3004
+ workflowTags: record.tags.filter((tag) => tag.startsWith("workflow-")),
3005
+ pathScopes: [],
3006
+ relatedConceptLinks: [],
3007
+ overlayUpdates: createOverlayUpdates(record.projectKey, record.roleTags, [], sourceLink),
3008
+ scores: DEFAULT_OKF_CANDIDATE_SCORES,
3009
+ decisionReason: "Pending M3 decision scoring.",
3010
+ evidenceRefs: record.provenance.sourceRefs,
3011
+ reviewState: record.reviewState,
3012
+ bodySections: {
3013
+ summary: record.summary,
3014
+ appliesWhen: [`Working in repo scope \`${record.projectKey}\`.`],
3015
+ guidance: [record.body],
3016
+ antiCriteria: ["Do not store raw trace payloads in active knowledge."],
3017
+ verification: hasVerificationEvidence ? ["Use verified local outcomes as evidence."] : [],
3018
+ citations: [],
3019
+ },
3020
+ privacyCheck: createOkfPrivacyCheck(),
3021
+ };
3022
+ }
3023
+
3024
+ function resolveFailedPlanPath(homeDir: string, projectKey: string, runId: string): string {
3025
+ const root = join(resolveEvoDevPaths(homeDir).stateDir, "evolution");
3026
+ const path = join(
3027
+ root,
3028
+ sanitizePlanStorageId("projectKey", projectKey),
3029
+ sanitizePlanStorageId("runId", runId),
3030
+ "knowledge-plan.failed.json",
3031
+ );
3032
+ assertPathDescendant(root, path, "failedPlanPath");
3033
+ return path;
3034
+ }
3035
+
3036
+ async function ensureLocalKnowledgeGitRepository(knowledgeDir: string): Promise<void> {
3037
+ await mkdir(knowledgeDir, { recursive: true });
3038
+ const gitDir = join(knowledgeDir, ".git");
3039
+ if (await pathExists(gitDir)) return;
3040
+
3041
+ await mkdir(join(gitDir, "objects", "info"), { recursive: true });
3042
+ await mkdir(join(gitDir, "objects", "pack"), { recursive: true });
3043
+ await mkdir(join(gitDir, "refs", "heads"), { recursive: true });
3044
+ await mkdir(join(gitDir, "refs", "tags"), { recursive: true });
3045
+ await mkdir(join(gitDir, "info"), { recursive: true });
3046
+ await writeTextIfMissing(join(gitDir, "HEAD"), "ref: refs/heads/main\n");
3047
+ await writeTextIfMissing(
3048
+ join(gitDir, "config"),
3049
+ [
3050
+ "[core]",
3051
+ "\trepositoryformatversion = 0",
3052
+ "\tfilemode = true",
3053
+ "\tbare = false",
3054
+ "\tlogallrefupdates = true",
3055
+ "",
3056
+ ].join("\n"),
3057
+ );
3058
+ await writeTextIfMissing(
3059
+ join(gitDir, "info", "exclude"),
3060
+ [
3061
+ "# EvoDev user-local knowledge git repository.",
3062
+ "# No remote is configured by default.",
3063
+ "",
3064
+ ].join("\n"),
3065
+ );
3066
+ }
3067
+
3068
+ function sanitizePlanStorageId(field: string, value: string): string {
3069
+ const trimmed = value.trim();
3070
+ if (trimmed === "") throw new Error(`Invalid OKF knowledge plan ${field}: ${value}`);
3071
+ if (trimmed.split(/[\\/]+/u).some((segment) => segment === "." || segment === "..")) {
3072
+ throw new Error(`Invalid OKF knowledge plan ${field}: ${value}`);
3073
+ }
3074
+ const sanitized =
3075
+ trimmed
3076
+ .toLowerCase()
3077
+ .replace(/[^a-z0-9._/-]+/gu, "-")
3078
+ .replace(/[\\/]+/gu, "-")
3079
+ .replace(/^-+|-+$/gu, "")
3080
+ .slice(0, 160) || "unknown";
3081
+ if (sanitized === "." || sanitized === "..") {
3082
+ throw new Error(`Invalid OKF knowledge plan ${field}: ${value}`);
3083
+ }
3084
+ return sanitized;
3085
+ }
3086
+
3087
+ function assertPathDescendant(root: string, candidate: string, field: string): void {
3088
+ const normalizedRoot = resolve(root);
3089
+ const normalizedCandidate = resolve(candidate);
3090
+ const relativePath = relative(normalizedRoot, normalizedCandidate);
3091
+ if (relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath)) {
3092
+ throw new Error(`OKF knowledge path ${field} escaped expected root.`);
3093
+ }
3094
+ }
3095
+
3096
+ async function writeTextIfMissing(path: string, value: string): Promise<void> {
3097
+ if (await pathExists(path)) return;
3098
+ await mkdir(dirname(path), { recursive: true });
3099
+ await writeFile(path, value, { encoding: "utf8", flag: "wx" });
3100
+ }
3101
+
3102
+ function createCandidateFromEvosCase(evosCase: EvolutionEvosCase): OkfKnowledgePlanCandidate {
3103
+ const targetPath = `concepts/evos/${evosCase.id}.md`;
3104
+ const sourceLink = `/${targetPath}`;
3105
+ return {
3106
+ id: evosCase.id,
3107
+ decision: "create",
3108
+ kind: "evos-case",
3109
+ okfType: "EvoDev Evolution Case",
3110
+ targetStore: "okf",
3111
+ targetPath,
3112
+ stableKey: `evos:${evosCase.projectKey}:${evosCase.id}`,
3113
+ confidence: evosCase.confidence,
3114
+ title: evosCase.title,
3115
+ description: evosCase.trigger.summary,
3116
+ claim: evosCase.expectedFutureBehavior,
3117
+ basis: "direct",
3118
+ metadataOnlyEvidence: true,
3119
+ howToApply: evosCase.expectedFutureBehavior,
3120
+ antiCriteria: ["Do not directly change runtime behavior from an evos case."],
3121
+ roleTags: evosCase.roleTags,
3122
+ repoTags: [evosCase.projectKey],
3123
+ workflowTags: [],
3124
+ pathScopes: [],
3125
+ relatedConceptLinks: [],
3126
+ overlayUpdates: createOverlayUpdates(evosCase.projectKey, evosCase.roleTags, [], sourceLink),
3127
+ scores: DEFAULT_OKF_CANDIDATE_SCORES,
3128
+ decisionReason: "Pending M3 decision scoring.",
3129
+ evidenceRefs: evosCase.provenance.sourceRefs,
3130
+ reviewState: evosCase.reviewState,
3131
+ bodySections: {
3132
+ summary: evosCase.result.summary,
3133
+ appliesWhen: [evosCase.trigger.summary],
3134
+ guidance: [evosCase.expectedFutureBehavior],
3135
+ antiCriteria: ["Use as contextual evidence, not as a hard rule."],
3136
+ verification: evosCase.result.verificationSignals,
3137
+ citations: [],
3138
+ },
3139
+ privacyCheck: createOkfPrivacyCheck(),
3140
+ };
3141
+ }
3142
+
3143
+ function createOverlayUpdates(
3144
+ projectKey: string,
3145
+ roleTags: string[],
3146
+ workflowTags: string[],
3147
+ sourceLink: string,
3148
+ ): OkfKnowledgeOverlayUpdate[] {
3149
+ return [
3150
+ {
3151
+ targetPath: `repos/${sanitizeSlug(projectKey)}/overview.md`,
3152
+ operation: "append-link",
3153
+ link: sourceLink,
3154
+ },
3155
+ ...roleTags.map((role) => ({
3156
+ targetPath: `roles/${sanitizeSlug(role)}/attention.md`,
3157
+ operation: "append-link" as const,
3158
+ link: sourceLink,
3159
+ })),
3160
+ ...workflowTags.map((workflow) => ({
3161
+ targetPath: `workflows/${sanitizeSlug(workflow)}/attention.md`,
3162
+ operation: "append-link" as const,
3163
+ link: sourceLink,
3164
+ })),
3165
+ ];
3166
+ }
3167
+
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
+ function sanitizeOkfPlanCandidate(candidate: OkfKnowledgePlanCandidate): OkfKnowledgePlanCandidate {
3229
+ return sanitizeReviewQueueValue(candidate) as OkfKnowledgePlanCandidate;
3230
+ }
3231
+
3232
+ function sanitizeReviewQueueValue(value: unknown): unknown {
3233
+ if (typeof value === "string") return sanitizeOkfText(value);
3234
+ if (value === null || value === undefined) return value;
3235
+ if (Array.isArray(value)) return value.map(sanitizeReviewQueueValue);
3236
+ if (typeof value !== "object") return value;
3237
+ return Object.fromEntries(
3238
+ Object.entries(value).map(([key, child]) => [key, sanitizeReviewQueueValue(child)]),
3239
+ );
3240
+ }
3241
+
3242
+ 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);
3249
+ }
3250
+
3251
+ function hasUnsafeCandidateContent(candidate: OkfKnowledgePlanCandidate): boolean {
3252
+ const content = JSON.stringify(candidate);
3253
+ return (
3254
+ FORBIDDEN_OKF_TEXT.test(content) ||
3255
+ FORBIDDEN_OKF_FIELD.test(content) ||
3256
+ PRIVATE_OR_INTERNAL_URL.test(content) ||
3257
+ (candidate.privacyCheck !== undefined &&
3258
+ Object.values(candidate.privacyCheck).some((value) => value !== false))
3259
+ );
3260
+ }
3261
+
3262
+ function hasHighRiskHumanReviewSignal(candidate: unknown): boolean {
3263
+ if (!isRecord(candidate)) return false;
3264
+ const body = isRecord(candidate.bodySections) ? candidate.bodySections : {};
3265
+ const stringArray = (value: unknown): string[] =>
3266
+ Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
3267
+ const stringValue = (value: unknown): string => (typeof value === "string" ? value : "");
3268
+ const values = [
3269
+ stringValue(candidate.id),
3270
+ stringValue(candidate.kind),
3271
+ stringValue(candidate.okfType),
3272
+ stringValue(candidate.targetStore),
3273
+ stringValue(candidate.targetPath),
3274
+ stringValue(candidate.stableKey),
3275
+ stringValue(candidate.title),
3276
+ stringValue(candidate.description),
3277
+ stringValue(candidate.claim),
3278
+ stringValue(candidate.howToApply),
3279
+ ...stringArray(candidate.antiCriteria),
3280
+ ...stringArray(candidate.roleTags),
3281
+ ...stringArray(candidate.repoTags),
3282
+ ...stringArray(candidate.workflowTags),
3283
+ ...stringArray(candidate.pathScopes),
3284
+ ...stringArray(candidate.relatedConceptLinks),
3285
+ stringValue(body.summary),
3286
+ ...stringArray(body.appliesWhen),
3287
+ ...stringArray(body.guidance),
3288
+ ...stringArray(body.antiCriteria),
3289
+ ...stringArray(body.verification),
3290
+ ...stringArray(body.citations),
3291
+ ];
3292
+ return HUMAN_REVIEW_DOMAIN_PATTERN.test(values.join(" "));
3293
+ }
3294
+
3295
+ function explainNeedsHumanDecision(
3296
+ candidate: OkfKnowledgePlanCandidate,
3297
+ scores: OkfKnowledgeCandidateScores,
3298
+ hasVerification: boolean,
3299
+ hasScopeTags: boolean,
3300
+ ): string {
3301
+ const reasons: string[] = [];
3302
+ if (candidate.targetStore !== "okf") reasons.push("target is not user-local OKF");
3303
+ if (candidate.basis !== "direct") reasons.push("basis is inferred");
3304
+ if (!candidate.metadataOnlyEvidence) reasons.push("evidence is not metadata-only");
3305
+ if (!hasVerification) reasons.push("verification evidence is missing");
3306
+ if (!hasScopeTags) reasons.push("repo or role tags are missing");
3307
+ if (scores.evidenceStrength < 4) reasons.push("evidence strength is below auto-accept");
3308
+ if (scores.privacyRisk > 2) reasons.push("privacy risk is above auto-accept");
3309
+ if (scores.stability < 3) reasons.push("stability is below auto-accept");
3310
+ 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"}.`;
3312
+ }
3313
+
3314
+ function resolveCandidateReviewStateForWrite(
3315
+ candidate: OkfKnowledgePlanCandidate,
3316
+ ): OkfKnowledgeReviewState {
3317
+ if (candidate.reviewState === "auto-accepted" || candidate.decision === "auto-accept") {
3318
+ return "auto-accepted";
3319
+ }
3320
+ if (candidate.reviewState === "accepted" || candidate.decision === "create") return "accepted";
3321
+ if (candidate.decision === "update") return "accepted";
3322
+ return candidate.reviewState ?? "auto-stored/unreviewed";
3323
+ }
3324
+
3325
+ function resolveCandidateScoresForWrite(
3326
+ candidate: OkfKnowledgePlanCandidate,
3327
+ ): OkfKnowledgeCandidateScores {
3328
+ return candidate.scores ?? DEFAULT_OKF_CANDIDATE_SCORES;
3329
+ }
3330
+
3331
+ function normalizeComparableText(value: string): string {
3332
+ return sanitizeSlug(value).replace(/[._/-]+/gu, "-");
3333
+ }
3334
+
3335
+ function listMarkdownFilesSync(root: string): Array<{ path: string; name: string }> {
3336
+ if (!existsSync(root)) return [];
3337
+ const entries = readdirSync(root, { withFileTypes: true });
3338
+ const files: Array<{ path: string; name: string }> = [];
3339
+ for (const entry of entries) {
3340
+ const path = join(root, entry.name);
3341
+ if (entry.isDirectory()) {
3342
+ files.push(...listMarkdownFilesSync(path));
3343
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
3344
+ files.push({ path, name: entry.name });
3345
+ }
3346
+ }
3347
+ return files;
3348
+ }
3349
+
3350
+ function knowledgeKindToOkfType(kind: string): string {
3351
+ if (kind === "rule") return "EvoDev Rule";
3352
+ if (kind === "workflow-hint") return "EvoDev Pattern";
3353
+ if (kind === "role-note") return "EvoDev Role Attention";
3354
+ if (kind === "verification-pattern") return "EvoDev Verification Pattern";
3355
+ if (kind === "skill-gap") return "EvoDev Warning";
3356
+ return "EvoDev Evolution Case";
3357
+ }
3358
+
3359
+ function knowledgeKindToDirectory(kind: string): string {
3360
+ if (kind === "rule") return "rules";
3361
+ if (kind === "workflow-hint") return "patterns";
3362
+ if (kind === "role-note") return "glossary";
3363
+ if (kind === "verification-pattern") return "verification";
3364
+ if (kind === "skill-gap") return "warnings";
3365
+ return "evos";
3366
+ }
3367
+
3368
+ async function ensureOverlayConcept(input: {
3369
+ okfDir: string;
3370
+ overlayPath: string;
3371
+ candidate: OkfKnowledgePlanCandidate;
3372
+ projectKey: string;
3373
+ runId: string;
3374
+ link: string;
3375
+ }): Promise<void> {
3376
+ const relativePath = toOkfRelativePath(input.okfDir, input.overlayPath);
3377
+ const segments = relativePath.split("/");
3378
+ const root = segments[0] ?? "repos";
3379
+ const scopeId = segments[1] ?? input.projectKey;
3380
+ const type =
3381
+ root === "roles"
3382
+ ? "EvoDev Role Attention"
3383
+ : root === "workflows"
3384
+ ? "EvoDev Workflow Attention"
3385
+ : "EvoDev Repo Attention";
3386
+ await ensureOkfDirectory(
3387
+ input.okfDir,
3388
+ dirname(relativePath),
3389
+ scopeId,
3390
+ `Attention overlay for ${scopeId}.`,
3391
+ );
3392
+ const exists = await pathExists(input.overlayPath);
3393
+ const linkLine = `- [${input.candidate.title}](${input.link}) - ${input.candidate.description}`;
3394
+ if (exists) {
3395
+ const current = await readFile(input.overlayPath, "utf8");
3396
+ if (current.includes(input.link)) return;
3397
+ await writeFile(input.overlayPath, `${current.trimEnd()}\n${linkLine}\n`, "utf8");
3398
+ return;
3399
+ }
3400
+ await writeFile(
3401
+ input.overlayPath,
3402
+ [
3403
+ "---",
3404
+ `type: ${yamlString(type)}`,
3405
+ `title: ${yamlString(`${scopeId} attention`)}`,
3406
+ `description: ${yamlString(`Attention overlay for ${scopeId}.`)}`,
3407
+ `resource: ${yamlString(`evodev://${relativePath.replace(/\.md$/, "")}`)}`,
3408
+ "tags:",
3409
+ ` - ${yamlString("evodev")}`,
3410
+ ` - ${yamlString(`${root.slice(0, -1)}:${scopeId}`)}`,
3411
+ `timestamp: ${yamlString(new Date().toISOString())}`,
3412
+ "evodev:",
3413
+ ` schema: ${yamlString("knowledge/v1")}`,
3414
+ ` stableKey: ${yamlString(`overlay:${relativePath.replace(/\.md$/, "")}`)}`,
3415
+ ` reviewState: ${yamlString(resolveCandidateReviewStateForWrite(input.candidate))}`,
3416
+ renderLifecycleYaml(
3417
+ " ",
3418
+ createDefaultOkfLifecycle({
3419
+ type,
3420
+ path: relativePath,
3421
+ tags: ["evodev", `${root.slice(0, -1)}:${scopeId}`],
3422
+ title: `${scopeId} attention`,
3423
+ reviewState: resolveCandidateReviewStateForWrite(input.candidate),
3424
+ createdAt: new Date().toISOString(),
3425
+ }),
3426
+ ),
3427
+ " source:",
3428
+ ` kind: ${yamlString("okf-organizer")}`,
3429
+ ` projectKey: ${yamlString(input.projectKey)}`,
3430
+ " rawContentStored: false",
3431
+ " scope:",
3432
+ " repoTags:",
3433
+ ` - ${yamlString(input.projectKey)}`,
3434
+ " roleTags: []",
3435
+ " workflowTags: []",
3436
+ " pathScopes: []",
3437
+ renderPrivacyYaml(" "),
3438
+ " organizer:",
3439
+ ` lastAction: ${yamlString(input.candidate.decision)}`,
3440
+ ` lastRunId: ${yamlString(input.runId)}`,
3441
+ " duplicateOf: null",
3442
+ " supersedes: []",
3443
+ "---",
3444
+ "",
3445
+ "# Attention",
3446
+ "",
3447
+ "This overlay links scope-specific attention to canonical EvoDev knowledge.",
3448
+ "",
3449
+ "# Linked Core Knowledge",
3450
+ "",
3451
+ linkLine,
3452
+ "",
3453
+ ].join("\n"),
3454
+ "utf8",
3455
+ );
3456
+ }
3457
+
3458
+ function renderOkfConcept(candidate: OkfKnowledgePlanCandidate, plan: OkfKnowledgePlan): string {
3459
+ const scores = resolveCandidateScoresForWrite(candidate);
3460
+ return [
3461
+ "---",
3462
+ `type: ${yamlString(candidate.okfType)}`,
3463
+ `title: ${yamlString(candidate.title)}`,
3464
+ `description: ${yamlString(candidate.description)}`,
3465
+ `resource: ${yamlString(`evodev://${candidate.targetPath.replace(/\.md$/, "")}`)}`,
3466
+ "tags:",
3467
+ ...[
3468
+ "evodev",
3469
+ candidate.kind,
3470
+ ...candidate.roleTags.map((tag) => `role:${tag}`),
3471
+ ...candidate.repoTags.map((tag) => `repo:${tag}`),
3472
+ ...candidate.workflowTags.map((tag) => `workflow:${tag}`),
3473
+ ].map((tag) => ` - ${yamlString(tag)}`),
3474
+ `timestamp: ${yamlString(plan.createdAt)}`,
3475
+ "evodev:",
3476
+ ` schema: ${yamlString("knowledge/v1")}`,
3477
+ ` 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
+ ),
3496
+ " source:",
3497
+ ` kind: ${yamlString("trace-distillation")}`,
3498
+ ` projectKey: ${yamlString(plan.projectKey)}`,
3499
+ ` runId: ${yamlString(plan.runId)}`,
3500
+ ` evidenceWindowId: ${yamlString(plan.evidenceWindowId)}`,
3501
+ " rawContentStored: false",
3502
+ " scope:",
3503
+ renderYamlList(" repoTags", candidate.repoTags),
3504
+ renderYamlList(" roleTags", candidate.roleTags),
3505
+ renderYamlList(" workflowTags", candidate.workflowTags),
3506
+ renderYamlList(" pathScopes", candidate.pathScopes),
3507
+ renderPrivacyYaml(" "),
3508
+ " organizer:",
3509
+ ` lastAction: ${yamlString(candidate.decision)}`,
3510
+ ` lastRunId: ${yamlString(plan.runId)}`,
3511
+ " duplicateOf: null",
3512
+ " supersedes: []",
3513
+ " scores:",
3514
+ ` evidenceStrength: ${scores.evidenceStrength}`,
3515
+ ` reuseValue: ${scores.reuseValue}`,
3516
+ ` actionability: ${scores.actionability}`,
3517
+ ` stability: ${scores.stability}`,
3518
+ ` privacyRisk: ${scores.privacyRisk}`,
3519
+ ` duplicationRisk: ${scores.duplicationRisk}`,
3520
+ "---",
3521
+ "",
3522
+ "# Summary",
3523
+ "",
3524
+ candidate.bodySections.summary,
3525
+ "",
3526
+ "# Applies When",
3527
+ "",
3528
+ renderMarkdownList(candidate.bodySections.appliesWhen),
3529
+ "",
3530
+ "# Guidance",
3531
+ "",
3532
+ renderMarkdownList(candidate.bodySections.guidance),
3533
+ "",
3534
+ "# Anti-Criteria",
3535
+ "",
3536
+ renderMarkdownList(candidate.bodySections.antiCriteria),
3537
+ "",
3538
+ "# Verification",
3539
+ "",
3540
+ renderMarkdownList(
3541
+ candidate.bodySections.verification.length === 0
3542
+ ? ["No verification command is stored in this concept."]
3543
+ : candidate.bodySections.verification,
3544
+ ),
3545
+ "",
3546
+ "# Related Concepts",
3547
+ "",
3548
+ renderMarkdownList(
3549
+ candidate.relatedConceptLinks.length === 0
3550
+ ? ["No related concepts yet."]
3551
+ : candidate.relatedConceptLinks,
3552
+ ),
3553
+ "",
3554
+ "# Citations",
3555
+ "",
3556
+ renderMarkdownList(
3557
+ candidate.bodySections.citations.length === 0
3558
+ ? ["No external citations stored."]
3559
+ : candidate.bodySections.citations,
3560
+ ),
3561
+ "",
3562
+ ].join("\n");
3563
+ }
3564
+
3565
+ async function ensureOkfDirectory(
3566
+ okfDir: string,
3567
+ relativeDir: string,
3568
+ title: string,
3569
+ description: string,
3570
+ ): Promise<void> {
3571
+ const dir =
3572
+ relativeDir === "" || relativeDir === "." ? okfDir : resolveOkfTargetPath(okfDir, relativeDir);
3573
+ await mkdir(dir, { recursive: true });
3574
+ const isRoot = dir === okfDir;
3575
+ const indexPath = join(dir, "index.md");
3576
+ if (!(await pathExists(indexPath))) {
3577
+ await writeFile(
3578
+ indexPath,
3579
+ isRoot
3580
+ ? [
3581
+ "---",
3582
+ `okf_version: ${yamlString("0.1")}`,
3583
+ `title: ${yamlString(title)}`,
3584
+ `description: ${yamlString(description)}`,
3585
+ "---",
3586
+ "",
3587
+ `# ${title}`,
3588
+ "",
3589
+ description,
3590
+ "",
3591
+ ].join("\n")
3592
+ : [`# ${title}`, "", description, ""].join("\n"),
3593
+ "utf8",
3594
+ );
3595
+ }
3596
+ const logPath = join(dir, "log.md");
3597
+ if (!(await pathExists(logPath))) {
3598
+ await writeFile(
3599
+ logPath,
3600
+ [
3601
+ "# Directory Update Log",
3602
+ "",
3603
+ `## ${todayIsoDate()}`,
3604
+ "* **Initialization**: Created OKF directory.",
3605
+ "",
3606
+ ].join("\n"),
3607
+ "utf8",
3608
+ );
3609
+ }
3610
+ }
3611
+
3612
+ async function regenerateOkfDirectoryIndexes(okfDir: string): Promise<string[]> {
3613
+ const directories = await listDirectories(okfDir);
3614
+ const paths: string[] = [];
3615
+ for (const directory of directories) {
3616
+ const rel = toOkfRelativePath(okfDir, directory);
3617
+ const isRoot = directory === okfDir;
3618
+ const entries = await readdir(directory, { withFileTypes: true });
3619
+ const conceptLines: string[] = [];
3620
+ const directoryLines: string[] = [];
3621
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3622
+ if (entry.isDirectory()) {
3623
+ directoryLines.push(`* [${entry.name}](./${entry.name}/) - ${titleFromSlug(entry.name)}.`);
3624
+ continue;
3625
+ }
3626
+ if (
3627
+ !entry.isFile() ||
3628
+ !entry.name.endsWith(".md") ||
3629
+ RESERVED_OKF_FILENAMES.has(entry.name)
3630
+ ) {
3631
+ continue;
3632
+ }
3633
+ const filePath = join(directory, entry.name);
3634
+ const concept = parseOkfConceptFile(okfDir, filePath, await readFile(filePath, "utf8"));
3635
+ if (concept !== null && !isActiveOkfConcept(concept)) continue;
3636
+ const title = concept?.title ?? titleFromSlug(entry.name.replace(/\.md$/, ""));
3637
+ const description = concept?.description ?? "";
3638
+ conceptLines.push(`* [${title}](./${entry.name}) - ${description}`);
3639
+ }
3640
+ const content = [
3641
+ ...(isRoot
3642
+ ? [
3643
+ "---",
3644
+ `okf_version: ${yamlString("0.1")}`,
3645
+ `title: ${yamlString("EvoDev User Knowledge")}`,
3646
+ `description: ${yamlString("User-local active engineering knowledge for EvoDev.")}`,
3647
+ "---",
3648
+ "",
3649
+ ]
3650
+ : []),
3651
+ `# ${isRoot ? "EvoDev Knowledge" : titleFromSlug(rel.split("/").at(-1) ?? "Index")}`,
3652
+ "",
3653
+ ...(directoryLines.length === 0 ? [] : ["# Subdirectories", "", ...directoryLines, ""]),
3654
+ ...(conceptLines.length === 0 ? [] : ["# Concepts", "", ...conceptLines, ""]),
3655
+ ].join("\n");
3656
+ const indexPath = join(directory, "index.md");
3657
+ await writeFile(indexPath, content, "utf8");
3658
+ paths.push(indexPath);
3659
+ }
3660
+ return paths;
3661
+ }
3662
+
3663
+ async function appendOrganizerLogs(input: {
3664
+ okfDir: string;
3665
+ affectedDirectories: string[];
3666
+ plan: OkfKnowledgePlan;
3667
+ conceptPaths: string[];
3668
+ overlayPaths: string[];
3669
+ skippedCandidates: string[];
3670
+ needsHumanCandidates: string[];
3671
+ }): Promise<string[]> {
3672
+ const summary = `**Update**: Distilled run \`${input.plan.runId}\` for repo \`${input.plan.projectKey}\`; created/updated ${input.conceptPaths.length} concept(s), updated ${input.overlayPaths.length} overlay(s), skipped ${input.skippedCandidates.length}, needs-human ${input.needsHumanCandidates.length}.`;
3673
+ const paths: string[] = [];
3674
+ const uniqueDirectories = [
3675
+ ...new Set(
3676
+ input.affectedDirectories.flatMap((directory) =>
3677
+ ancestorDirectories(input.okfDir, directory),
3678
+ ),
3679
+ ),
3680
+ ];
3681
+ for (const directory of uniqueDirectories) {
3682
+ const logPath = join(directory, "log.md");
3683
+ await prependLogEntry(logPath, summary);
3684
+ paths.push(logPath);
3685
+ }
3686
+ return paths;
3687
+ }
3688
+
3689
+ function ancestorDirectories(root: string, directory: string): string[] {
3690
+ const directories = [directory];
3691
+ let current = directory;
3692
+ while (current !== root && current.startsWith(root)) {
3693
+ current = dirname(current);
3694
+ directories.push(current);
3695
+ }
3696
+ return directories;
3697
+ }
3698
+
3699
+ async function appendOkfLog(okfDir: string, entry: string): Promise<void> {
3700
+ await prependLogEntry(join(okfDir, "log.md"), entry);
3701
+ }
3702
+
3703
+ async function prependLogEntry(logPath: string, entry: string): Promise<void> {
3704
+ await mkdir(dirname(logPath), { recursive: true });
3705
+ const date = todayIsoDate();
3706
+ const existing = (await pathExists(logPath))
3707
+ ? await readFile(logPath, "utf8")
3708
+ : "# Directory Update Log\n";
3709
+ const line = `* ${entry}`;
3710
+ if (existing.includes(`## ${date}`)) {
3711
+ await writeFile(logPath, existing.replace(`## ${date}\n`, `## ${date}\n${line}\n`), "utf8");
3712
+ return;
3713
+ }
3714
+ const withoutTitle = existing.replace(/^# Directory Update Log\s*/u, "").trimStart();
3715
+ await writeFile(
3716
+ logPath,
3717
+ `${["# Directory Update Log", "", `## ${date}`, line, "", withoutTitle].join("\n").trimEnd()}\n`,
3718
+ "utf8",
3719
+ );
3720
+ }
3721
+
3722
+ function parseOkfConceptFile(
3723
+ okfDir: string,
3724
+ filePath: string,
3725
+ content: string,
3726
+ ): OkfKnowledgeConcept | null {
3727
+ const parsed = extractFrontmatter(content);
3728
+ if (parsed === null) return null;
3729
+ const rel = toOkfRelativePath(okfDir, filePath);
3730
+ const id = rel.replace(/\.md$/, "");
3731
+ const frontmatter = parsed.frontmatter;
3732
+ const stableKey =
3733
+ readIndentedYamlScalar(frontmatter, "stableKey") ?? `legacy:${rel.replace(/\.md$/, "")}`;
3734
+ const reviewState = parseOkfReviewState(
3735
+ readIndentedYamlScalar(frontmatter, "reviewState") ?? "auto-stored/unreviewed",
3736
+ );
3737
+ const type = readYamlScalar(frontmatter, "type") ?? "Unknown";
3738
+ const title = readYamlScalar(frontmatter, "title") ?? titleFromSlug(id.split("/").at(-1) ?? id);
3739
+ const description = readYamlScalar(frontmatter, "description") ?? "";
3740
+ const tags = readYamlList(frontmatter, "tags");
3741
+ const repoTags = readYamlList(frontmatter, "repoTags");
3742
+ const roleTags = readYamlList(frontmatter, "roleTags");
3743
+ const workflowTags = readYamlList(frontmatter, "workflowTags");
3744
+ const pathScopes = readYamlList(frontmatter, "pathScopes");
3745
+ const lifecycleParsed = parseOkfLifecycle(frontmatter, {
3746
+ type,
3747
+ path: rel,
3748
+ tags,
3749
+ title,
3750
+ reviewState,
3751
+ createdAt: readYamlScalar(frontmatter, "timestamp") ?? undefined,
3752
+ });
3753
+ return {
3754
+ id,
3755
+ path: filePath,
3756
+ sourceLink: `/${rel}`,
3757
+ type,
3758
+ stableKey,
3759
+ reviewState,
3760
+ lifecycle: lifecycleParsed.lifecycle,
3761
+ lifecyclePersisted: lifecycleParsed.persisted,
3762
+ title,
3763
+ description,
3764
+ tags,
3765
+ repoTags,
3766
+ roleTags,
3767
+ workflowTags,
3768
+ pathScopes,
3769
+ body: parsed.body,
3770
+ };
3771
+ }
3772
+
3773
+ function matchesConceptFilters(
3774
+ concept: OkfKnowledgeConcept,
3775
+ input: { projectKey?: string; roleId?: string; workflowId?: string; paths?: string[] },
3776
+ ): boolean {
3777
+ if (
3778
+ input.projectKey !== undefined &&
3779
+ concept.repoTags.length > 0 &&
3780
+ !concept.repoTags.includes(sanitizeSlug(input.projectKey)) &&
3781
+ !concept.tags.includes(`repo:${sanitizeSlug(input.projectKey)}`)
3782
+ ) {
3783
+ return false;
3784
+ }
3785
+ if (
3786
+ input.roleId !== undefined &&
3787
+ concept.roleTags.length > 0 &&
3788
+ !concept.roleTags.includes(sanitizeSlug(input.roleId)) &&
3789
+ !concept.tags.includes(`role:${sanitizeSlug(input.roleId)}`)
3790
+ ) {
3791
+ return false;
3792
+ }
3793
+ if (
3794
+ input.workflowId !== undefined &&
3795
+ concept.workflowTags.length > 0 &&
3796
+ !concept.workflowTags.includes(sanitizeSlug(input.workflowId)) &&
3797
+ !concept.tags.includes(`workflow:${sanitizeSlug(input.workflowId)}`)
3798
+ ) {
3799
+ return false;
3800
+ }
3801
+ if (input.paths !== undefined && input.paths.length > 0 && concept.pathScopes.length > 0) {
3802
+ return input.paths.some((path) =>
3803
+ concept.pathScopes.some((scope) => path.startsWith(scope) || scope.startsWith(path)),
3804
+ );
3805
+ }
3806
+ return true;
3807
+ }
3808
+
3809
+ function normalizeKnowledgeQueryScope(input: {
3810
+ projectKey?: string;
3811
+ roleId?: string;
3812
+ workflowId?: string;
3813
+ paths?: string[];
3814
+ }): KnowledgeQueryScope {
3815
+ return {
3816
+ projectKey: input.projectKey === undefined ? undefined : sanitizeSlug(input.projectKey),
3817
+ roleId: input.roleId === undefined ? undefined : sanitizeSlug(input.roleId),
3818
+ workflowId: input.workflowId === undefined ? undefined : sanitizeSlug(input.workflowId),
3819
+ paths: (input.paths ?? []).flatMap((path) => {
3820
+ const normalized = normalizeRepoRelativeScopePath(path);
3821
+ return normalized === null ? [] : [normalized];
3822
+ }),
3823
+ hasPathFilter: (input.paths ?? []).length > 0,
3824
+ };
3825
+ }
3826
+
3827
+ function normalizeKnowledgeQueryText(value: string | undefined): string | undefined {
3828
+ if (value === undefined) return undefined;
3829
+ const normalized = sanitizeOkfText(value);
3830
+ return normalized === "" ? undefined : normalized;
3831
+ }
3832
+
3833
+ function createKnowledgeContextRevision(items: ScopedKnowledgeContextPackItem[]): string {
3834
+ return `okf-rev-${sha256Short(
3835
+ stableJsonStringify(
3836
+ items.map((item) => ({
3837
+ id: item.id,
3838
+ sourceType: item.sourceType,
3839
+ sourceLink: item.sourceLink,
3840
+ section: item.section,
3841
+ title: item.title,
3842
+ matchReasons: item.matchReasons,
3843
+ })),
3844
+ ),
3845
+ )}`;
3846
+ }
3847
+
3848
+ function sha256Short(value: string): string {
3849
+ return createHash("sha256").update(value).digest("hex").slice(0, 16);
3850
+ }
3851
+
3852
+ function stableJsonStringify(value: unknown): string {
3853
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
3854
+ if (Array.isArray(value)) return `[${value.map(stableJsonStringify).join(",")}]`;
3855
+ const entries = Object.entries(value as Record<string, unknown>)
3856
+ .filter(([, child]) => child !== undefined)
3857
+ .sort(([left], [right]) => left.localeCompare(right));
3858
+ return `{${entries
3859
+ .map(([key, child]) => `${JSON.stringify(key)}:${stableJsonStringify(child)}`)
3860
+ .join(",")}}`;
3861
+ }
3862
+
3863
+ function sanitizeReceiptPathSegment(value: string): string {
3864
+ const segment = value
3865
+ .trim()
3866
+ .toLowerCase()
3867
+ .replace(/[^a-z0-9._-]+/gu, "-")
3868
+ .replace(/-+/gu, "-")
3869
+ .replace(/^-+|-+$/gu, "")
3870
+ .slice(0, 160);
3871
+ return segment === "" ? "unknown" : segment;
3872
+ }
3873
+
3874
+ function createLexicalKnowledgeDocument(
3875
+ input: Omit<LexicalKnowledgeDocument, "safeTokens">,
3876
+ ): LexicalKnowledgeDocument {
3877
+ const safeInput = {
3878
+ ...input,
3879
+ title: sanitizeOkfText(input.title),
3880
+ description: sanitizeOkfText(input.description),
3881
+ tags: input.tags.map(sanitizeOkfText),
3882
+ repoTags: input.repoTags.map(sanitizeOkfText),
3883
+ roleTags: input.roleTags.map(sanitizeOkfText),
3884
+ workflowTags: input.workflowTags.map(sanitizeOkfText),
3885
+ pathScopes: input.pathScopes.map(sanitizeOkfText),
3886
+ stableKey: sanitizeOkfText(input.stableKey),
3887
+ headings: input.headings.map(sanitizeOkfText),
3888
+ bodySummary: sanitizeOkfText(input.bodySummary),
3889
+ };
3890
+ return {
3891
+ ...safeInput,
3892
+ safeTokens: uniqueStrings(
3893
+ [
3894
+ safeInput.id,
3895
+ safeInput.sourceLink,
3896
+ safeInput.stableKey,
3897
+ safeInput.title,
3898
+ safeInput.description,
3899
+ ...safeInput.tags,
3900
+ ...safeInput.repoTags,
3901
+ ...safeInput.roleTags,
3902
+ ...safeInput.workflowTags,
3903
+ ...safeInput.pathScopes,
3904
+ ...safeInput.headings,
3905
+ safeInput.bodySummary,
3906
+ ].flatMap(tokenizeLexicalText),
3907
+ ),
3908
+ };
3909
+ }
3910
+
3911
+ function parseLexicalQuery(queryText: string): {
3912
+ tokens: string[];
3913
+ exactAnchors: string[];
3914
+ pathAnchors: string[];
3915
+ scriptAnchors: string[];
3916
+ } {
3917
+ return {
3918
+ tokens: uniqueStrings(tokenizeLexicalText(queryText)),
3919
+ exactAnchors: uniqueStrings(extractExactAnchors(queryText)),
3920
+ pathAnchors: uniqueStrings(extractPathAnchors(queryText)),
3921
+ scriptAnchors: uniqueStrings(extractScriptAnchors(queryText)),
3922
+ };
3923
+ }
3924
+
3925
+ function tokenizeLexicalText(value: string): string[] {
3926
+ const normalized = normalizeLexicalComparable(value)
3927
+ .replace(/([a-z]+)(\d{3,6})/gu, "$1$2 $1 $2")
3928
+ .replace(/(\d)([a-z])/gu, "$1 $2");
3929
+ return uniqueStrings([
3930
+ ...normalized.split(/[^a-z0-9]+/u).filter((token) => token !== ""),
3931
+ ...extractExactAnchors(value),
3932
+ ...extractScriptAnchors(value),
3933
+ ...extractPathAnchors(value).flatMap((path) => [
3934
+ path,
3935
+ ...path.split(/[/.]+/u).filter((token) => token !== ""),
3936
+ ]),
3937
+ ]);
3938
+ }
3939
+
3940
+ function normalizeLexicalComparable(value: string): string {
3941
+ return sanitizeOkfText(value).toLowerCase().replace(/\\/gu, "/");
3942
+ }
3943
+
3944
+ function extractExactAnchors(value: string): string[] {
3945
+ return uniqueStrings(
3946
+ [...value.matchAll(/\b[a-z]{1,8}\d{3,6}\b/giu)].map((match) => match[0].toLowerCase()),
3947
+ );
3948
+ }
3949
+
3950
+ function extractScriptAnchors(value: string): string[] {
3951
+ return uniqueStrings(
3952
+ [...value.matchAll(/\b(bun|npm|pnpm|yarn)\s+run\s+([a-z0-9:_./-]+)/giu)].map(
3953
+ (match) => `${match[1]?.toLowerCase()}-run-${sanitizeSlug(match[2] ?? "")}`,
3954
+ ),
3955
+ );
3956
+ }
3957
+
3958
+ function extractPathAnchors(value: string): string[] {
3959
+ const anchors = [
3960
+ ...value.matchAll(
3961
+ /(?:\.{0,2}\/)?[a-z0-9_.-]+(?:\/[a-z0-9_.-]+)+(?:\.[a-z0-9]+)?|[a-z0-9_.-]+\.(?:ts|tsx|js|jsx|json|md|yml|yaml|toml|rs|py|go|java|kt|swift|css|scss|html)/giu,
3962
+ ),
3963
+ ].map((match) => normalizeQueryPathAnchor(match[0]));
3964
+ return uniqueStrings(anchors.filter((anchor) => anchor !== ""));
3965
+ }
3966
+
3967
+ function normalizeQueryPathAnchor(value: string): string {
3968
+ return value
3969
+ .trim()
3970
+ .toLowerCase()
3971
+ .replace(/\\/gu, "/")
3972
+ .replace(/^\.?\//u, "")
3973
+ .replace(/\/+/gu, "/")
3974
+ .replace(/\/$/u, "");
3975
+ }
3976
+
3977
+ function pathAnchorMatches(anchor: string, values: string[]): boolean {
3978
+ const normalizedAnchor = normalizeQueryPathAnchor(anchor);
3979
+ if (normalizedAnchor === "") return false;
3980
+ const anchorBase = normalizedAnchor.split("/").at(-1) ?? normalizedAnchor;
3981
+ return values.some((value) => {
3982
+ const normalizedValue = normalizeQueryPathAnchor(value.replace(/^evodev:\/\//u, ""));
3983
+ if (normalizedValue === "") return false;
3984
+ const valueBase = normalizedValue.split("/").at(-1) ?? normalizedValue;
3985
+ return (
3986
+ normalizedValue === normalizedAnchor ||
3987
+ normalizedValue.includes(normalizedAnchor) ||
3988
+ normalizedAnchor.includes(normalizedValue) ||
3989
+ segmentAwarePathOverlap(normalizedAnchor, normalizedValue) ||
3990
+ (anchorBase.includes(".") && anchorBase === valueBase)
3991
+ );
3992
+ });
3993
+ }
3994
+
3995
+ function extractMarkdownHeadings(body: string): string[] {
3996
+ return body
3997
+ .split("\n")
3998
+ .flatMap((line) => {
3999
+ const match = line.match(/^#{1,6}\s+(.+?)\s*$/u);
4000
+ return match?.[1] === undefined ? [] : [match[1]];
4001
+ })
4002
+ .slice(0, 20);
4003
+ }
4004
+
4005
+ function summarizeLexicalBody(body: string): string {
4006
+ return body
4007
+ .replace(/^#{1,6}\s+/gmu, "")
4008
+ .replace(/\s+/gu, " ")
4009
+ .trim()
4010
+ .slice(0, 1200);
4011
+ }
4012
+
4013
+ 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")
4018
+ .replace(/[^a-z0-9:._/-]+/gu, "-")
4019
+ .replace(/-+/gu, "-")
4020
+ .replace(/^-+|-+$/gu, "")
4021
+ .slice(0, 120);
4022
+ }
4023
+
4024
+ function uniqueStrings(values: string[]): string[] {
4025
+ return [...new Set(values.filter((value) => value !== ""))];
4026
+ }
4027
+
4028
+ function matchOkfConceptForContext(
4029
+ concept: OkfKnowledgeConcept,
4030
+ scope: KnowledgeQueryScope,
4031
+ ): OkfContextMatch | null {
4032
+ const reasons: string[] = [];
4033
+ let exactMatches = 0;
4034
+ let overlayMatch = false;
4035
+ let pathMatch = false;
4036
+
4037
+ const repoTags = collectScopedTags(concept, "repo");
4038
+ if (scope.projectKey !== undefined) {
4039
+ if (repoTags.length > 0 && !repoTags.includes(scope.projectKey)) return null;
4040
+ if (repoTags.includes(scope.projectKey)) {
4041
+ reasons.push(`repo:${scope.projectKey}`);
4042
+ exactMatches += 1;
4043
+ }
4044
+ }
4045
+
4046
+ const roleTags = collectScopedTags(concept, "role");
4047
+ if (scope.roleId !== undefined) {
4048
+ if (roleTags.length > 0 && !roleTags.includes(scope.roleId)) return null;
4049
+ if (roleTags.includes(scope.roleId)) {
4050
+ reasons.push(`role:${scope.roleId}`);
4051
+ exactMatches += 1;
4052
+ }
4053
+ }
4054
+
4055
+ const workflowTags = collectScopedTags(concept, "workflow");
4056
+ if (scope.workflowId !== undefined) {
4057
+ if (workflowTags.length > 0 && !workflowTags.includes(scope.workflowId)) return null;
4058
+ if (workflowTags.includes(scope.workflowId)) {
4059
+ reasons.push(`workflow:${scope.workflowId}`);
4060
+ exactMatches += 1;
4061
+ }
4062
+ }
4063
+
4064
+ const overlay = matchOverlayScope(concept.id, scope);
4065
+ if (overlay === null) return null;
4066
+ if (overlay !== "none") {
4067
+ overlayMatch = true;
4068
+ reasons.push(`overlay:${overlay}`);
4069
+ exactMatches += 1;
4070
+ }
4071
+
4072
+ const pathScopes = concept.pathScopes.flatMap((path) => {
4073
+ const normalized = normalizeRepoRelativeScopePath(path);
4074
+ return normalized === null ? [] : [normalized];
4075
+ });
4076
+ if (scope.hasPathFilter && pathScopes.length > 0) {
4077
+ if (scope.paths.length === 0) return null;
4078
+ pathMatch = scope.paths.some((path) =>
4079
+ pathScopes.some((pathScope) => segmentAwarePathOverlap(path, pathScope)),
4080
+ );
4081
+ if (!pathMatch) return null;
4082
+ reasons.push("path-scope");
4083
+ exactMatches += 1;
4084
+ }
4085
+
4086
+ return { reasons, exactMatches, overlayMatch, pathMatch };
4087
+ }
4088
+
4089
+ function collectScopedTags(
4090
+ concept: OkfKnowledgeConcept,
4091
+ kind: "repo" | "role" | "workflow",
4092
+ ): string[] {
4093
+ const direct =
4094
+ kind === "repo" ? concept.repoTags : kind === "role" ? concept.roleTags : concept.workflowTags;
4095
+ const prefix = `${kind}:`;
4096
+ return [
4097
+ ...new Set([
4098
+ ...direct.map(sanitizeSlug),
4099
+ ...concept.tags
4100
+ .filter((tag) => tag.startsWith(prefix))
4101
+ .map((tag) => sanitizeSlug(tag.slice(prefix.length))),
4102
+ ]),
4103
+ ].filter((tag) => tag !== "unknown");
4104
+ }
4105
+
4106
+ function matchOverlayScope(
4107
+ conceptId: string,
4108
+ scope: KnowledgeQueryScope,
4109
+ ): "repo" | "role" | "workflow" | "none" | null {
4110
+ const [root, id] = conceptId.split("/");
4111
+ if (root === "repos") {
4112
+ if (scope.projectKey !== undefined && id !== scope.projectKey) return null;
4113
+ return scope.projectKey === undefined ? "none" : "repo";
4114
+ }
4115
+ if (root === "roles") {
4116
+ if (scope.roleId !== undefined && id !== scope.roleId) return null;
4117
+ return scope.roleId === undefined ? "none" : "role";
4118
+ }
4119
+ if (root === "workflows") {
4120
+ if (scope.workflowId !== undefined && id !== scope.workflowId) return null;
4121
+ return scope.workflowId === undefined ? "none" : "workflow";
4122
+ }
4123
+ return "none";
4124
+ }
4125
+
4126
+ function calculateOkfContextScore(concept: OkfKnowledgeConcept, match: OkfContextMatch): number {
4127
+ let score = 0;
4128
+ score += match.exactMatches * 100;
4129
+ if (match.overlayMatch) score += 80;
4130
+ if (match.pathMatch) score += 40;
4131
+ if (concept.id.startsWith("concepts/")) score += 1;
4132
+ return score;
4133
+ }
4134
+
4135
+ function resolveContextSection(concept: OkfKnowledgeConcept): OkfKnowledgeContextSection {
4136
+ if (concept.id.startsWith("roles/")) return "role-attention";
4137
+ if (concept.id.startsWith("repos/")) return "repo-attention";
4138
+ if (concept.id.startsWith("workflows/")) return "workflow-attention";
4139
+ if (concept.id.startsWith("concepts/evos/")) return "accepted-evos-cases";
4140
+ if (concept.id.startsWith("concepts/verification/")) return "verification";
4141
+ if (/verification/i.test(concept.type)) return "verification";
4142
+ return "applicable-knowledge";
4143
+ }
4144
+
4145
+ function contextSectionSortWeight(section: OkfKnowledgeContextSection): number {
4146
+ if (section === "role-attention") return 0;
4147
+ if (section === "repo-attention") return 1;
4148
+ if (section === "workflow-attention") return 2;
4149
+ if (section === "verification") return 3;
4150
+ if (section === "applicable-knowledge") return 4;
4151
+ return 5;
4152
+ }
4153
+
4154
+ function normalizeRepoRelativeScopePath(value: string): string | null {
4155
+ const trimmed = value.trim().replace(/\\/gu, "/");
4156
+ if (trimmed === "" || trimmed.startsWith("/") || /^[A-Za-z]:\//u.test(trimmed)) return null;
4157
+ const normalized = trimmed.replace(/\/+/gu, "/").replace(/^\.\//u, "").replace(/\/$/u, "");
4158
+ const segments = normalized.split("/");
4159
+ if (
4160
+ segments.length === 0 ||
4161
+ segments.some((segment) => segment === "" || segment === "." || segment === "..")
4162
+ ) {
4163
+ return null;
4164
+ }
4165
+ return segments.join("/");
4166
+ }
4167
+
4168
+ function segmentAwarePathOverlap(left: string, right: string): boolean {
4169
+ return left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
4170
+ }
4171
+
4172
+ function isUnsafeOkfQueryContent(content: string): boolean {
4173
+ const parsed = extractFrontmatter(content);
4174
+ const frontmatter = parsed?.frontmatter ?? "";
4175
+ if (OKF_QUERY_PRIVACY_FLAG_KEYS.some((key) => readOkfPrivacyBoolean(frontmatter, key) === true)) {
4176
+ return true;
4177
+ }
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;
4182
+ }
4183
+
4184
+ function isActiveOkfConcept(concept: OkfKnowledgeConcept): boolean {
4185
+ return resolveOkfConceptQueryEligibility(concept, {
4186
+ includeStale: false,
4187
+ now: new Date(),
4188
+ }).include;
4189
+ }
4190
+
4191
+ function isActiveOkfReviewState(reviewState: OkfKnowledgeReviewState): boolean {
4192
+ return ACTIVE_OKF_REVIEW_STATES.includes(reviewState);
4193
+ }
4194
+
4195
+ function resolveOkfConceptQueryEligibility(
4196
+ concept: OkfKnowledgeConcept,
4197
+ input: { includeStale: boolean; now: Date },
4198
+ ): { include: boolean; stale: boolean; scoreAdjustment: number; reason: string } {
4199
+ if (
4200
+ concept.lifecycle.status === "deprecated" ||
4201
+ concept.lifecycle.status === "revoked" ||
4202
+ concept.lifecycle.status === "superseded" ||
4203
+ concept.reviewState === "deprecated" ||
4204
+ concept.reviewState === "revoked" ||
4205
+ concept.reviewState === "superseded"
4206
+ ) {
4207
+ return { include: false, stale: false, scoreAdjustment: 0, reason: "inactive" };
4208
+ }
4209
+ const staleByStatus = concept.lifecycle.status === "stale" || concept.reviewState === "stale";
4210
+ const staleByDate = isLifecycleDateDue(concept.lifecycle.staleAfter, input.now);
4211
+ const stale = staleByStatus || staleByDate;
4212
+ if (stale) {
4213
+ return {
4214
+ include: input.includeStale,
4215
+ stale: input.includeStale,
4216
+ scoreAdjustment: input.includeStale ? -250 : 0,
4217
+ reason: staleByStatus ? "status=stale" : `staleAfter=${concept.lifecycle.staleAfter}`,
4218
+ };
4219
+ }
4220
+ return {
4221
+ include: isActiveOkfReviewState(concept.reviewState) && concept.lifecycle.status === "active",
4222
+ stale: false,
4223
+ scoreAdjustment: 0,
4224
+ reason: "active",
4225
+ };
4226
+ }
4227
+
4228
+ function normalizeQueryNow(value: string | Date | undefined): Date {
4229
+ if (value instanceof Date && Number.isFinite(value.getTime())) return value;
4230
+ if (typeof value === "string") {
4231
+ const normalized = normalizeIsoDateString(value);
4232
+ if (normalized !== null) return new Date(normalized);
4233
+ }
4234
+ return new Date();
4235
+ }
4236
+
4237
+ function isLifecycleDateDue(value: string, now: Date): boolean {
4238
+ const normalized = normalizeIsoDateString(value);
4239
+ return normalized !== null && Date.parse(normalized) <= now.getTime();
4240
+ }
4241
+
4242
+ function readYamlBoolean(frontmatter: string, key: string): boolean | null {
4243
+ const scalar = readYamlScalar(frontmatter, key);
4244
+ if (scalar === null) return null;
4245
+ if (scalar === "true") return true;
4246
+ if (scalar === "false") return false;
4247
+ return null;
4248
+ }
4249
+
4250
+ function readOkfPrivacyBoolean(frontmatter: string, key: string): boolean | null {
4251
+ const root = readYamlBoolean(frontmatter, key);
4252
+ const privacyBlock = extractNestedYamlBlock(frontmatter, "evodev", "privacy");
4253
+ const nested = privacyBlock === null ? null : readIndentedYamlBoolean(privacyBlock, key);
4254
+ if (root === true || nested === true) return true;
4255
+ if (root === false || nested === false) return false;
4256
+ return null;
4257
+ }
4258
+
4259
+ function readIndentedYamlBoolean(frontmatter: string, key: string): boolean | null {
4260
+ const scalar = readIndentedYamlScalar(frontmatter, key);
4261
+ if (scalar === null) return null;
4262
+ if (scalar === "true") return true;
4263
+ if (scalar === "false") return false;
4264
+ return null;
4265
+ }
4266
+
4267
+ function validateOkfKnowledgePlan(plan: OkfKnowledgePlan): void {
4268
+ assertOkfKnowledgePlanContract(plan);
4269
+ }
4270
+
4271
+ function validateFailedPlanArtifact(artifact: OkfKnowledgeFailedPlanArtifact): void {
4272
+ if (!isRecord(artifact)) throw new Error("Failed plan artifact must be an object.");
4273
+ if (artifact.schemaVersion !== 1)
4274
+ throw new Error("Failed plan artifact schemaVersion must be 1.");
4275
+ if (artifact.kind !== "okf-knowledge-failed-plan")
4276
+ throw new Error("Failed plan artifact kind is invalid.");
4277
+ if (artifact.failureKind !== "validation" && artifact.failureKind !== "organizer") {
4278
+ throw new Error("Failed plan artifact failureKind is invalid.");
4279
+ }
4280
+ if (!isNonEmptyString(artifact.projectKey))
4281
+ throw new Error("Failed plan artifact projectKey is required.");
4282
+ if (!isNonEmptyString(artifact.runId)) throw new Error("Failed plan artifact runId is required.");
4283
+ if (!isNonEmptyString(artifact.createdAt))
4284
+ throw new Error("Failed plan artifact createdAt is required.");
4285
+ if (typeof artifact.resumable !== "boolean")
4286
+ throw new Error("Failed plan artifact resumable must be boolean.");
4287
+ if (!Array.isArray(artifact.findings))
4288
+ throw new Error("Failed plan artifact findings must be an array.");
4289
+ for (const finding of artifact.findings) {
4290
+ if (!isRecord(finding)) throw new Error("Failed plan finding must be an object.");
4291
+ if (
4292
+ !isNonEmptyString(finding.path) ||
4293
+ !isNonEmptyString(finding.code) ||
4294
+ !isNonEmptyString(finding.message)
4295
+ ) {
4296
+ throw new Error("Failed plan finding fields are required.");
4297
+ }
4298
+ if (finding.severity !== "error" && finding.severity !== "warning") {
4299
+ throw new Error("Failed plan finding severity is invalid.");
4300
+ }
4301
+ }
4302
+ if (!isNonEmptyString(artifact.error)) throw new Error("Failed plan artifact error is required.");
4303
+ if (artifact.plan !== undefined) validateOkfKnowledgePlan(artifact.plan);
4304
+ }
4305
+
4306
+ function assertCandidateScores(scores: OkfKnowledgeCandidateScores): void {
4307
+ for (const [key, value] of Object.entries(scores)) {
4308
+ if (!Number.isInteger(value) || value < 1 || value > 5) {
4309
+ throw new Error(`OKF candidate score ${key} must be an integer from 1 to 5.`);
4310
+ }
4311
+ }
4312
+ }
4313
+
4314
+ function assertOkfCandidateNoUnsafeContent(candidate: OkfKnowledgePlanCandidate): void {
4315
+ if (hasUnsafeCandidateContent(candidate)) {
4316
+ throw new Error("OKF candidate contains unsafe raw or sensitive content.");
4317
+ }
4318
+ }
4319
+
4320
+ function assertOkfPrivacy(privacy: OkfKnowledgePrivacyCheck): void {
4321
+ if (
4322
+ privacy.rawPromptsStored !== false ||
4323
+ privacy.rawLogsStored !== false ||
4324
+ privacy.sourceDumpsStored !== false ||
4325
+ privacy.rawCommandOutputStored !== false ||
4326
+ privacy.secretsStored !== false ||
4327
+ privacy.internalLinksStored !== false
4328
+ ) {
4329
+ throw new Error("OKF knowledge privacy fields must all be false.");
4330
+ }
4331
+ }
4332
+
4333
+ function createOkfPrivacyCheck(): OkfKnowledgePrivacyCheck {
4334
+ return {
4335
+ rawPromptsStored: false,
4336
+ rawLogsStored: false,
4337
+ sourceDumpsStored: false,
4338
+ rawCommandOutputStored: false,
4339
+ secretsStored: false,
4340
+ internalLinksStored: false,
4341
+ };
4342
+ }
4343
+
4344
+ function createDefaultOkfLifecycle(input: {
4345
+ type: string;
4346
+ path: string;
4347
+ tags: string[];
4348
+ title: string;
4349
+ reviewState: OkfKnowledgeReviewState;
4350
+ createdAt?: string;
4351
+ }): OkfKnowledgeLifecycle {
4352
+ const createdAt = normalizeIsoDateString(input.createdAt) ?? "1970-01-01T00:00:00.000Z";
4353
+ const policy = resolveOkfLifecyclePolicy(input);
4354
+ return {
4355
+ status: lifecycleStatusFromReviewState(input.reviewState),
4356
+ createdAt,
4357
+ lastVerifiedAt: createdAt,
4358
+ reviewAfter: addDaysIso(createdAt, policy.reviewAfterDays),
4359
+ staleAfter: addDaysIso(createdAt, policy.staleAfterDays),
4360
+ supersedes: [],
4361
+ supersededBy: null,
4362
+ revokedAt: null,
4363
+ revokedReason: null,
4364
+ };
4365
+ }
4366
+
4367
+ function parseOkfLifecycle(
4368
+ frontmatter: string,
4369
+ input: {
4370
+ type: string;
4371
+ path: string;
4372
+ tags: string[];
4373
+ title: string;
4374
+ reviewState: OkfKnowledgeReviewState;
4375
+ createdAt?: string;
4376
+ },
4377
+ ): { lifecycle: OkfKnowledgeLifecycle; persisted: boolean } {
4378
+ const defaults = createDefaultOkfLifecycle(input);
4379
+ const block = extractNestedYamlBlock(frontmatter, "evodev", "lifecycle");
4380
+ if (block === null) return { lifecycle: defaults, persisted: false };
4381
+ const status = readIndentedYamlScalar(block, "status");
4382
+ const createdAt = readIndentedYamlScalar(block, "createdAt");
4383
+ const lastVerifiedAt = readIndentedYamlScalar(block, "lastVerifiedAt");
4384
+ const reviewAfter = readIndentedYamlScalar(block, "reviewAfter");
4385
+ const staleAfter = readIndentedYamlScalar(block, "staleAfter");
4386
+ const revokedAt = readIndentedYamlScalar(block, "revokedAt");
4387
+ return {
4388
+ persisted: true,
4389
+ lifecycle: {
4390
+ status: isOkfLifecycleStatus(status) ? status : defaults.status,
4391
+ createdAt: normalizeIsoDateString(createdAt) ?? defaults.createdAt,
4392
+ lastVerifiedAt: normalizeIsoDateString(lastVerifiedAt) ?? defaults.lastVerifiedAt,
4393
+ reviewAfter: normalizeIsoDateString(reviewAfter) ?? defaults.reviewAfter,
4394
+ staleAfter: normalizeIsoDateString(staleAfter) ?? defaults.staleAfter,
4395
+ supersedes: readYamlList(block, "supersedes"),
4396
+ supersededBy: readNullableLifecycleString(readIndentedYamlScalar(block, "supersededBy")),
4397
+ revokedAt:
4398
+ readNullableLifecycleString(revokedAt) === null ? null : normalizeIsoDateString(revokedAt),
4399
+ revokedReason: readNullableLifecycleString(readIndentedYamlScalar(block, "revokedReason")),
4400
+ },
4401
+ };
4402
+ }
4403
+
4404
+ function lintOkfLifecycle(input: {
4405
+ concept: OkfKnowledgeConcept;
4406
+ frontmatter: string;
4407
+ knownConceptIds: Set<string>;
4408
+ now: Date;
4409
+ errors: string[];
4410
+ warnings: string[];
4411
+ }): void {
4412
+ const source = input.concept.sourceLink;
4413
+ const block = extractNestedYamlBlock(input.frontmatter, "evodev", "lifecycle");
4414
+ if (!input.concept.lifecyclePersisted || block === null) {
4415
+ input.warnings.push(`Lifecycle missing: ${source}`);
4416
+ } else {
4417
+ const status = readIndentedYamlScalar(block, "status");
4418
+ if (!isOkfLifecycleStatus(status)) {
4419
+ input.errors.push(
4420
+ `Lifecycle invalid status: ${source} status=${sanitizeOkfText(status ?? "missing")}`,
4421
+ );
4422
+ }
4423
+ for (const field of ["createdAt", "lastVerifiedAt", "reviewAfter", "staleAfter"] as const) {
4424
+ const value = readIndentedYamlScalar(block, field);
4425
+ if (value === null) {
4426
+ input.warnings.push(`Lifecycle missing date: ${source} ${field}`);
4427
+ } else if (normalizeIsoDateString(value) === null) {
4428
+ input.errors.push(`Lifecycle invalid date: ${source} ${field}`);
4429
+ }
4430
+ }
4431
+ }
4432
+
4433
+ if (isLifecycleDateDue(input.concept.lifecycle.reviewAfter, input.now)) {
4434
+ input.warnings.push(
4435
+ `Lifecycle review due: ${source} reviewAfter=${input.concept.lifecycle.reviewAfter}`,
4436
+ );
4437
+ }
4438
+ if (isLifecycleDateDue(input.concept.lifecycle.staleAfter, input.now)) {
4439
+ input.warnings.push(
4440
+ `Lifecycle stale due: ${source} staleAfter=${input.concept.lifecycle.staleAfter}`,
4441
+ );
4442
+ }
4443
+ if (input.concept.lifecycle.status === "stale" || input.concept.reviewState === "stale") {
4444
+ input.warnings.push(`Lifecycle stale status: ${source}`);
4445
+ }
4446
+ if (input.concept.lifecycle.status === "revoked" || input.concept.reviewState === "revoked") {
4447
+ if (input.concept.lifecycle.revokedAt === null) {
4448
+ input.warnings.push(`Lifecycle revoked missing revokedAt: ${source}`);
4449
+ }
4450
+ if (input.concept.lifecycle.revokedReason === null) {
4451
+ input.warnings.push(`Lifecycle revoked missing revokedReason: ${source}`);
4452
+ }
4453
+ }
4454
+ if (
4455
+ input.concept.lifecycle.status === "superseded" ||
4456
+ input.concept.reviewState === "superseded"
4457
+ ) {
4458
+ const replacement = input.concept.lifecycle.supersededBy;
4459
+ if (replacement === null) {
4460
+ input.warnings.push(`Lifecycle superseded missing supersededBy: ${source}`);
4461
+ } else if (!input.knownConceptIds.has(normalizeConceptId(replacement))) {
4462
+ input.warnings.push(
4463
+ `Lifecycle superseded replacement missing: ${source} supersededBy=${sanitizeOkfText(replacement)}`,
4464
+ );
4465
+ }
4466
+ }
4467
+ }
4468
+
4469
+ function renderLifecycleYaml(indent: string, lifecycle: OkfKnowledgeLifecycle): string {
4470
+ return [
4471
+ `${indent}lifecycle:`,
4472
+ `${indent} status: ${yamlString(lifecycle.status)}`,
4473
+ `${indent} createdAt: ${yamlString(lifecycle.createdAt)}`,
4474
+ `${indent} lastVerifiedAt: ${yamlString(lifecycle.lastVerifiedAt)}`,
4475
+ `${indent} reviewAfter: ${yamlString(lifecycle.reviewAfter)}`,
4476
+ `${indent} staleAfter: ${yamlString(lifecycle.staleAfter)}`,
4477
+ renderYamlList(`${indent} supersedes`, lifecycle.supersedes),
4478
+ `${indent} supersededBy: ${yamlNullableString(lifecycle.supersededBy)}`,
4479
+ `${indent} revokedAt: ${yamlNullableString(lifecycle.revokedAt)}`,
4480
+ `${indent} revokedReason: ${yamlNullableString(lifecycle.revokedReason)}`,
4481
+ ].join("\n");
4482
+ }
4483
+
4484
+ function lifecycleStatusFromReviewState(
4485
+ reviewState: OkfKnowledgeReviewState,
4486
+ ): OkfKnowledgeLifecycleStatus {
4487
+ if (reviewState === "stale") return "stale";
4488
+ if (reviewState === "deprecated") return "deprecated";
4489
+ if (reviewState === "revoked") return "revoked";
4490
+ if (reviewState === "superseded") return "superseded";
4491
+ return "active";
4492
+ }
4493
+
4494
+ function resolveOkfLifecyclePolicy(input: {
4495
+ type: string;
4496
+ path: string;
4497
+ tags: string[];
4498
+ title: string;
4499
+ }): { reviewAfterDays: number; staleAfterDays: number } {
4500
+ const comparable =
4501
+ `${input.type} ${input.path} ${input.tags.join(" ")} ${input.title}`.toLowerCase();
4502
+ if (/\bverification\b/u.test(comparable)) return { reviewAfterDays: 120, staleAfterDays: 240 };
4503
+ if (/\bworkflow\b/u.test(comparable)) return { reviewAfterDays: 60, staleAfterDays: 120 };
4504
+ return { reviewAfterDays: 90, staleAfterDays: 180 };
4505
+ }
4506
+
4507
+ function isOkfLifecycleStatus(value: string | null): value is OkfKnowledgeLifecycleStatus {
4508
+ return OKF_LIFECYCLE_STATUSES.includes(value as OkfKnowledgeLifecycleStatus);
4509
+ }
4510
+
4511
+ function normalizeIsoDateString(value: string | null | undefined): string | null {
4512
+ if (value === undefined || value === null || value === "" || value === "null") return null;
4513
+ const time = Date.parse(value);
4514
+ if (!Number.isFinite(time)) return null;
4515
+ return new Date(time).toISOString();
4516
+ }
4517
+
4518
+ function addDaysIso(value: string, days: number): string {
4519
+ const date = new Date(value);
4520
+ date.setUTCDate(date.getUTCDate() + days);
4521
+ return date.toISOString();
4522
+ }
4523
+
4524
+ function readNullableLifecycleString(value: string | null): string | null {
4525
+ if (value === null) return null;
4526
+ const normalized = sanitizeOkfText(value);
4527
+ return normalized === "" || normalized === "null" ? null : normalized;
4528
+ }
4529
+
4530
+ function yamlNullableString(value: string | null): string {
4531
+ return value === null ? "null" : yamlString(value);
4532
+ }
4533
+
4534
+ function renderPrivacyYaml(indent: string): string {
4535
+ return [
4536
+ `${indent}privacy:`,
4537
+ `${indent} classification: ${yamlString("local-private")}`,
4538
+ `${indent} rawPromptsStored: false`,
4539
+ `${indent} rawLogsStored: false`,
4540
+ `${indent} sourceDumpsStored: false`,
4541
+ `${indent} rawCommandOutputStored: false`,
4542
+ `${indent} secretsStored: false`,
4543
+ `${indent} internalLinksStored: false`,
4544
+ ].join("\n");
4545
+ }
4546
+
4547
+ function renderYamlList(key: string, values: string[]): string {
4548
+ if (values.length === 0) return `${key}: []`;
4549
+ return [`${key}:`, ...values.map((value) => ` - ${yamlString(value)}`)].join("\n");
4550
+ }
4551
+
4552
+ function renderMarkdownList(values: string[]): string {
4553
+ return values.map((value) => `- ${value}`).join("\n");
4554
+ }
4555
+
4556
+ function yamlString(value: string): string {
4557
+ return JSON.stringify(value);
4558
+ }
4559
+
4560
+ function extractFrontmatter(content: string): { frontmatter: string; body: string } | null {
4561
+ if (!content.startsWith("---\n")) return null;
4562
+ const end = content.indexOf("\n---", 4);
4563
+ if (end < 0) return null;
4564
+ const frontmatter = content.slice(4, end).trim();
4565
+ const body = content.slice(end + 4).replace(/^\n/u, "");
4566
+ return { frontmatter, body };
4567
+ }
4568
+
4569
+ function hasFrontmatter(content: string): boolean {
4570
+ return extractFrontmatter(content) !== null;
4571
+ }
4572
+
4573
+ function extractNestedYamlBlock(
4574
+ frontmatter: string,
4575
+ rootKey: string,
4576
+ childKey: string,
4577
+ ): string | null {
4578
+ const lines = frontmatter.split("\n");
4579
+ const rootIndex = lines.findIndex((line) => line.trim() === `${rootKey}:`);
4580
+ if (rootIndex < 0) return null;
4581
+ const rootEnd = findYamlBlockEnd(lines, rootIndex, 0);
4582
+ const childIndex = lines.findIndex(
4583
+ (line, index) =>
4584
+ index > rootIndex &&
4585
+ index < rootEnd &&
4586
+ line.startsWith(" ") &&
4587
+ line.trim() === `${childKey}:`,
4588
+ );
4589
+ if (childIndex < 0) return null;
4590
+ const childEnd = findYamlBlockEnd(lines, childIndex, 2);
4591
+ return lines.slice(childIndex + 1, childEnd).join("\n");
4592
+ }
4593
+
4594
+ function findYamlBlockEnd(lines: string[], startIndex: number, parentIndent: number): number {
4595
+ for (let index = startIndex + 1; index < lines.length; index += 1) {
4596
+ const line = lines[index] ?? "";
4597
+ if (line.trim() === "") continue;
4598
+ const indent = line.length - line.trimStart().length;
4599
+ if (indent <= parentIndent) return index;
4600
+ }
4601
+ return lines.length;
4602
+ }
4603
+
4604
+ function readYamlScalar(frontmatter: string, key: string): string | null {
4605
+ const match = frontmatter.match(new RegExp(`^${escapeRegExp(key)}:\\s*(.+?)\\s*$`, "m"));
4606
+ if (match?.[1] === undefined) return null;
4607
+ return parseYamlValue(match[1]);
4608
+ }
4609
+
4610
+ function readIndentedYamlScalar(frontmatter: string, key: string): string | null {
4611
+ const match = frontmatter.match(new RegExp(`^\\s*${escapeRegExp(key)}:\\s*(.+?)\\s*$`, "m"));
4612
+ if (match?.[1] === undefined) return null;
4613
+ return parseYamlValue(match[1]);
4614
+ }
4615
+
4616
+ function parseOkfReviewState(value: string): OkfKnowledgeReviewState {
4617
+ return OKF_REVIEW_STATES.includes(value as OkfKnowledgeReviewState)
4618
+ ? (value as OkfKnowledgeReviewState)
4619
+ : "auto-stored/unreviewed";
4620
+ }
4621
+
4622
+ function readYamlList(frontmatter: string, key: string): string[] {
4623
+ const inline = frontmatter.match(
4624
+ new RegExp(`^\\s*${escapeRegExp(key)}:\\s*\\[(.*?)\\]\\s*$`, "m"),
4625
+ );
4626
+ if (inline?.[1] !== undefined) {
4627
+ return inline[1]
4628
+ .split(",")
4629
+ .map((item) => parseYamlValue(item.trim()))
4630
+ .filter((item) => item !== "");
4631
+ }
4632
+ const lines = frontmatter.split("\n");
4633
+ const values: string[] = [];
4634
+ for (let index = 0; index < lines.length; index += 1) {
4635
+ const line = lines[index] ?? "";
4636
+ if (!line.trim().startsWith(`${key}:`)) continue;
4637
+ for (let next = index + 1; next < lines.length; next += 1) {
4638
+ const candidate = lines[next] ?? "";
4639
+ if (!/^\s+-\s+/u.test(candidate)) break;
4640
+ values.push(parseYamlValue(candidate.replace(/^\s+-\s+/u, "")));
4641
+ }
4642
+ }
4643
+ return [...new Set(values.filter((value) => value !== ""))];
4644
+ }
4645
+
4646
+ function parseYamlValue(value: string): string {
4647
+ const trimmed = value.trim();
4648
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
4649
+ try {
4650
+ return JSON.parse(trimmed) as string;
4651
+ } catch {
4652
+ return trimmed.slice(1, -1);
4653
+ }
4654
+ }
4655
+ return trimmed.replace(/^['"]|['"]$/gu, "");
4656
+ }
4657
+
4658
+ async function listMarkdownFiles(root: string): Promise<Array<{ path: string; name: string }>> {
4659
+ if (!(await pathExists(root))) return [];
4660
+ const entries = await readdir(root, { withFileTypes: true });
4661
+ const files: Array<{ path: string; name: string }> = [];
4662
+ for (const entry of entries) {
4663
+ const path = join(root, entry.name);
4664
+ if (entry.isDirectory()) {
4665
+ files.push(...(await listMarkdownFiles(path)));
4666
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
4667
+ files.push({ path, name: entry.name });
4668
+ }
4669
+ }
4670
+ return files;
4671
+ }
4672
+
4673
+ async function listDirectories(root: string): Promise<string[]> {
4674
+ if (!(await pathExists(root))) return [];
4675
+ const entries = await readdir(root, { withFileTypes: true });
4676
+ const directories = [root];
4677
+ for (const entry of entries) {
4678
+ if (!entry.isDirectory()) continue;
4679
+ directories.push(...(await listDirectories(join(root, entry.name))));
4680
+ }
4681
+ return directories;
4682
+ }
4683
+
4684
+ function groupConceptsByTag(
4685
+ concepts: OkfKnowledgeConcept[],
4686
+ field: "repoTags" | "roleTags" | "workflowTags",
4687
+ ): Array<{ id: string; concepts: Array<{ id: string; title: string; sourceLink: string }> }> {
4688
+ const groups = new Map<string, Array<{ id: string; title: string; sourceLink: string }>>();
4689
+ for (const concept of concepts) {
4690
+ for (const id of concept[field]) {
4691
+ groups.set(id, [
4692
+ ...(groups.get(id) ?? []),
4693
+ { id: concept.id, title: concept.title, sourceLink: concept.sourceLink },
4694
+ ]);
4695
+ }
4696
+ }
4697
+ return [...groups.entries()].map(([id, groupedConcepts]) => ({ id, concepts: groupedConcepts }));
4698
+ }
4699
+
4700
+ function resolveOkfTargetPath(okfDir: string, targetPath: string): string {
4701
+ const clean = targetPath.replace(/^\/+/u, "");
4702
+ if (clean.split("/").some((segment) => segment === ".." || segment === "." || segment === "")) {
4703
+ throw new Error(`Unsafe OKF target path: ${targetPath}`);
4704
+ }
4705
+ const resolved = join(okfDir, clean);
4706
+ const rel = relative(okfDir, resolved);
4707
+ if (rel.startsWith("..") || rel === "") throw new Error(`Unsafe OKF target path: ${targetPath}`);
4708
+ return resolved;
4709
+ }
4710
+
4711
+ function normalizeConceptId(value: string): string {
4712
+ return value.replace(/^\/+/u, "").replace(/\.md$/u, "");
4713
+ }
4714
+
4715
+ function toOkfRelativePath(okfDir: string, path: string): string {
4716
+ return relative(okfDir, path).replace(/\\/gu, "/");
4717
+ }
4718
+
4719
+ function toOkfLink(okfDir: string, path: string): string {
4720
+ return `/${toOkfRelativePath(okfDir, path)}`;
4721
+ }
4722
+
4723
+ function displayOkfPath(okfDir: string, path: string): string {
4724
+ const rel = toOkfRelativePath(okfDir, path);
4725
+ return rel === "" ? "/" : rel;
4726
+ }
4727
+
4728
+ function sanitizeSlug(value: string): string {
4729
+ const slug = value
4730
+ .trim()
4731
+ .toLowerCase()
4732
+ .replace(/[^a-z0-9._/-]+/gu, "-")
4733
+ .replace(/\/+/gu, "/")
4734
+ .replace(/^-+|-+$/gu, "");
4735
+ return slug === "" ? "unknown" : slug;
4736
+ }
4737
+
4738
+ function titleFromSlug(value: string): string {
4739
+ return value
4740
+ .replace(/\.md$/u, "")
4741
+ .split(/[/-]/u)
4742
+ .filter(Boolean)
4743
+ .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
4744
+ .join(" ");
4745
+ }
4746
+
4747
+ function escapeRegExp(value: string): string {
4748
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
4749
+ }
4750
+
4751
+ function todayIsoDate(): string {
4752
+ return new Date().toISOString().slice(0, 10);
4753
+ }
4754
+
4755
+ async function writeJson(
4756
+ path: string,
4757
+ value: unknown,
4758
+ options: { overwrite?: boolean } = {},
4759
+ ): Promise<void> {
4760
+ await mkdir(dirname(path), { recursive: true });
4761
+ const flag = options.overwrite === true ? "w" : "wx";
4762
+ await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", flag });
4763
+ }
4764
+
4765
+ async function renameOrCopy(source: string, target: string): Promise<void> {
4766
+ await mkdir(dirname(target), { recursive: true });
4767
+ try {
4768
+ await rename(source, target);
4769
+ } catch {
4770
+ if (await pathExists(source)) {
4771
+ await writeFile(target, await readFile(source, "utf8"), "utf8");
4772
+ }
4773
+ }
4774
+ }
4775
+
4776
+ async function pathExists(path: string): Promise<boolean> {
4777
+ try {
4778
+ await stat(path);
4779
+ return true;
4780
+ } catch (error) {
4781
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
4782
+ throw error;
4783
+ }
4784
+ }