@mmnto/totem 1.15.2 → 1.15.4

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 (59) hide show
  1. package/dist/compile-lesson.d.ts +35 -0
  2. package/dist/compile-lesson.d.ts.map +1 -1
  3. package/dist/compile-lesson.js +80 -39
  4. package/dist/compile-lesson.js.map +1 -1
  5. package/dist/compile-lesson.test.js +229 -0
  6. package/dist/compile-lesson.test.js.map +1 -1
  7. package/dist/compiler-schema.d.ts +58 -16
  8. package/dist/compiler-schema.d.ts.map +1 -1
  9. package/dist/compiler-schema.js +79 -0
  10. package/dist/compiler-schema.js.map +1 -1
  11. package/dist/compiler-schema.test.js +160 -1
  12. package/dist/compiler-schema.test.js.map +1 -1
  13. package/dist/compiler.d.ts +1 -1
  14. package/dist/compiler.d.ts.map +1 -1
  15. package/dist/compiler.js +1 -1
  16. package/dist/compiler.js.map +1 -1
  17. package/dist/index.d.ts +5 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +6 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/lesson-pattern.d.ts +20 -0
  22. package/dist/lesson-pattern.d.ts.map +1 -1
  23. package/dist/lesson-pattern.js +38 -0
  24. package/dist/lesson-pattern.js.map +1 -1
  25. package/dist/lesson-pattern.test.js +94 -1
  26. package/dist/lesson-pattern.test.js.map +1 -1
  27. package/dist/regex-safety/apply-rules-bounded.d.ts +35 -0
  28. package/dist/regex-safety/apply-rules-bounded.d.ts.map +1 -0
  29. package/dist/regex-safety/apply-rules-bounded.js +114 -0
  30. package/dist/regex-safety/apply-rules-bounded.js.map +1 -0
  31. package/dist/regex-safety/apply-rules-bounded.test.d.ts +2 -0
  32. package/dist/regex-safety/apply-rules-bounded.test.d.ts.map +1 -0
  33. package/dist/regex-safety/apply-rules-bounded.test.js +136 -0
  34. package/dist/regex-safety/apply-rules-bounded.test.js.map +1 -0
  35. package/dist/regex-safety/evaluator.d.ts +95 -0
  36. package/dist/regex-safety/evaluator.d.ts.map +1 -0
  37. package/dist/regex-safety/evaluator.js +314 -0
  38. package/dist/regex-safety/evaluator.js.map +1 -0
  39. package/dist/regex-safety/evaluator.test.d.ts +2 -0
  40. package/dist/regex-safety/evaluator.test.d.ts.map +1 -0
  41. package/dist/regex-safety/evaluator.test.js +224 -0
  42. package/dist/regex-safety/evaluator.test.js.map +1 -0
  43. package/dist/regex-safety/telemetry.d.ts +50 -0
  44. package/dist/regex-safety/telemetry.d.ts.map +1 -0
  45. package/dist/regex-safety/telemetry.js +50 -0
  46. package/dist/regex-safety/telemetry.js.map +1 -0
  47. package/dist/regex-safety/telemetry.test.d.ts +2 -0
  48. package/dist/regex-safety/telemetry.test.d.ts.map +1 -0
  49. package/dist/regex-safety/telemetry.test.js +82 -0
  50. package/dist/regex-safety/telemetry.test.js.map +1 -0
  51. package/dist/regex-safety/worker.d.ts +31 -0
  52. package/dist/regex-safety/worker.d.ts.map +1 -0
  53. package/dist/regex-safety/worker.js +51 -0
  54. package/dist/regex-safety/worker.js.map +1 -0
  55. package/dist/rule-engine.d.ts +1 -0
  56. package/dist/rule-engine.d.ts.map +1 -1
  57. package/dist/rule-engine.js +1 -1
  58. package/dist/rule-engine.js.map +1 -1
  59. package/package.json +1 -1
@@ -204,6 +204,115 @@ describe('buildCompiledRule', () => {
204
204
  const result = buildCompiledRule(parsed, lesson, existingByHash);
205
205
  expect(result.rule.severity).toBe('warning');
206
206
  });
