@evo-dev/core 0.0.1-alpha.15 → 0.0.1-alpha.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evo-dev/core",
3
- "version": "0.0.1-alpha.15",
3
+ "version": "0.0.1-alpha.17",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,14 +15,25 @@ export interface PluginSettings {
15
15
  }
16
16
 
17
17
  export interface MemorySettings {
18
- autoAccept: boolean;
18
+ reviewKnowledgeUpdates: boolean;
19
19
  runtimeInjection: boolean;
20
20
  staleReview: boolean;
21
21
  lexicalIndex: boolean;
22
22
  sessionMemory: SessionMemoryPolicySnapshot;
23
23
  }
24
24
 
25
+ type LegacyMemorySettingsInput = Partial<MemorySettings> & {
26
+ /**
27
+ * Deprecated compatibility input. This flag was never connected to OKF activation and is
28
+ * intentionally omitted from canonical settings output.
29
+ */
30
+ autoAccept?: boolean;
31
+ };
32
+
25
33
  export interface EvolutionSettings {
34
+ schedule: {
35
+ dailyTime: string;
36
+ };
26
37
  automation: {
27
38
  knowledge: boolean;
28
39
  semanticKnowledge: boolean;
@@ -70,8 +81,9 @@ export type SettingsInput = Partial<{
70
81
  doctor: Partial<EvoDevSettings["doctor"]>;
71
82
  hooks: unknown;
72
83
  teamRuntime: Partial<TeamRuntimeSettings>;
73
- memory: Partial<MemorySettings>;
84
+ memory: LegacyMemorySettingsInput;
74
85
  evolution: Partial<{
86
+ schedule: Partial<EvolutionSettings["schedule"]>;
75
87
  automation: Partial<EvolutionSettings["automation"]>;
76
88
  }>;
77
89
  }>;
@@ -130,7 +142,7 @@ export function createDefaultTeamRuntimeSettings(): TeamRuntimeSettings {
130
142
 
131
143
  export function createDefaultMemorySettings(): MemorySettings {
132
144
  return {
133
- autoAccept: true,
145
+ reviewKnowledgeUpdates: true,
134
146
  runtimeInjection: true,
135
147
  staleReview: true,
136
148
  lexicalIndex: true,
@@ -140,6 +152,9 @@ export function createDefaultMemorySettings(): MemorySettings {
140
152
 
141
153
  export function createDefaultEvolutionSettings(): EvolutionSettings {
142
154
  return {
155
+ schedule: {
156
+ dailyTime: "02:00",
157
+ },
143
158
  automation: {
144
159
  knowledge: true,
145
160
  semanticKnowledge: false,
@@ -195,6 +210,10 @@ export function mergeSettings(
195
210
  evolution: {
196
211
  ...defaults.evolution,
197
212
  ...existing.evolution,
213
+ schedule: {
214
+ ...defaults.evolution.schedule,
215
+ ...existing.evolution?.schedule,
216
+ },
198
217
  automation: {
199
218
  ...defaults.evolution.automation,
200
219
  ...existing.evolution?.automation,
@@ -272,8 +291,15 @@ export function parseSettings(value: unknown): EvoDevSettings {
272
291
  function parseEvolutionSettings(value: unknown, path: string): EvolutionSettings {
273
292
  const input = expectRecord(value, path);
274
293
  const defaults = createDefaultEvolutionSettings();
294
+ const schedule = expectRecord(input.schedule ?? defaults.schedule, `${path}.schedule`);
275
295
  const automation = expectRecord(input.automation ?? defaults.automation, `${path}.automation`);
276
296
  return {
297
+ schedule: {
298
+ dailyTime: parseDailyTime(
299
+ schedule.dailyTime ?? defaults.schedule.dailyTime,
300
+ `${path}.schedule.dailyTime`,
301
+ ),
302
+ },
277
303
  automation: {
278
304
  knowledge:
279
305
  automation.knowledge === undefined
@@ -291,14 +317,26 @@ function parseEvolutionSettings(value: unknown, path: string): EvolutionSettings
291
317
  };
292
318
  }
293
319
 
320
+ function parseDailyTime(value: unknown, path: string): string {
321
+ const dailyTime = expectString(value, path);
322
+ const match = /^(\d{2}):(\d{2})$/u.exec(dailyTime);
323
+ if (match === null || Number(match[1]) > 23 || Number(match[2]) > 59) {
324
+ throw new EvoDevConfigError(`Invalid ${path}; expected a 24-hour HH:MM time`);
325
+ }
326
+ return dailyTime;
327
+ }
328
+
294
329
  function parseMemorySettings(value: unknown, path: string): MemorySettings {
295
330
  const input = expectRecord(value, path);
296
331
  const defaults = createDefaultMemorySettings();
332
+ if (input.autoAccept !== undefined) {
333
+ expectBoolean(input.autoAccept, `${path}.autoAccept`);
334
+ }
297
335
  return {
298
- autoAccept:
299
- input.autoAccept === undefined
300
- ? defaults.autoAccept
301
- : expectBoolean(input.autoAccept, `${path}.autoAccept`),
336
+ reviewKnowledgeUpdates:
337
+ input.reviewKnowledgeUpdates === undefined
338
+ ? defaults.reviewKnowledgeUpdates
339
+ : expectBoolean(input.reviewKnowledgeUpdates, `${path}.reviewKnowledgeUpdates`),
302
340
  runtimeInjection:
303
341
  input.runtimeInjection === undefined
304
342
  ? defaults.runtimeInjection
@@ -437,6 +437,56 @@ export async function updateEvolutionRepoProposalReviewState(input: {
437
437
  );
438
438
  }
439
439
 
440
+ export async function supersedeEvolutionRepoProposal(input: {
441
+ homeDir: string;
442
+ proposalId: string;
443
+ projectKey?: string;
444
+ expectedReviewState?: EvolutionRepoProposal["reviewState"];
445
+ reason: string;
446
+ now?: string | Date;
447
+ }): Promise<{ path: string; record: EvolutionRepoProposal; changed: boolean }> {
448
+ return await withEvolutionReviewDecisionLock(
449
+ { homeDir: input.homeDir, kind: "repo-proposal", itemId: input.proposalId },
450
+ async () => {
451
+ const record = await readEvolutionRepoProposalById(input);
452
+ const paths = resolveEvolutionPaths({
453
+ homeDir: input.homeDir,
454
+ projectKey: record.projectKey,
455
+ runId: record.provenance.runId,
456
+ });
457
+ const path = join(paths.repoProposalsDir, `${record.id}.json`);
458
+ if (record.reviewState === "superseded") return { path, record, changed: false };
459
+ if (
460
+ input.expectedReviewState !== undefined &&
461
+ record.reviewState !== input.expectedReviewState
462
+ ) {
463
+ throw new Error("Repo proposal review state changed before supersession was applied.");
464
+ }
465
+ if (record.reviewState !== "pending" && record.reviewState !== "deferred") {
466
+ throw new Error("Only pending or deferred repo proposals can be superseded.");
467
+ }
468
+ const reason = input.reason.trim();
469
+ if (reason === "" || reason.length > 500) {
470
+ throw new Error("Repo proposal supersession reason must be 1-500 characters.");
471
+ }
472
+ const changedAt = normalizeTimestamp(input.now);
473
+ const next: EvolutionRepoProposal = {
474
+ ...record,
475
+ reviewState: "superseded",
476
+ reviewStateChangedAt: changedAt,
477
+ supersession: {
478
+ policy: "session-proposal-v2",
479
+ reason: sanitizeText(reason),
480
+ supersededAt: changedAt,
481
+ },
482
+ };
483
+ validateEvolutionRepoProposal(next);
484
+ await writeRepoProposalAndIndex({ homeDir: input.homeDir, proposal: next, changedAt });
485
+ return { path, record: next, changed: true };
486
+ },
487
+ );
488
+ }
489
+
440
490
  export async function updateEvolutionRepoProposalImprovementEval(input: {
441
491
  homeDir: string;
442
492
  proposalId: string;
@@ -312,7 +312,8 @@ function hasPendingDerivedReview(
312
312
  (proposal) =>
313
313
  proposal.provenance.runId === runId &&
314
314
  proposal.reviewState !== "rejected" &&
315
- proposal.reviewState !== "applied",
315
+ proposal.reviewState !== "applied" &&
316
+ proposal.reviewState !== "superseded",
316
317
  )
317
318
  ) {
318
319
  return true;