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