@evomap/evolver-core 2.0.0-beta.18 → 2.0.0-beta.19

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 (68) hide show
  1. package/dist/algo/candidateAssembly.js +21 -2
  2. package/dist/algo/cycleEngine.d.ts +12 -0
  3. package/dist/algo/cycleEngine.js +36 -4
  4. package/dist/algo/geneHealth.d.ts +2 -2
  5. package/dist/algo/geneHealth.js +5 -4
  6. package/dist/algo/geneSelection.d.ts +1 -1
  7. package/dist/algo/orchestrator.js +9 -2
  8. package/dist/assetstore/assetSidecarRecords.js +4 -0
  9. package/dist/assetstore/assetStoreHealth.js +41 -24
  10. package/dist/assetstore/assetStoreStorage.d.ts +1 -1
  11. package/dist/assetstore/assetStoreStorage.js +16 -7
  12. package/dist/assetstore/localJsonl.d.ts +2 -1
  13. package/dist/assetstore/localJsonl.js +54 -10
  14. package/dist/assetstore/provenance.d.ts +24 -0
  15. package/dist/assetstore/provenance.js +219 -12
  16. package/dist/assetstore/provider.d.ts +20 -1
  17. package/dist/assetstore/provider.js +34 -1
  18. package/dist/bootstrap/index.d.ts +2 -1
  19. package/dist/bootstrap/index.js +2 -1
  20. package/dist/bootstrap/v1EnvCompat.d.ts +110 -0
  21. package/dist/bootstrap/v1EnvCompat.js +256 -0
  22. package/dist/events/public.d.ts +1 -1
  23. package/dist/events/public.js +1 -1
  24. package/dist/events/reports.d.ts +2 -0
  25. package/dist/events/reports.js +4 -0
  26. package/dist/exec/autoExec.d.ts +1 -1
  27. package/dist/exec/autoExec.js +8 -10
  28. package/dist/exec/autonomousCycle.d.ts +19 -4
  29. package/dist/exec/autonomousCycle.js +63 -13
  30. package/dist/exec/claudeBridge.d.ts +25 -7
  31. package/dist/exec/claudeBridge.js +264 -29
  32. package/dist/exec/prompt.js +5 -1
  33. package/dist/exec/runnerRegistry.d.ts +68 -26
  34. package/dist/exec/runnerRegistry.js +307 -72
  35. package/dist/exec/selfPr.js +1 -7
  36. package/dist/feedback/envelope.d.ts +61 -0
  37. package/dist/feedback/envelope.js +168 -0
  38. package/dist/feedback/index.d.ts +1 -0
  39. package/dist/feedback/index.js +1 -0
  40. package/dist/hub/assetCallLog.d.ts +35 -1
  41. package/dist/hub/assetCallLog.js +124 -1
  42. package/dist/hub/bindings.d.ts +8 -1
  43. package/dist/hub/bindings.js +17 -6
  44. package/dist/hub/capability.d.ts +11 -1
  45. package/dist/hub/fake.d.ts +2 -2
  46. package/dist/hub/fake.js +1 -1
  47. package/dist/index.d.ts +3 -1
  48. package/dist/index.js +4 -1
  49. package/dist/mailbox/dispatch.d.ts +1 -1
  50. package/dist/mailbox/dispatch.js +22 -6
  51. package/dist/mailbox/envelope.d.ts +7 -1
  52. package/dist/mailbox/envelope.js +9 -2
  53. package/dist/mailbox/ipcServer.d.ts +10 -2
  54. package/dist/mailbox/ipcServer.js +163 -13
  55. package/dist/mailbox/store.d.ts +38 -2
  56. package/dist/mailbox/store.js +416 -27
  57. package/dist/signals/curriculum.d.ts +55 -0
  58. package/dist/signals/curriculum.js +202 -0
  59. package/dist/signals/expand.js +17 -6
  60. package/dist/signals/index.d.ts +2 -1
  61. package/dist/signals/index.js +2 -1
  62. package/dist/strategy/constraintAblation.js +115 -369
  63. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  64. package/dist/strategy/constraintAblationPredicates.js +339 -0
  65. package/dist/trace/proxyTurns.js +15 -7
  66. package/dist/verify/validation.d.ts +11 -1
  67. package/dist/verify/validation.js +31 -0
  68. package/package.json +4 -1
