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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/assets/skills/coding/knowledge-distillation/SKILL.md +5 -3
  2. package/assets/team/agents/code-reviewer.md +48 -0
  3. package/assets/team/agents/docs-maintainer.md +51 -0
  4. package/assets/team/agents/implementation-engineer.md +51 -0
  5. package/assets/team/agents/product-scope-analyst.md +58 -0
  6. package/assets/team/agents/release-engineer.md +55 -0
  7. package/assets/team/agents/security-boundary-reviewer.md +50 -0
  8. package/assets/team/agents/solution-architect.md +51 -0
  9. package/assets/team/agents/verification-engineer.md +51 -0
  10. package/assets/team/team.md +102 -0
  11. package/dist/assets/index.js +5 -5
  12. package/dist/config/index.js +793 -241
  13. package/dist/index.js +20840 -12908
  14. package/dist/plugins/index.js +13 -13
  15. package/package.json +1 -1
  16. package/src/agents/index.ts +1 -265
  17. package/src/code-agent-traces/index.ts +11 -12
  18. package/src/config/index.ts +2 -0
  19. package/src/config/settings.ts +116 -7
  20. package/src/config/store.ts +1 -1
  21. package/src/daemon/index.ts +1 -41
  22. package/src/evolution/candidates/index.ts +730 -0
  23. package/src/evolution/control/index.ts +20 -0
  24. package/src/evolution/evidence/analysis.ts +533 -0
  25. package/src/evolution/evidence/index.ts +3 -0
  26. package/src/evolution/evidence/session-memory/analysis.ts +287 -0
  27. package/src/evolution/evidence/session-memory/constants.ts +9 -0
  28. package/src/evolution/evidence/session-memory/index.ts +9 -0
  29. package/src/evolution/evidence/session-memory/paths.ts +29 -0
  30. package/src/evolution/evidence/session-memory/policy.ts +39 -0
  31. package/src/evolution/evidence/session-memory/retention.ts +643 -0
  32. package/src/evolution/evidence/session-memory/segment.ts +216 -0
  33. package/src/evolution/evidence/session-memory/semantic-packet.ts +408 -0
  34. package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
  35. package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
  36. package/src/evolution/evidence/session-memory/storage.ts +744 -0
  37. package/src/evolution/evidence/session-memory/types.ts +296 -0
  38. package/src/evolution/evidence/session-memory/updater.ts +199 -0
  39. package/src/evolution/formatters.ts +169 -0
  40. package/src/evolution/imports/apply.ts +435 -0
  41. package/src/evolution/imports/diff.ts +472 -0
  42. package/src/evolution/imports/index.ts +7 -0
  43. package/src/evolution/imports/materialize.ts +640 -0
  44. package/src/evolution/imports/paths.ts +129 -0
  45. package/src/evolution/imports/stage.ts +414 -0
  46. package/src/evolution/imports/storage.ts +952 -0
  47. package/src/evolution/imports/types.ts +226 -0
  48. package/src/evolution/index.ts +19 -2827
  49. package/src/evolution/knowledge/change-store.ts +558 -0
  50. package/src/evolution/knowledge/changes.ts +459 -0
  51. package/src/evolution/knowledge/freshness.ts +69 -0
  52. package/src/{knowledge → evolution/knowledge}/index.ts +1532 -206
  53. package/src/evolution/knowledge/review.ts +446 -0
  54. package/src/evolution/knowledge/support.ts +135 -0
  55. package/src/evolution/paths.ts +44 -0
  56. package/src/evolution/processor/distillation.ts +518 -0
  57. package/src/evolution/processor/index.ts +3 -0
  58. package/src/evolution/processor/process.ts +594 -0
  59. package/src/{learning → evolution/review}/index.ts +10 -14
  60. package/src/evolution/schema.ts +639 -0
  61. package/src/evolution/shared.ts +1053 -0
  62. package/src/evolution/triggers/classification.ts +102 -0
  63. package/src/evolution/triggers/index.ts +295 -0
  64. package/src/hooks/index.ts +281 -197
  65. package/src/index.ts +15 -4
  66. package/src/projects/index.ts +934 -0
  67. package/src/runtime-logs/index.ts +100 -13
  68. package/src/team/index.ts +582 -3
  69. package/src/utils/errors.ts +13 -0
  70. package/src/utils/fs.ts +40 -0
  71. package/src/utils/hash.ts +9 -0
  72. package/src/utils/ids.ts +12 -0
  73. package/src/utils/index.ts +7 -0
  74. package/src/utils/parsing.ts +11 -0
  75. package/src/utils/text.ts +18 -0
  76. package/src/utils/time.ts +5 -0
  77. package/src/workflow/index.ts +3 -21
  78. package/src/project/index.ts +0 -507
  79. package/src/task/index.ts +0 -840