207
+ // ─── Declared severity override (mmnto-ai/totem#1656) ──
208
+ it('honors declaredSeverityOverride when LLM emits a different severity', () => {
209
+ const parsed = {
210
+ compilable: true,
211
+ pattern: 'test',
212
+ message: 'test',
213
+ engine: 'regex',
214
+ severity: 'warning',
215
+ };
216
+ const result = buildCompiledRule(parsed, lesson, existingByHash, {
217
+ declaredSeverityOverride: 'error',
218
+ });
219
+ expect(result.rule.severity).toBe('error');
220
+ expect(result.severityOverride).toEqual({ from: 'warning', to: 'error' });
221
+ });
222
+ it('honors declaredSeverityOverride when LLM emits no severity', () => {
223
+ const parsed = {
224
+ compilable: true,
225
+ pattern: 'test',
226
+ message: 'test',
227
+ engine: 'regex',
228
+ };
229
+ const result = buildCompiledRule(parsed, lesson, existingByHash, {
230
+ declaredSeverityOverride: 'error',
231
+ });
232
+ expect(result.rule.severity).toBe('error');
233
+ expect(result.severityOverride).toEqual({ from: undefined, to: 'error' });
234
+ });
235
+ it('omits severityOverride marker when declared severity matches LLM output', () => {
236
+ const parsed = {
237
+ compilable: true,
238
+ pattern: 'test',
239
+ message: 'test',
240
+ engine: 'regex',
241
+ severity: 'error',
242
+ };
243
+ const result = buildCompiledRule(parsed, lesson, existingByHash, {
244
+ declaredSeverityOverride: 'error',
245
+ });
246
+ expect(result.rule.severity).toBe('error');
247
+ expect(result.severityOverride).toBeUndefined();
248
+ });
249
+ it('omits severityOverride marker when no declared severity provided', () => {
250
+ const parsed = {
251
+ compilable: true,
252
+ pattern: 'test',
253
+ message: 'test',
254
+ engine: 'regex',
255
+ severity: 'warning',
256
+ };
257
+ const result = buildCompiledRule(parsed, lesson, existingByHash);
258
+ expect(result.rule.severity).toBe('warning');
259
+ expect(result.severityOverride).toBeUndefined();
260
+ });
261
+ it('omits severityOverride marker when declared warning matches the default fallback', () => {
262
+ // LLM emits no severity AND declared is 'warning'. The emitted severity
263
+ // would have defaulted to 'warning' anyway, so the override changes
264
+ // nothing in the final rule. Marker must not fire — it would be
265
+ // telemetry noise on every lesson that declares its severity as warning.
266
+ const parsed = {
267
+ compilable: true,
268
+ pattern: 'test',
269
+ message: 'test',
270
+ engine: 'regex',
271
+ // severity deliberately omitted (undefined)
272
+ };
273
+ const result = buildCompiledRule(parsed, lesson, existingByHash, {
274
+ declaredSeverityOverride: 'warning',
275
+ });
276
+ expect(result.rule.severity).toBe('warning');
277
+ expect(result.severityOverride).toBeUndefined();
278
+ });
279
+ it('preserves severityOverride on rejection paths too', () => {
280
+ // CR round-3 finding on mmnto-ai/totem#1658: if the LLM drifts on
281
+ // severity AND emits an invalid pattern, the rejection path must still
282
+ // surface severityOverride so telemetry captures exactly the
283
+ // prompt-drift cases this signal is meant to detect.
284
+ const parsed = {
285
+ compilable: true,
286
+ pattern: '(unclosed',
287
+ message: 'test',
288
+ engine: 'regex',
289
+ severity: 'warning',
290
+ };
291
+ const result = buildCompiledRule(parsed, lesson, existingByHash, {
292
+ declaredSeverityOverride: 'error',
293
+ });
294
+ expect(result.rule).toBeNull();
295
+ expect(result.rejectReason).toContain('Rejected regex');
296
+ expect(result.severityOverride).toEqual({ from: 'warning', to: 'error' });
297
+ });
298
+ it('does not fabricate severityOverride on rejection paths when declared severity matches', () => {
299
+ // Inverse of the above: rejection path + no actual drift = no marker.
300
+ // The rejection is still reported; only the severity-override signal
301
+ // stays absent.
302
+ const parsed = {
303
+ compilable: true,
304
+ pattern: '(unclosed',
305
+ message: 'test',
306
+ engine: 'regex',
307
+ severity: 'error',
308
+ };
309
+ const result = buildCompiledRule(parsed, lesson, existingByHash, {
310
+ declaredSeverityOverride: 'error',
311
+ });
312
+ expect(result.rule).toBeNull();
313
+ expect(result.rejectReason).toContain('Rejected regex');
314
+ expect(result.severityOverride).toBeUndefined();
315
+ });
207
316
  });