@@ -0,0 +1,339 @@
1
+ const FACTUAL_TAIL_PREFIX_TERMS = new Set([
2
+ 'after', 'although', 'as', 'because', 'before', 'however', 'since', 'than', 'though',
3
+ 'whereas', 'yet',
4
+ ]);
5
+ const RELATIVE_TERMS = new Set(['that', 'which', 'who']);
6
+ const SUBORDINATOR_TERMS = new Set(['after', 'as', 'before']);
7
+ const NOMINAL_MODIFIER_TERMS = new Set([
8
+ 'access', 'audit', 'backup', 'cold', 'data', 'offline', 'ongoing',
9
+ 'policy', 'remote', 'secure', 'security', 'token',
10
+ ]);
11
+ const NOMINAL_HEAD_TERMS = new Set([
12
+ 'access', 'approval', 'archive', 'audit', 'auditor', 'credentials', 'data', 'details',
13
+ 'leak', 'leaks', 'metadata', 'policy', 'receipt', 'records', 'request', 'review', 'server',
14
+ 'storage', 'system', 'transit',
15
+ ]);
16
+ const MATRIX_NOMINAL_COLLOCATIONS = new Set(['access:policy']);
17
+ const OBJECT_COORDINATOR_TERMS = new Set(['and', 'or', 'then']);
18
+ const FACTUAL_COORDINATOR_TERMS = new Set(['and', 'but', 'then']);
19
+ const TRAILING_MODIFIER_TERMS = new Set([
20
+ 'earlier', 'here', 'now', 'soon', 'today', 'tonight', 'there', 'yesterday',
21
+ ]);
22
+ const MATRIX_PREDICATE_TERMS = new Set([
23
+ 'arrive', 'arrives', 'become', 'becomes', 'exist', 'exists', 'expire', 'expires',
24
+ 'leak', 'leaks', 'match', 'matches', 'pass', 'passes', 'remain', 'remains', 'trigger', 'triggers',
25
+ ]);
26
+ class ProvidedTargetPrefixClassifier {
27
+ input;
28
+ tokens;
29
+ matchedObjectForms = new Set();
30
+ matchedObjectIndexes = new Set();
31
+ constructor(input) {
32
+ this.input = input;
33
+ this.tokens = input.predicates.tokenize(input.source).tokens;
34
+ }
35
+ classify() {
36
+ const objectPrefixEnd = this.matchObjectPrefix();
37
+ if (objectPrefixEnd < 0)
38
+ return 'none';
39
+ if (this.hasFiniteNonObjectPrefix(objectPrefixEnd))
40
+ return 'conditional';
41
+ const tailStart = this.consumeRemainingObjectTerms(objectPrefixEnd + 1);
42
+ return this.classifyObjectTail(tailStart);
43
+ }
44
+ matchObjectPrefix() {
45
+ const requiredMatches = Math.min(2, this.input.objectForms.length);
46
+ const deferredOverlaps = [];
47
+ for (let tokenIndex = 0; tokenIndex < this.tokens.length; tokenIndex += 1) {
48
+ const [formIndex, ...overlaps] = this.unmatchedObjectFormIndexes(this.valueAt(tokenIndex));
49
+ if (formIndex === undefined)
50
+ continue;
51
+ this.matchedObjectForms.add(formIndex);
52
+ this.matchedObjectIndexes.add(tokenIndex);
53
+ deferredOverlaps.push(...overlaps.map((overlap) => ({ formIndex: overlap, tokenIndex })));
54
+ if (this.matchedObjectForms.size >= requiredMatches)
55
+ return tokenIndex;
56
+ }
57
+ for (const { formIndex, tokenIndex } of deferredOverlaps) {
58
+ if (this.matchedObjectForms.has(formIndex))
59
+ continue;
60
+ this.matchedObjectForms.add(formIndex);
61
+ this.matchedObjectIndexes.add(tokenIndex);
62
+ if (this.matchedObjectForms.size >= requiredMatches) {
63
+ return Math.max(...this.matchedObjectIndexes);
64
+ }
65
+ }
66
+ return -1;
67
+ }
68
+ unmatchedObjectFormIndexes(value) {
69
+ return this.input.objectForms.flatMap((forms, candidateIndex) => (!this.matchedObjectForms.has(candidateIndex) && forms.has(value) ? [candidateIndex] : []));
70
+ }
71
+ hasFiniteNonObjectPrefix(objectPrefixEnd) {
72
+ const prefix = this.tokens
73
+ .slice(0, objectPrefixEnd + 1)
74
+ .filter((_, index) => !this.matchedObjectIndexes.has(index))
75
+ .map((token) => token.value)
76
+ .join(' ');
77
+ return this.input.predicates.finiteClauseSubjectPolarity(prefix) !== 'absent'
78
+ || this.input.predicates.hasIndependentAffirmativeAction(prefix, this.input.actionForms);
79
+ }
80
+ skipModifiers(initialIndex) {
81
+ let index = initialIndex;
82
+ while (index < this.tokens.length && this.isSkippableModifier(this.valueAt(index)))
83
+ index += 1;
84
+ return index;
85
+ }
86
+ isSkippableModifier(term) {
87
+ return this.input.lexicon.leadingClauseModifiers.has(term)
88
+ || TRAILING_MODIFIER_TERMS.has(term)
89
+ || /ly$/u.test(term);
90
+ }
91
+ consumeRemainingObjectTerms(initialIndex) {
92
+ let index = this.skipModifiers(initialIndex);
93
+ const remaining = new Set(this.input.objectForms.keys());
94
+ for (const matched of this.matchedObjectForms)
95
+ remaining.delete(matched);
96
+ while (index < this.tokens.length) {
97
+ const candidate = [...remaining].find((formIndex) => (this.input.objectForms[formIndex]?.has(this.valueAt(index))));
98
+ if (candidate === undefined)
99
+ break;
100
+ remaining.delete(candidate);
101
+ index = this.skipModifiers(index + 1);
102
+ }
103
+ return index;
104
+ }
105
+ isPastPredicate(term) {
106
+ return term.endsWith('ed')
107
+ || this.input.lexicon.irregularSimplePast.has(term)
108
+ || this.input.lexicon.irregularPastParticiples.has(term);
109
+ }
110
+ consumeNominal(initialIndex) {
111
+ let index = this.skipModifiers(initialIndex);
112
+ if (this.input.lexicon.subjectArticles.has(this.valueAt(index)))
113
+ index += 1;
114
+ while (index + 1 < this.tokens.length && this.isSimpleNominalModifier(this.valueAt(index)))
115
+ index += 1;
116
+ if (index < this.tokens.length)
117
+ index += 1;
118
+ return this.skipModifiers(index);
119
+ }
120
+ isSimpleNominalModifier(term) {
121
+ return this.input.predicates.isAttributiveTargetModifier(term) || NOMINAL_MODIFIER_TERMS.has(term);
122
+ }
123
+ isObjectForm(term) {
124
+ return this.input.objectForms.some((forms) => forms.has(term));
125
+ }
126
+ isMatrixPredicate(term) {
127
+ return MATRIX_PREDICATE_TERMS.has(term)
128
+ || this.input.predicates.isFinitePredicateTerm(term)
129
+ || (this.input.actionForms.has(term) && !this.isObjectForm(term));
130
+ }
131
+ isNominalHead(term) {
132
+ return NOMINAL_HEAD_TERMS.has(term) || this.isObjectForm(term);
133
+ }
134
+ isNominalModifier(term) {
135
+ return this.input.predicates.isAttributiveTargetModifier(term)
136
+ || NOMINAL_MODIFIER_TERMS.has(term)
137
+ || this.isObjectForm(term);
138
+ }
139
+ reachesKnownNominalHead(initialIndex, genericModifierBudget) {
140
+ let index = initialIndex;
141
+ let remainingBudget = genericModifierBudget;
142
+ while (index < this.tokens.length) {
143
+ const term = this.valueAt(index);
144
+ if (this.isNominalHead(term))
145
+ return true;
146
+ if (!this.isNominalModifier(term)) {
147
+ if (remainingBudget <= 0)
148
+ return false;
149
+ remainingBudget -= 1;
150
+ }
151
+ index = this.skipModifiers(index + 1);
152
+ }
153
+ return false;
154
+ }
155
+ isMatrixNominalCollocation(initialIndex) {
156
+ const modifier = this.valueAt(initialIndex);
157
+ let index = this.skipModifiers(initialIndex + 1);
158
+ while (index < this.tokens.length) {
159
+ const term = this.valueAt(index);
160
+ if (this.isNominalHead(term))
161
+ return MATRIX_NOMINAL_COLLOCATIONS.has(`${modifier}:${term}`);
162
+ if (!this.isNominalModifier(term))
163
+ return false;
164
+ index = this.skipModifiers(index + 1);
165
+ }
166
+ return false;
167
+ }
168
+ consumeNominalPhrase(initialIndex) {
169
+ let index = this.skipModifiers(initialIndex);
170
+ const hadArticle = this.input.lexicon.subjectArticles.has(this.valueAt(index));
171
+ if (hadArticle)
172
+ index += 1;
173
+ const prenominal = this.consumePrenominalModifiers(index, hadArticle ? 2 : 1);
174
+ index = prenominal.index;
175
+ const head = this.valueAt(index);
176
+ if (!this.isValidNominalHead(head, prenominal.sawModifier))
177
+ return index;
178
+ return this.consumeGerundComplements(this.skipModifiers(index + 1));
179
+ }
180
+ consumePrenominalModifiers(initialIndex, genericLimit) {
181
+ let index = initialIndex;
182
+ let genericModifiers = 0;
183
+ let sawModifier = false;
184
+ while (index + 1 < this.tokens.length) {
185
+ const nextIndex = this.skipModifiers(index + 1);
186
+ const term = this.valueAt(index);
187
+ if (this.isPrenominalBoundary(nextIndex))
188
+ break;
189
+ const knownModifier = this.isNominalModifier(term);
190
+ const genericModifier = !knownModifier
191
+ && !this.isNominalHead(term)
192
+ && genericModifiers < genericLimit
193
+ && this.reachesKnownNominalHead(nextIndex, genericLimit - genericModifiers - 1);
194
+ if (!knownModifier && !genericModifier)
195
+ break;
196
+ if (genericModifier)
197
+ genericModifiers += 1;
198
+ sawModifier = true;
199
+ index = nextIndex;
200
+ }
201
+ return { index, sawModifier };
202
+ }
203
+ isPrenominalBoundary(nextIndex) {
204
+ const next = this.valueAt(nextIndex);
205
+ return nextIndex >= this.tokens.length
206
+ || RELATIVE_TERMS.has(next)
207
+ || this.input.lexicon.conditionalMarkers.has(next)
208
+ || this.isFactualTailPrefix(next)
209
+ || this.input.lexicon.subjectArticles.has(next)
210
+ || (this.isMatrixPredicate(next) && !this.isMatrixNominalCollocation(nextIndex));
211
+ }
212
+ isValidNominalHead(head, sawPrenominalModifier) {
213
+ return Boolean(head)
214
+ && !RELATIVE_TERMS.has(head)
215
+ && !this.input.lexicon.conditionalMarkers.has(head)
216
+ && !this.isFactualTailPrefix(head)
217
+ && !this.input.lexicon.subjectArticles.has(head)
218
+ && !this.isMatrixPredicate(head)
219
+ && (!sawPrenominalModifier || this.isNominalHead(head));
220
+ }
221
+ consumeGerundComplements(initialIndex) {
222
+ let index = initialIndex;
223
+ while (this.valueAt(index).endsWith('ing')) {
224
+ const complementStart = this.skipModifiers(index + 1);
225
+ const complementEnd = this.consumeNominalPhrase(complementStart);
226
+ if (complementEnd <= complementStart)
227
+ break;
228
+ index = complementEnd;
229
+ }
230
+ return index;
231
+ }
232
+ consumeTerminalNominal(initialIndex) {
233
+ const nominalEnd = this.consumeNominalPhrase(initialIndex);
234
+ if (nominalEnd >= this.tokens.length)
235
+ return nominalEnd;
236
+ const prefixHead = this.firstNominalHead(initialIndex, nominalEnd);
237
+ return this.isNominalHead(this.valueAt(nominalEnd))
238
+ && prefixHead === nominalEnd
239
+ && this.skipModifiers(nominalEnd + 1) >= this.tokens.length
240
+ ? this.tokens.length
241
+ : nominalEnd;
242
+ }
243
+ firstNominalHead(initialIndex, nominalEnd) {
244
+ let index = this.skipModifiers(initialIndex);
245
+ if (this.input.lexicon.subjectArticles.has(this.valueAt(index))) {
246
+ index = this.skipModifiers(index + 1);
247
+ }
248
+ while (index < nominalEnd && !this.isNominalHead(this.valueAt(index))) {
249
+ index = this.skipModifiers(index + 1);
250
+ }
251
+ return index;
252
+ }
253
+ classifyRelativeTail(initialIndex) {
254
+ let index = this.skipModifiers(initialIndex);
255
+ const first = this.valueAt(index);
256
+ if (this.input.lexicon.subjectArticles.has(first)) {
257
+ index = this.consumeNominal(index);
258
+ }
259
+ else if (this.hasElidedPastSubject(index)) {
260
+ index = this.skipModifiers(index + 1);
261
+ }
262
+ while (this.input.lexicon.passiveAuxiliaries.has(this.valueAt(index))) {
263
+ index = this.skipModifiers(index + 1);
264
+ }
265
+ if (!this.isPastPredicate(this.valueAt(index)))
266
+ return 'conditional';
267
+ index = this.skipModifiers(index + 1);
268
+ return this.isFactualTerminalNominal(index);
269
+ }
270
+ hasElidedPastSubject(index) {
271
+ const first = this.valueAt(index);
272
+ return !this.isPastPredicate(first)
273
+ && !this.input.lexicon.passiveAuxiliaries.has(first)
274
+ && this.isPastPredicate(this.valueAt(index + 1));
275
+ }
276
+ classifySubordinateTail(initialIndex) {
277
+ let index = this.skipModifiers(initialIndex);
278
+ if (index >= this.tokens.length)
279
+ return 'factual';
280
+ if (this.isPastPredicate(this.valueAt(index)))
281
+ index = this.skipModifiers(index + 1);
282
+ return this.isFactualTerminalNominal(index);
283
+ }
284
+ isFactualTerminalNominal(index) {
285
+ if (index >= this.tokens.length)
286
+ return 'factual';
287
+ return this.consumeTerminalNominal(index) >= this.tokens.length ? 'factual' : 'conditional';
288
+ }
289
+ classifyObjectTail(initialIndex) {
290
+ const index = this.skipModifiers(initialIndex);
291
+ if (index >= this.tokens.length)
292
+ return 'factual';
293
+ const term = this.valueAt(index);
294
+ if (OBJECT_COORDINATOR_TERMS.has(term))
295
+ return this.classifyCoordinatedTail(index + 1);
296
+ if (RELATIVE_TERMS.has(term))
297
+ return this.classifyRelativeTail(index + 1);
298
+ if (this.input.lexicon.commaDelimitedPrepositions.has(term)) {
299
+ return this.classifyObjectTail(this.consumeTerminalNominal(index + 1));
300
+ }
301
+ if (this.input.lexicon.conditionalMarkers.has(term))
302
+ return 'conditional';
303
+ if (SUBORDINATOR_TERMS.has(term))
304
+ return this.classifySubordinateTail(index + 1);
305
+ if (this.isFactualTailPrefix(term))
306
+ return 'factual';
307
+ if (term.endsWith('ing'))
308
+ return this.classifyObjectTail(this.consumeTerminalNominal(index + 1));
309
+ return 'conditional';
310
+ }
311
+ classifyCoordinatedTail(initialIndex) {
312
+ let index = this.skipModifiers(initialIndex);
313
+ if (index >= this.tokens.length)
314
+ return 'factual';
315
+ const coordinatedTerm = this.valueAt(index);
316
+ if (this.input.actionForms.has(coordinatedTerm) && this.isPastPredicate(coordinatedTerm)) {
317
+ index = this.skipModifiers(index + 1);
318
+ if (index >= this.tokens.length)
319
+ return 'factual';
320
+ if (this.input.lexicon.commaDelimitedPrepositions.has(this.valueAt(index))) {
321
+ return this.classifyObjectTail(index);
322
+ }
323
+ }
324
+ return this.classifyObjectTail(this.consumeTerminalNominal(index));
325
+ }
326
+ isFactualTailPrefix(term) {
327
+ return FACTUAL_TAIL_PREFIX_TERMS.has(term)
328
+ || this.input.lexicon.commaDelimitedPrepositions.has(term)
329
+ || FACTUAL_COORDINATOR_TERMS.has(term);
330
+ }
331
+ valueAt(index) {
332
+ return this.tokens[index]?.value ?? '';
333
+ }
334
+ }
335
+ export function classifyProvidedTargetPrefix(input) {
336
+ if (!input.source || input.objectForms.length === 0)
337
+ return 'none';
338
+ return new ProvidedTargetPrefixClassifier(input).classify();
339
+ }
@@ -35,17 +35,18 @@ function utcDayStamp(ms) {
35
35
  return `${d.getUTCFullYear()}${p(d.getUTCMonth() + 1)}${p(d.getUTCDate())}`;
36
36
  }