@@ -0,0 +1,459 @@
1
+ import { createHash } from "node:crypto";
2
+ import type {
3
+ OkfKnowledgeConcept,
4
+ OkfKnowledgeLifecycleStatus,
5
+ OkfKnowledgePlanCandidate,
6
+ } from "./index.ts";
7
+ import type { OkfKnowledgeSupportRef } from "./support.ts";
8
+
9
+ export const OKF_KNOWLEDGE_RENDER_CONTRACT_VERSION = 2;
10
+
11
+ export type OkfKnowledgeChangeClassification =
12
+ | "create"
13
+ | "refresh"
14
+ | "update"
15
+ | "supersede"
16
+ | "revoke"
17
+ | "no_write";
18
+
19
+ export type OkfKnowledgeChangeOperation = "create" | "update" | "supersede" | "revoke";
20
+
21
+ export type OkfKnowledgeChangeState =
22
+ | "pending"
23
+ | "accepted"
24
+ | "rejected"
25
+ | "deferred"
26
+ | "applied"
27
+ | "superseded";
28
+
29
+ export interface OkfKnowledgeRuntimeProjectionV1 {
30
+ renderContractVersion: number;
31
+ type: string;
32
+ title: string;
33
+ description: string;
34
+ stableKey: string;
35
+ claim: string;
36
+ summary: string;
37
+ appliesWhen: string[];
38
+ howToApply: string;
39
+ guidance: string[];
40
+ antiCriteria: string[];
41
+ verification: string[];
42
+ citations: string[];
43
+ scopes: {
44
+ repoTags: string[];
45
+ roleTags: string[];
46
+ workflowTags: string[];
47
+ pathScopes: string[];
48
+ };
49
+ relatedConceptLinks: string[];
50
+ supportRef: OkfKnowledgeSupportRef | null;
51
+ lifecycle: {
52
+ status: OkfKnowledgeLifecycleStatus;
53
+ supersedes: string[];
54
+ supersededBy: string | null;
55
+ revoked: boolean;
56
+ };
57
+ }
58
+
59
+ export interface OkfKnowledgeRuntimeDiffV1 {
60
+ runtimeFields: string[];
61
+ metadataFields: string[];
62
+ summary: string;
63
+ }
64
+
65
+ export interface OkfKnowledgeChangeClassificationResult {
66
+ classification: OkfKnowledgeChangeClassification;
67
+ baseRevision: string | null;
68
+ candidateRevision: string;
69
+ baseProjection: OkfKnowledgeRuntimeProjectionV1 | null;
70
+ candidateProjection: OkfKnowledgeRuntimeProjectionV1;
71
+ diff: OkfKnowledgeRuntimeDiffV1;
72
+ reason: string;
73
+ }
74
+
75
+ export function createOkfKnowledgeRuntimeProjectionFromCandidate(
76
+ candidate: OkfKnowledgePlanCandidate,
77
+ ): OkfKnowledgeRuntimeProjectionV1 {
78
+ return normalizeRuntimeProjection({
79
+ renderContractVersion: OKF_KNOWLEDGE_RENDER_CONTRACT_VERSION,
80
+ type: candidate.okfType,
81
+ title: candidate.title,
82
+ description: candidate.description,
83
+ stableKey: candidate.stableKey,
84
+ claim: candidate.claim,
85
+ summary: candidate.bodySections.summary,
86
+ appliesWhen: candidate.bodySections.appliesWhen,
87
+ howToApply: candidate.howToApply,
88
+ guidance: candidate.bodySections.guidance,
89
+ antiCriteria: uniqueStrings([
90
+ ...candidate.antiCriteria,
91
+ ...candidate.bodySections.antiCriteria,
92
+ ]),
93
+ verification: candidate.bodySections.verification,
94
+ citations: candidate.bodySections.citations,
95
+ scopes: {
96
+ repoTags: candidate.repoTags,
97
+ roleTags: candidate.roleTags,
98
+ workflowTags: candidate.workflowTags,
99
+ pathScopes: candidate.pathScopes,
100
+ },
101
+ relatedConceptLinks: candidate.relatedConceptLinks,
102
+ supportRef: candidate.supportRef ?? null,
103
+ lifecycle: {
104
+ status: candidate.decision === "revoke" ? "revoked" : "active",
105
+ supersedes: [],
106
+ supersededBy: null,
107
+ revoked: candidate.decision === "revoke",
108
+ },
109
+ });
110
+ }
111
+
112
+ export function createOkfKnowledgeRuntimeProjectionFromConcept(
113
+ concept: OkfKnowledgeConcept,
114
+ ): OkfKnowledgeRuntimeProjectionV1 {
115
+ const sections = parseMarkdownSections(concept.body);
116
+ const summary = readSectionText(sections, "Summary");
117
+ const guidance = readSectionList(sections, "Guidance");
118
+ return normalizeRuntimeProjection({
119
+ renderContractVersion: OKF_KNOWLEDGE_RENDER_CONTRACT_VERSION,
120
+ type: concept.type,
121
+ title: concept.title,
122
+ description: concept.description,
123
+ stableKey: concept.stableKey,
124
+ claim: readSectionText(sections, "Claim") || summary,
125
+ summary,
126
+ appliesWhen: readSectionList(sections, "Applies When"),
127
+ howToApply: readSectionText(sections, "How to Apply") || guidance[0] || "",
128
+ guidance,
129
+ antiCriteria: readSectionList(sections, "Anti-Criteria"),
130
+ verification: readSectionList(sections, "Verification").filter(
131
+ (item) => item !== "No verification command is stored in this concept.",
132
+ ),
133
+ citations: readSectionList(sections, "Citations").filter(
134
+ (item) => item !== "No external citations stored.",
135
+ ),
136
+ scopes: {
137
+ repoTags: concept.repoTags,
138
+ roleTags: concept.roleTags,
139
+ workflowTags: concept.workflowTags,
140
+ pathScopes: concept.pathScopes,
141
+ },
142
+ relatedConceptLinks: readSectionList(sections, "Related Concepts").filter(
143
+ (item) => item !== "No related concepts yet.",
144
+ ),
145
+ supportRef: concept.supportRef ?? null,
146
+ lifecycle: {
147
+ status: concept.lifecycle.status,
148
+ supersedes: concept.lifecycle.supersedes,
149
+ supersededBy: concept.lifecycle.supersededBy,
150
+ revoked: concept.lifecycle.status === "revoked" || concept.lifecycle.revokedAt !== null,
151
+ },
152
+ });
153
+ }
154
+
155
+ export function createOkfKnowledgeRevision(projection: OkfKnowledgeRuntimeProjectionV1): string {
156
+ return `knowledge-rev-${createHash("sha256")
157
+ .update(stableJsonStringify(normalizeRuntimeProjection(projection)))
158
+ .digest("hex")
159
+ .slice(0, 24)}`;
160
+ }
161
+
162
+ export function diffOkfKnowledgeRuntimeProjections(
163
+ base: OkfKnowledgeRuntimeProjectionV1 | null,
164
+ candidate: OkfKnowledgeRuntimeProjectionV1,
165
+ metadataFields: string[] = [],
166
+ ): OkfKnowledgeRuntimeDiffV1 {
167
+ const runtimeFields = base === null ? ["concept"] : listChangedProjectionFields(base, candidate);
168
+ const normalizedMetadataFields = uniqueStrings(metadataFields).sort();
169
+ const summary =
170
+ runtimeFields.length === 0
171
+ ? normalizedMetadataFields.length === 0
172
+ ? "No effective knowledge change."
173
+ : `Metadata refresh: ${normalizedMetadataFields.join(", ")}.`
174
+ : base === null
175
+ ? "Create a new active knowledge concept."
176
+ : `Runtime knowledge changed: ${runtimeFields.join(", ")}.`;
177
+ return {
178
+ runtimeFields,
179
+ metadataFields: normalizedMetadataFields,
180
+ summary,
181
+ };
182
+ }
183
+
184
+ export function classifyOkfKnowledgeCandidate(input: {
185
+ candidate: OkfKnowledgePlanCandidate;
186
+ existingConcept?: OkfKnowledgeConcept | null;
187
+ metadataFields?: string[];
188
+ }): OkfKnowledgeChangeClassificationResult {
189
+ const existing = input.existingConcept ?? null;
190
+ const baseProjection =
191
+ existing === null ? null : createOkfKnowledgeRuntimeProjectionFromConcept(existing);
192
+ let candidateProjection = createOkfKnowledgeRuntimeProjectionFromCandidate(input.candidate);
193
+ if (baseProjection !== null && existing?.stableKey === input.candidate.stableKey) {
194
+ candidateProjection =
195
+ input.candidate.decision === "revoke"
196
+ ? normalizeRuntimeProjection({
197
+ ...baseProjection,
198
+ lifecycle: {
199
+ ...baseProjection.lifecycle,
200
+ status: "revoked",
201
+ revoked: true,
202
+ },
203
+ })
204
+ : {
205
+ ...candidateProjection,
206
+ lifecycle: baseProjection.lifecycle,
207
+ };
208
+ }
209
+ const candidateRevision = createOkfKnowledgeRevision(candidateProjection);
210
+ const baseRevision = baseProjection === null ? null : createOkfKnowledgeRevision(baseProjection);
211
+ const diff = diffOkfKnowledgeRuntimeProjections(
212
+ baseProjection,
213
+ candidateProjection,
214
+ input.metadataFields ?? ["evidenceRefs", "verifiedAt"],
215
+ );
216
+
217
+ if (input.candidate.decision === "no_write" || input.candidate.decision === "skip") {
218
+ return {
219
+ classification: "no_write",
220
+ baseRevision,
221
+ candidateRevision,
222
+ baseProjection,
223
+ candidateProjection,
224
+ diff,
225
+ reason: input.candidate.decisionReason || "Candidate is not eligible for a knowledge write.",
226
+ };
227
+ }
228
+ if (input.candidate.decision === "revoke" && existing === null) {
229
+ return {
230
+ classification: "no_write",
231
+ baseRevision,
232
+ candidateRevision,
233
+ baseProjection,
234
+ candidateProjection,
235
+ diff,
236
+ reason: "No active concept uses this stable key, so there is nothing to revoke.",
237
+ };
238
+ }
239
+ if (existing === null) {
240
+ return {
241
+ classification: "create",
242
+ baseRevision,
243
+ candidateRevision,
244
+ baseProjection,
245
+ candidateProjection,
246
+ diff,
247
+ reason: "No active concept uses this stable key.",
248
+ };
249
+ }
250
+ if (existing.stableKey !== input.candidate.stableKey) {
251
+ return {
252
+ classification: "no_write",
253
+ baseRevision,
254
+ candidateRevision,
255
+ baseProjection,
256
+ candidateProjection,
257
+ diff,
258
+ reason: "Existing concept identity does not match the candidate stable key.",
259
+ };
260
+ }
261
+ if (input.candidate.decision === "revoke") {
262
+ return {
263
+ classification: "revoke",
264
+ baseRevision,
265
+ candidateRevision,
266
+ baseProjection,
267
+ candidateProjection,
268
+ diff,
269
+ reason: input.candidate.decisionReason,
270
+ };
271
+ }
272
+ if (shouldSupersede(existing, input.candidate)) {
273
+ return {
274
+ classification: "supersede",
275
+ baseRevision,
276
+ candidateRevision,
277
+ baseProjection,
278
+ candidateProjection,
279
+ diff,
280
+ reason: "The candidate changes the concept identity, canonical path, or incompatible scope.",
281
+ };
282
+ }
283
+ if (diff.runtimeFields.length === 0) {
284
+ return {
285
+ classification: "refresh",
286
+ baseRevision,
287
+ candidateRevision,
288
+ baseProjection,
289
+ candidateProjection,
290
+ diff,
291
+ reason: "Runtime projection is unchanged; only verification or provenance metadata changed.",
292
+ };
293
+ }
294
+ return {
295
+ classification: "update",
296
+ baseRevision,
297
+ candidateRevision,
298
+ baseProjection,
299
+ candidateProjection,
300
+ diff,
301
+ reason: diff.summary,
302
+ };
303
+ }
304
+
305
+ function shouldSupersede(
306
+ concept: OkfKnowledgeConcept,
307
+ candidate: OkfKnowledgePlanCandidate,
308
+ ): boolean {
309
+ const existingTargetPath = concept.sourceLink.replace(/^\/+/u, "");
310
+ const candidateTargetPath = candidate.targetPath.replace(/^\/+/u, "");
311
+ if (existingTargetPath !== candidateTargetPath) return true;
312
+ if (normalizeText(concept.type) !== normalizeText(candidate.okfType)) return true;
313
+ return (
314
+ scopesAreIncompatible(concept.repoTags, candidate.repoTags) ||
315
+ scopesAreIncompatible(concept.roleTags, candidate.roleTags) ||
316
+ scopesAreIncompatible(concept.workflowTags, candidate.workflowTags)
317
+ );
318
+ }
319
+
320
+ function scopesAreIncompatible(left: string[], right: string[]): boolean {
321
+ if (left.length === 0 || right.length === 0) return false;
322
+ const normalizedRight = new Set(right.map(normalizeText));
323
+ return !left.some((value) => normalizedRight.has(normalizeText(value)));
324
+ }
325
+
326
+ function normalizeRuntimeProjection(
327
+ value: OkfKnowledgeRuntimeProjectionV1,
328
+ ): OkfKnowledgeRuntimeProjectionV1 {
329
+ return {
330
+ renderContractVersion: value.renderContractVersion,
331
+ type: normalizeText(value.type),
332
+ title: normalizeText(value.title),
333
+ description: normalizeText(value.description),
334
+ stableKey: normalizeText(value.stableKey),
335
+ claim: normalizeMarkdown(value.claim),
336
+ summary: normalizeMarkdown(value.summary),
337
+ appliesWhen: normalizeArray(value.appliesWhen),
338
+ howToApply: normalizeMarkdown(value.howToApply),
339
+ guidance: normalizeArray(value.guidance),
340
+ antiCriteria: normalizeArray(value.antiCriteria),
341
+ verification: normalizeArray(value.verification),
342
+ citations: normalizeArray(value.citations),
343
+ scopes: {
344
+ repoTags: normalizeArray(value.scopes.repoTags),
345
+ roleTags: normalizeArray(value.scopes.roleTags),
346
+ workflowTags: normalizeArray(value.scopes.workflowTags),
347
+ pathScopes: normalizeArray(value.scopes.pathScopes),
348
+ },
349
+ relatedConceptLinks: normalizeArray(value.relatedConceptLinks),
350
+ supportRef: value.supportRef,
351
+ lifecycle: {
352
+ status: value.lifecycle.status,
353
+ supersedes: normalizeArray(value.lifecycle.supersedes),
354
+ supersededBy:
355
+ value.lifecycle.supersededBy === null ? null : normalizeText(value.lifecycle.supersededBy),
356
+ revoked: value.lifecycle.revoked,
357
+ },
358
+ };
359
+ }
360
+
361
+ function listChangedProjectionFields(
362
+ base: OkfKnowledgeRuntimeProjectionV1,
363
+ candidate: OkfKnowledgeRuntimeProjectionV1,
364
+ ): string[] {
365
+ const fields: Array<[string, unknown, unknown]> = [
366
+ ["type", base.type, candidate.type],
367
+ ["title", base.title, candidate.title],
368
+ ["description", base.description, candidate.description],
369
+ ["stableKey", base.stableKey, candidate.stableKey],
370
+ ["claim", base.claim, candidate.claim],
371
+ ["summary", base.summary, candidate.summary],
372
+ ["appliesWhen", base.appliesWhen, candidate.appliesWhen],
373
+ ["howToApply", base.howToApply, candidate.howToApply],
374
+ ["guidance", base.guidance, candidate.guidance],
375
+ ["antiCriteria", base.antiCriteria, candidate.antiCriteria],
376
+ ["verification", base.verification, candidate.verification],
377
+ ["citations", base.citations, candidate.citations],
378
+ ["scopes.repoTags", base.scopes.repoTags, candidate.scopes.repoTags],
379
+ ["scopes.roleTags", base.scopes.roleTags, candidate.scopes.roleTags],
380
+ ["scopes.workflowTags", base.scopes.workflowTags, candidate.scopes.workflowTags],
381
+ ["scopes.pathScopes", base.scopes.pathScopes, candidate.scopes.pathScopes],
382
+ ["relatedConceptLinks", base.relatedConceptLinks, candidate.relatedConceptLinks],
383
+ ["supportRef", base.supportRef, candidate.supportRef],
384
+ ["lifecycle", base.lifecycle, candidate.lifecycle],
385
+ ];
386
+ return fields
387
+ .filter(([, left, right]) => stableJsonStringify(left) !== stableJsonStringify(right))
388
+ .map(([field]) => field);
389
+ }
390
+
391
+ function parseMarkdownSections(body: string): Map<string, string> {
392
+ const sections = new Map<string, string>();
393
+ let heading = "";
394
+ let lines: string[] = [];
395
+ const flush = () => {
396
+ if (heading !== "") sections.set(heading, lines.join("\n").trim());
397
+ };
398
+ for (const line of body.replace(/\r\n?/gu, "\n").split("\n")) {
399
+ const match = line.match(/^#\s+(.+?)\s*$/u);
400
+ if (match?.[1] !== undefined) {
401
+ flush();
402
+ heading = match[1];
403
+ lines = [];
404
+ continue;
405
+ }
406
+ lines.push(line);
407
+ }
408
+ flush();
409
+ return sections;
410
+ }
411
+
412
+ function readSectionText(sections: Map<string, string>, heading: string): string {
413
+ return normalizeMarkdown(sections.get(heading) ?? "");
414
+ }
415
+
416
+ function readSectionList(sections: Map<string, string>, heading: string): string[] {
417
+ const section = sections.get(heading) ?? "";
418
+ const items = section
419
+ .split("\n")
420
+ .map((line) => line.trim())
421
+ .filter((line) => /^[-*]\s+/u.test(line))
422
+ .map((line) => line.replace(/^[-*]\s+/u, ""));
423
+ if (items.length > 0) return normalizeArray(items);
424
+ const text = normalizeMarkdown(section);
425
+ return text === "" ? [] : [text];
426
+ }
427
+
428
+ function normalizeText(value: string): string {
429
+ return value.replace(/\s+/gu, " ").trim();
430
+ }
431
+
432
+ function normalizeMarkdown(value: string): string {
433
+ return value
434
+ .replace(/\r\n?/gu, "\n")
435
+ .split("\n")
436
+ .map((line) => line.trimEnd())
437
+ .join("\n")
438
+ .replace(/\n{3,}/gu, "\n\n")
439
+ .trim();
440
+ }
441
+
442
+ function normalizeArray(values: string[]): string[] {
443
+ return uniqueStrings(values.map(normalizeMarkdown).filter((value) => value !== "")).sort();
444
+ }
445
+
446
+ function uniqueStrings(values: string[]): string[] {
447
+ return [...new Set(values)];
448
+ }
449
+
450
+ function stableJsonStringify(value: unknown): string {
451
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
452
+ if (Array.isArray(value)) return `[${value.map(stableJsonStringify).join(",")}]`;
453
+ const entries = Object.entries(value as Record<string, unknown>)
454
+ .filter(([, child]) => child !== undefined)
455
+ .sort(([left], [right]) => left.localeCompare(right));
456
+ return `{${entries
457
+ .map(([key, child]) => `${JSON.stringify(key)}:${stableJsonStringify(child)}`)
458
+ .join(",")}}`;
459
+ }
@@ -0,0 +1,69 @@
1
+ import type { OkfKnowledgeConcept, OkfKnowledgePlanCandidate } from "./index.ts";
2
+ import { isDirectKnowledgeSupport, resolveOkfKnowledgeRuntimeEligibility } from "./support.ts";
3
+
4
+ export type OkfKnowledgeFreshness = "verified" | "review-due" | "unknown" | "stale";
5
+
6
+ export interface OkfKnowledgeVerificationSnapshotV1 {
7
+ schemaVersion: 1;
8
+ verifiedAt: string;
9
+ evidenceRefs: string[];
10
+ repository: null | {
11
+ projectKey: string;
12
+ head: string | null;
13
+ checkedPaths: Array<{
14
+ path: string;
15
+ sha256: string;
16
+ }>;
17
+ };
18
+ }
19
+
20
+ export interface OkfKnowledgeFreshnessResult {
21
+ freshness: OkfKnowledgeFreshness;
22
+ reason: string;
23
+ }
24
+
25
+ export function deriveOkfKnowledgeFreshness(input: {
26
+ concept: OkfKnowledgeConcept;
27
+ now?: string | Date;
28
+ repositoryFingerprintChanged?: boolean;
29
+ verifiedContradiction?: boolean;
30
+ }): OkfKnowledgeFreshnessResult {
31
+ const eligibility = resolveOkfKnowledgeRuntimeEligibility({
32
+ reviewState: input.concept.reviewState,
33
+ lifecycle: input.concept.lifecycle,
34
+ supportRef: input.concept.supportRef ?? null,
35
+ now: input.now,
36
+ sourceStatus:
37
+ input.repositoryFingerprintChanged === true
38
+ ? "changed"
39
+ : input.repositoryFingerprintChanged === false
40
+ ? "current"
41
+ : undefined,
42
+ verifiedContradiction: input.verifiedContradiction,
43
+ });
44
+ return {
45
+ freshness: eligibility.freshness,
46
+ reason: eligibility.reason,
47
+ };
48
+ }
49
+
50
+ export function createOkfKnowledgeVerificationSnapshot(input: {
51
+ candidate: OkfKnowledgePlanCandidate;
52
+ verifiedAt: string;
53
+ projectKey: string;
54
+ }): OkfKnowledgeVerificationSnapshotV1 | null {
55
+ if (!isDirectKnowledgeSupport(input.candidate.supportRef)) return null;
56
+ const verifiedAt = normalizeIso(input.verifiedAt);
57
+ if (verifiedAt === null) return null;
58
+ return {
59
+ schemaVersion: 1,
60
+ verifiedAt,
61
+ evidenceRefs: [...new Set(input.candidate.evidenceRefs)].sort(),
62
+ repository: null,
63
+ };
64
+ }
65
+
66
+ function normalizeIso(value: string): string | null {
67
+ const time = Date.parse(value);
68
+ return Number.isFinite(time) ? new Date(time).toISOString() : null;
69
+ }