208
317
  // ─── self-suppression guard (#1177) ────────────────
209
318
  describe('self-suppression guard (#1177)', () => {
@@ -1211,6 +1320,92 @@ describe('compileLesson', () => {
1211
1320
  expect(result.reason).toBeUndefined();
1212
1321
  }
1213
1322
  });
1323
+ it('routes LLM context-required signal to reasonCode context-required (Pipeline 2, mmnto-ai/totem#1598)', async () => {
1324
+ const deps = {
1325
+ parseCompilerResponse: vi.fn().mockReturnValue({
1326
+ compilable: false,
1327
+ reasonCode: 'context-required',
1328
+ reason: 'Lesson constrains scope to an enclosing function ("inside _process") the pattern cannot express.',
1329
+ }),
1330
+ runOrchestrator: vi.fn().mockResolvedValue('response'),
1331
+ existingByHash: new Map(),
1332
+ callbacks: { onWarn: vi.fn(), onDim: vi.fn() },
1333
+ };
1334
+ const result = await compileLesson(lesson, 'system prompt', deps);
1335
+ expect(result.status).toBe('skipped');
1336
+ if (result.status === 'skipped') {
1337
+ expect(result.reasonCode).toBe('context-required');
1338
+ }
1339
+ });
1340
+ it('routes LLM semantic-analysis-required signal to reasonCode semantic-analysis-required (Pipeline 2, mmnto-ai/totem#1634)', async () => {
1341
+ const deps = {
1342
+ parseCompilerResponse: vi.fn().mockReturnValue({
1343
+ compilable: false,
1344
+ reasonCode: 'semantic-analysis-required',
1345
+ reason: 'Lesson requires closure-body AST analysis (captured-float assignment inside par_iter_mut().for_each).',
1346
+ }),
1347
+ runOrchestrator: vi.fn().mockResolvedValue('response'),
1348
+ existingByHash: new Map(),
1349
+ callbacks: { onWarn: vi.fn(), onDim: vi.fn() },
1350
+ };
1351
+ const result = await compileLesson(lesson, 'system prompt', deps);
1352
+ expect(result.status).toBe('skipped');
1353
+ if (result.status === 'skipped') {
1354
+ expect(result.reasonCode).toBe('semantic-analysis-required');
1355
+ }
1356
+ });
1357
+ it('falls back to out-of-scope when LLM omits reasonCode on a non-compilable response', async () => {
1358
+ // Backward compatibility: LLM responses that mark compilable:false without
1359
+ // the reasonCode field continue to route through the generic out-of-scope
1360
+ // exit path. Only the narrow context-required signal opts into the new
1361
+ // classifier bucket.
1362
+ const deps = {
1363
+ parseCompilerResponse: vi.fn().mockReturnValue({
1364
+ compilable: false,
1365
+ reason: 'Describes a conceptual principle, not a code pattern',
1366
+ }),
1367
+ runOrchestrator: vi.fn().mockResolvedValue('response'),
1368
+ existingByHash: new Map(),
1369
+ callbacks: { onWarn: vi.fn(), onDim: vi.fn() },
1370
+ };
1371
+ const result = await compileLesson(lesson, 'system prompt', deps);
1372
+ expect(result.status).toBe('skipped');
1373
+ if (result.status === 'skipped') {
1374
+ expect(result.reasonCode).toBe('out-of-scope');
1375
+ }
1376
+ });
1377
+ it('anti-lazy: compiles a structurally-capturable scope-sensitive lesson when LLM supplies a pattern', async () => {
1378
+ // The context-required escape hatch (mmnto-ai/totem#1598) risks lazy-
1379
+ // rejection of lessons whose scope CAN be captured structurally (fileGlobs,
1380
+ // kind-scoped ast-grep, etc.). Lock in that a valid compilable response
1381
+ // with a pattern still compiles even when the lesson body literally
1382
+ // contains the escape-hatch trigger keywords ("inside", "only for new").
1383
+ // The compiler routing must trust `compilable:true` and must not scan the
1384
+ // lesson body for escape-hatch triggers. CR PR mmnto-ai/totem#1639 round-1
1385
+ // flagged that a fixture without scope markers left this invariant
1386
+ // untested; the explicit scope-sensitive body closes that gap.
1387
+ const scopeSensitiveLesson = {
1388
+ index: 0,
1389
+ heading: 'Use logger inside request handlers (anti-lazy #1598)',
1390
+ body: 'Always use logger inside request handlers; only for new handlers, console.log is forbidden. The scope here IS expressible structurally via fileGlobs, so the LLM must compile despite the "inside" and "only for new" trigger keywords in the body.',
1391
+ hash: 'antilazy1598scopesensitive',
1392
+ };
1393
+ const deps = {
1394
+ parseCompilerResponse: vi.fn().mockReturnValue({
1395
+ compilable: true,
1396
+ pattern: 'console\\.log',
1397
+ message: 'Use logger inside request handlers instead of console.log',
1398
+ engine: 'regex',
1399
+ badExample: 'console.log("debug")',
1400
+ goodExample: 'logger.info("debug")',
1401
+ }),
1402
+ runOrchestrator: vi.fn().mockResolvedValue('response'),
1403
+ existingByHash: new Map(),
1404
+ callbacks: { onWarn: vi.fn(), onDim: vi.fn() },
1405
+ };
1406
+ const result = await compileLesson(scopeSensitiveLesson, 'system prompt', deps);
1407
+ expect(result.status).toBe('compiled');
1408
+ });
1214
1409
  it('calls onWarn callback on failures', async () => {
1215
1410
  const deps = {
1216
1411
  parseCompilerResponse: vi.fn().mockReturnValue(null),
@@ -1470,6 +1665,40 @@ describe('compileLesson Pipeline 3 (Bad/Good snippets)', () => {
1470
1665
  expect(result.status).toBe('skipped');
1471
1666
  expect(deps.callbacks.onDim).toHaveBeenCalledWith(pipeline3Lesson.heading, expect.stringContaining('Pipeline 3'));
1472
1667
  });
1668
+ it('routes LLM context-required signal to reasonCode context-required (Pipeline 3, mmnto-ai/totem#1598)', async () => {
1669
+ const deps = {
1670
+ parseCompilerResponse: vi.fn().mockReturnValue({
1671
+ compilable: false,
1672
+ reasonCode: 'context-required',
1673
+ reason: 'Lesson constrains scope to "only for NEW proposal IDs"; pattern cannot distinguish new from existing.',
1674
+ }),
1675
+ runOrchestrator: vi.fn().mockResolvedValue('response'),
1676
+ existingByHash: new Map(),
1677
+ callbacks: { onWarn: vi.fn(), onDim: vi.fn() },
1678
+ };
1679
+ const result = await compileLesson(pipeline3Lesson, 'system prompt', deps);
1680
+ expect(result.status).toBe('skipped');
1681
+ if (result.status === 'skipped') {
1682
+ expect(result.reasonCode).toBe('context-required');
1683
+ }
1684
+ });
1685
+ it('routes LLM semantic-analysis-required signal to reasonCode semantic-analysis-required (Pipeline 3, mmnto-ai/totem#1634)', async () => {
1686
+ const deps = {
1687
+ parseCompilerResponse: vi.fn().mockReturnValue({
1688
+ compilable: false,
1689
+ reasonCode: 'semantic-analysis-required',
1690
+ reason: 'Multi-file contract: rule applies cross-system; single-lesson-body pattern cannot express the multi-file hazard.',
1691
+ }),
1692
+ runOrchestrator: vi.fn().mockResolvedValue('response'),
1693
+ existingByHash: new Map(),
1694
+ callbacks: { onWarn: vi.fn(), onDim: vi.fn() },
1695
+ };
1696
+ const result = await compileLesson(pipeline3Lesson, 'system prompt', deps);
1697
+ expect(result.status).toBe('skipped');
1698
+ if (result.status === 'skipped') {
1699
+ expect(result.reasonCode).toBe('semantic-analysis-required');
1700
+ }
1701
+ });
1473
1702
  it('rejects at the smoke gate when the pattern does not match the Bad snippet (mmnto/totem#1408)', async () => {
1474
1703
  const deps = {
1475
1704
  parseCompilerResponse: vi.fn().mockReturnValue({