37
37
  /**
38
- * Keep day-stamped files that could contain the window's turns. One extra preceding day is included so the
39
- * session-first-turn heuristic can see the pre-window history of a session that started before midnight.
38
+ * Keep day-stamped files that could contain the window's turns. Heuristic correlation must inspect all earlier
39
+ * day files to prove that an in-window session did not start before the run; exact session-id correlation only
40
+ * needs files that overlap the run window.
40
41
  * Non-day-stamped `llm-trace-*.jsonl` names (custom sinks/tests) are kept conservatively — the ts window
41
42
  * filter below is the authority; the filename filter only trims read volume.
42
43
  */
43
- function fileCoversWindow(name, window) {
44
+ function fileCoversWindow(name, window, includeSessionHistory) {
44
45
  const match = DAY_STAMPED_FILE_RE.exec(name);
45
46
  if (!match)
46
47
  return true;
47
48
  const stamp = match[1];
48
- return stamp >= utcDayStamp(window.startMs - 24 * 60 * 60 * 1000) && stamp <= utcDayStamp(window.endMs);
49
+ return (includeSessionHistory || stamp >= utcDayStamp(window.startMs)) && stamp <= utcDayStamp(window.endMs);
49
50
  }
50
51
  function turnTsMs(turn) {
51
52
  if (turn.ts === null)
@@ -102,10 +103,14 @@ export function collectRunLlmTurns(dir, window, opts = {}) {
102
103
  if (!(window.endMs >= window.startMs))
103
104
  return [];
104
105
  const names = readdirSync(dir)
105
- .filter((name) => TRACE_FILE_RE.test(name) && fileCoversWindow(name, window))
106
+ .filter((name) => TRACE_FILE_RE.test(name) && fileCoversWindow(name, window, opts.sessionId === undefined))
106
107
  .sort();
107
108
  const turns = [];
108
109
  for (const name of names) {
110
+ const dayMatch = DAY_STAMPED_FILE_RE.exec(name);
111
+ const isHistoricalDay = opts.sessionId === undefined
112
+ && dayMatch !== null
113
+ && dayMatch[1] < utcDayStamp(window.startMs);
109
114
  let text;
110
115
  try {
111
116
  text = readFileSync(join(dir, name), 'utf8');
@@ -117,8 +122,11 @@ export function collectRunLlmTurns(dir, window, opts = {}) {
117
122
  const { rows } = readTraceRowsFromJsonl(text, { ...(opts.readOptions ?? {}), allowPartial: true });
118
123
  for (const row of rows) {
119
124
  const turn = traceRecordToTurnDraft(row);
120
- if (turn !== null)
121
- turns.push(turn);
125
+ if (turn !== null) {
126
+ // Older day files establish that a session predates this run; they can never contribute candidate
127
+ // turns even if a malformed row carries an in-window timestamp.
128
+ turns.push(isHistoricalDay ? { ...turn, ts: new Date(window.startMs - 1).toISOString() } : turn);
129
+ }
122
130
  }
123
131
  }
124
132
  return selectRunLlmTurns(turns, window, opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {});
@@ -38,4 +38,14 @@ export declare function summarizeStdout(stdout: string): string;
38
38
  export declare function runValidation(plan: ValidationPlan, run: CommandRunner): Promise<{
39
39
  results: ValidationResult[];
40
40
  passed: boolean;
41
- }>;
41
+ }>;
42
+ /**
43
+ * 检查验证命令是否安全可作为 Gene.validation 条目使用(忠实移植 v1 policyCheck.isValidationCommandAllowed)。
44
+ * Gene 验证命令只允许 `node …` 形式,不允许非 node 命令(如 pnpm/npm/echo 等)。
45
+ * 安全条件:
46
+ * 1. 必须以 `node ` 开头
47
+ * 2. 不包含命令替换(` 或 $()
48
+ * 3. 不包含 shell 元字符(引号外)
49
+ * 4. 不包含被阻止的 node 标志(-e/--eval/--print 等)
50
+ */
51
+ export declare function isValidationCommandAllowed(cmd: string): boolean;
@@ -82,4 +82,35 @@ export async function runValidation(plan, run) {
82
82
  // 全部 allowed 的命令都 exit 0 才算通过; 有命令被 deny 视为未通过(校验不完整)
83
83
  const passed = results.length > 0 && results.every((r) => r.allowed && r.passed);
84
84
  return { results, passed };
85
+ }
86
+ /**
87
+ * 检查验证命令是否安全可作为 Gene.validation 条目使用(忠实移植 v1 policyCheck.isValidationCommandAllowed)。
88
+ * Gene 验证命令只允许 `node …` 形式,不允许非 node 命令(如 pnpm/npm/echo 等)。
89
+ * 安全条件:
90
+ * 1. 必须以 `node ` 开头
91
+ * 2. 不包含命令替换(` 或 $()
92
+ * 3. 不包含 shell 元字符(引号外)
93
+ * 4. 不包含被阻止的 node 标志(-e/--eval/--print 等)
94
+ */
95
+ export function isValidationCommandAllowed(cmd) {
96
+ const c = String(cmd || '').trim();
97
+ if (!c)
98
+ return false;
99
+ // 只允许 node 命令作为验证命令
100
+ if (!c.startsWith('node '))
101
+ return false;
102
+ // 不允许命令替换
103
+ if (/`|\$\(/.test(c))
104
+ return false;
105
+ // 移除引号内的内容后检查 shell 元字符
106
+ const stripped = c.replace(/"[^"]*"/g, '').replace(/'[^']*'/g, '');
107
+ if (SHELL_METACHARS.test(stripped))
108
+ return false;
109
+ // 检查被阻止的 node 标志
110
+ const tokens = c.split(/\s+/);
111
+ const executable = tokens[0] ?? '';
112
+ const args = tokens.slice(1);
113
+ if (nodeFlagViolation(executable, args))
114
+ return false;
115
+ return true;
85
116
  }
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@evomap/evolver-core",
3
- "version": "2.0.0-beta.18",
3
+ "version": "2.0.0-beta.19",
4
4
  "private": false,
5
5
  "type": "module",
6
+ "engines": {
7
+ "node": "^22.13.0 || >=23.4.0"
8
+ },
6
9
  "description": "hub-无关核心: 算法引擎/原材料/mailbox/资产库/workflow",
7
10
  "main": "./dist/index.js",
8
11
  "types": "./dist/index.d.ts",