@mmnto/totem 1.18.3 → 1.20.0
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/dist/compile-lesson.d.ts +42 -0
- package/dist/compile-lesson.d.ts.map +1 -1
- package/dist/compile-lesson.js +127 -0
- package/dist/compile-lesson.js.map +1 -1
- package/dist/compile-lesson.test.js +361 -0
- package/dist/compile-lesson.test.js.map +1 -1
- package/dist/compiler-schema.d.ts +110 -30
- package/dist/compiler-schema.d.ts.map +1 -1
- package/dist/compiler-schema.js +48 -2
- package/dist/compiler-schema.js.map +1 -1
- package/dist/compiler-schema.test.js +80 -0
- package/dist/compiler-schema.test.js.map +1 -1
- package/dist/compiler.d.ts +13 -6
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +14 -7
- package/dist/compiler.js.map +1 -1
- package/dist/compiler.test.js +33 -0
- package/dist/compiler.test.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/recurrence-stats.d.ts +10 -10
- package/dist/retrospect.d.ts +42 -42
- package/dist/stage4-verifier.d.ts +133 -0
- package/dist/stage4-verifier.d.ts.map +1 -0
- package/dist/stage4-verifier.js +355 -0
- package/dist/stage4-verifier.js.map +1 -0
- package/dist/stage4-verifier.test.d.ts +2 -0
- package/dist/stage4-verifier.test.d.ts.map +1 -0
- package/dist/stage4-verifier.test.js +372 -0
- package/dist/stage4-verifier.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -2947,4 +2947,365 @@ describe('compileLesson trace events', () => {
|
|
|
2947
2947
|
expect(orchestratorMock).not.toHaveBeenCalled();
|
|
2948
2948
|
});
|
|
2949
2949
|
});
|
|
2950
|
+
// ─── ADR-091 Stage 4 integration (mmnto-ai/totem#1682) ──
|
|
2951
|
+
describe('compileLesson Stage 4 integration', () => {
|
|
2952
|
+
function makePipeline2Deps(stage4Result) {
|
|
2953
|
+
const onWarn = vi.fn();
|
|
2954
|
+
const onDim = vi.fn();
|
|
2955
|
+
const onStage4Outcome = vi.fn();
|
|
2956
|
+
const verifyStage4 = stage4Result ? vi.fn().mockResolvedValue(stage4Result) : undefined;
|
|
2957
|
+
const deps = {
|
|
2958
|
+
parseCompilerResponse: vi.fn().mockReturnValue({
|
|
2959
|
+
compilable: true,
|
|
2960
|
+
pattern: 'console\\.log',
|
|
2961
|
+
message: 'No console.log',
|
|
2962
|
+
engine: 'regex',
|
|
2963
|
+
badExample: 'console.log("debug")',
|
|
2964
|
+
goodExample: '// noop\n',
|
|
2965
|
+
}),
|
|
2966
|
+
runOrchestrator: vi.fn().mockResolvedValue('{"compilable": true}'),
|
|
2967
|
+
existingByHash: new Map(),
|
|
2968
|
+
callbacks: { onWarn, onDim, onStage4Outcome },
|
|
2969
|
+
...(verifyStage4 ? { verifyStage4 } : {}),
|
|
2970
|
+
};
|
|
2971
|
+
return { deps, onWarn, onStage4Outcome, verifyStage4 };
|
|
2972
|
+
}
|
|
2973
|
+
function makePipeline2Lesson() {
|
|
2974
|
+
return {
|
|
2975
|
+
index: 0,
|
|
2976
|
+
heading: 'Pipeline 2 stage 4 lesson',
|
|
2977
|
+
body: 'Body without manual pattern, no Bad/Good snippets — Pipeline 2 path.',
|
|
2978
|
+
hash: 'h-stage4-p2',
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
it('does NOT invoke Stage 4 when deps.verifyStage4 is absent (existing behavior preserved)', async () => {
|
|
2982
|
+
const { deps } = makePipeline2Deps();
|
|
2983
|
+
const result = await compileLesson(makePipeline2Lesson(), 'system prompt', deps);
|
|
2984
|
+
expect(result.status).toBe('compiled');
|
|
2985
|
+
if (result.status === 'compiled') {
|
|
2986
|
+
expect(result.rule.status).toBeUndefined();
|
|
2987
|
+
expect(result.rule.confidence).toBeUndefined();
|
|
2988
|
+
}
|
|
2989
|
+
expect(deps.callbacks.onStage4Outcome).not.toHaveBeenCalled();
|
|
2990
|
+
});
|
|
2991
|
+
it("Pipeline 2: 'no-matches' outcome sets status='untested-against-codebase'", async () => {
|
|
2992
|
+
const { deps, onStage4Outcome } = makePipeline2Deps({
|
|
2993
|
+
outcome: 'no-matches',
|
|
2994
|
+
baselineMatches: [],
|
|
2995
|
+
inScopeMatches: [],
|
|
2996
|
+
candidateDebtLines: [],
|
|
2997
|
+
});
|
|
2998
|
+
const result = await compileLesson(makePipeline2Lesson(), 'system prompt', deps);
|
|
2999
|
+
expect(result.status).toBe('compiled');
|
|
3000
|
+
if (result.status === 'compiled') {
|
|
3001
|
+
expect(result.rule.status).toBe('untested-against-codebase');
|
|
3002
|
+
expect(result.rule.confidence).toBeUndefined();
|
|
3003
|
+
expect(result.rule.archivedReason).toBeUndefined();
|
|
3004
|
+
}
|
|
3005
|
+
expect(onStage4Outcome).toHaveBeenCalledTimes(1);
|
|
3006
|
+
expect(result.trace).toContainEqual(expect.objectContaining({ layer: 4, action: 'verify', outcome: 'no-matches' }));
|
|
3007
|
+
});
|
|
3008
|
+
it("Pipeline 2: 'in-scope-bad-example' outcome sets confidence='high'", async () => {
|
|
3009
|
+
const { deps, onStage4Outcome } = makePipeline2Deps({
|
|
3010
|
+
outcome: 'in-scope-bad-example',
|
|
3011
|
+
baselineMatches: [],
|
|
3012
|
+
inScopeMatches: ['packages/cli/src/foo.ts'],
|
|
3013
|
+
candidateDebtLines: [],
|
|
3014
|
+
});
|
|
3015
|
+
const result = await compileLesson(makePipeline2Lesson(), 'system prompt', deps);
|
|
3016
|
+
expect(result.status).toBe('compiled');
|
|
3017
|
+
if (result.status === 'compiled') {
|
|
3018
|
+
expect(result.rule.confidence).toBe('high');
|
|
3019
|
+
expect(result.rule.status).toBeUndefined();
|
|
3020
|
+
}
|
|
3021
|
+
expect(onStage4Outcome).toHaveBeenCalledTimes(1);
|
|
3022
|
+
expect(result.trace).toContainEqual(expect.objectContaining({ layer: 4, action: 'verify', outcome: 'in-scope-bad-example' }));
|
|
3023
|
+
});
|
|
3024
|
+
it("Pipeline 2: 'candidate-debt' outcome forces severity='warning' and emits onWarn", async () => {
|
|
3025
|
+
const { deps, onWarn, onStage4Outcome } = makePipeline2Deps({
|
|
3026
|
+
outcome: 'candidate-debt',
|
|
3027
|
+
baselineMatches: [],
|
|
3028
|
+
inScopeMatches: ['packages/cli/src/foo.ts', 'packages/cli/src/bar.ts'],
|
|
3029
|
+
candidateDebtLines: [
|
|
3030
|
+
'console.log(`${env.X}`)',
|
|
3031
|
+
'console.log(req.body.id)',
|
|
3032
|
+
"console.log('a')",
|
|
3033
|
+
"console.log('b')",
|
|
3034
|
+
],
|
|
3035
|
+
});
|
|
3036
|
+
// Make the rule's declared severity 'error' to verify the force-downgrade.
|
|
3037
|
+
deps.parseCompilerResponse.mockReturnValue({
|
|
3038
|
+
compilable: true,
|
|
3039
|
+
pattern: 'console\\.log',
|
|
3040
|
+
message: 'No console.log',
|
|
3041
|
+
engine: 'regex',
|
|
3042
|
+
badExample: 'console.log("debug")',
|
|
3043
|
+
goodExample: '// noop\n',
|
|
3044
|
+
severity: 'error',
|
|
3045
|
+
});
|
|
3046
|
+
const result = await compileLesson(makePipeline2Lesson(), 'system prompt', deps);
|
|
3047
|
+
expect(result.status).toBe('compiled');
|
|
3048
|
+
if (result.status === 'compiled') {
|
|
3049
|
+
expect(result.rule.severity).toBe('warning');
|
|
3050
|
+
}
|
|
3051
|
+
expect(onStage4Outcome).toHaveBeenCalledTimes(1);
|
|
3052
|
+
expect(result.trace).toContainEqual(expect.objectContaining({ layer: 4, action: 'verify', outcome: 'candidate-debt' }));
|
|
3053
|
+
// Sample of debt sites should be emitted via onWarn.
|
|
3054
|
+
expect(onWarn).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('Stage 4: candidate debt'));
|
|
3055
|
+
expect(onWarn).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('+ 1 more'));
|
|
3056
|
+
});
|
|
3057
|
+
it("Pipeline 2: 'out-of-scope' outcome archives the rule with reasonCode + paths in archivedReason", async () => {
|
|
3058
|
+
const { deps, onWarn, onStage4Outcome } = makePipeline2Deps({
|
|
3059
|
+
outcome: 'out-of-scope',
|
|
3060
|
+
baselineMatches: ['packages/core/src/transport.ts', 'packages/cli/src/foo.test.ts'],
|
|
3061
|
+
inScopeMatches: ['packages/cli/src/foo.ts'],
|
|
3062
|
+
candidateDebtLines: [],
|
|
3063
|
+
});
|
|
3064
|
+
const result = await compileLesson(makePipeline2Lesson(), 'system prompt', deps);
|
|
3065
|
+
expect(result.status).toBe('compiled');
|
|
3066
|
+
if (result.status === 'compiled') {
|
|
3067
|
+
expect(result.rule.status).toBe('archived');
|
|
3068
|
+
expect(result.rule.archivedAt).toBeDefined();
|
|
3069
|
+
expect(result.rule.archivedReason).toContain('Stage 4');
|
|
3070
|
+
expect(result.rule.archivedReason).toContain('stage4-out-of-scope-match');
|
|
3071
|
+
expect(result.rule.archivedReason).toContain('packages/core/src/transport.ts');
|
|
3072
|
+
expect(result.rule.archivedReason).toContain('packages/cli/src/foo.test.ts');
|
|
3073
|
+
}
|
|
3074
|
+
expect(onStage4Outcome).toHaveBeenCalledTimes(1);
|
|
3075
|
+
expect(result.trace).toContainEqual(expect.objectContaining({
|
|
3076
|
+
layer: 4,
|
|
3077
|
+
action: 'verify',
|
|
3078
|
+
outcome: 'out-of-scope',
|
|
3079
|
+
reasonCode: 'stage4-out-of-scope-match',
|
|
3080
|
+
}));
|
|
3081
|
+
expect(onWarn).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('Stage 4: archived'));
|
|
3082
|
+
});
|
|
3083
|
+
it('Pipeline 3 (Bad/Good example-based) wires Stage 4 — outcome mutates the rule (CR mmnto-ai/totem#1757 R2)', async () => {
|
|
3084
|
+
// Stage 4 is wired into both Pipeline 2 and Pipeline 3 success
|
|
3085
|
+
// branches. The other tests in this block pin Pipeline 2; this case
|
|
3086
|
+
// covers Pipeline 3 so a regression in the Pipeline 3 hook can't
|
|
3087
|
+
// bypass verification while the Pipeline 2 cases stay green.
|
|
3088
|
+
const verifyStage4 = vi.fn().mockResolvedValue({
|
|
3089
|
+
outcome: 'in-scope-bad-example',
|
|
3090
|
+
baselineMatches: [],
|
|
3091
|
+
inScopeMatches: ['packages/cli/src/foo.ts'],
|
|
3092
|
+
candidateDebtLines: [],
|
|
3093
|
+
});
|
|
3094
|
+
const onStage4Outcome = vi.fn();
|
|
3095
|
+
const onWarn = vi.fn();
|
|
3096
|
+
const deps = {
|
|
3097
|
+
parseCompilerResponse: vi.fn().mockReturnValue({
|
|
3098
|
+
compilable: true,
|
|
3099
|
+
pattern: 'console\\.log',
|
|
3100
|
+
message: 'No console.log',
|
|
3101
|
+
engine: 'regex',
|
|
3102
|
+
badExample: "console.log('debug')",
|
|
3103
|
+
goodExample: '// noop',
|
|
3104
|
+
}),
|
|
3105
|
+
runOrchestrator: vi.fn().mockResolvedValue('{"compilable": true}'),
|
|
3106
|
+
existingByHash: new Map(),
|
|
3107
|
+
callbacks: { onWarn, onDim: vi.fn(), onStage4Outcome },
|
|
3108
|
+
verifyStage4,
|
|
3109
|
+
};
|
|
3110
|
+
// Pipeline 3 dispatches when `extractBadGoodSnippets` returns
|
|
3111
|
+
// snippets — body needs explicit Bad/Good code blocks AND no
|
|
3112
|
+
// manual `**Pattern:**` (else Pipeline 1 wins).
|
|
3113
|
+
const pipeline3Lesson = {
|
|
3114
|
+
index: 0,
|
|
3115
|
+
heading: 'No console.log in production',
|
|
3116
|
+
body: [
|
|
3117
|
+
'**Bad:**',
|
|
3118
|
+
'',
|
|
3119
|
+
'```ts',
|
|
3120
|
+
"console.log('debug')",
|
|
3121
|
+
'```',
|
|
3122
|
+
'',
|
|
3123
|
+
'**Good:**',
|
|
3124
|
+
'',
|
|
3125
|
+
'```ts',
|
|
3126
|
+
'// noop',
|
|
3127
|
+
'```',
|
|
3128
|
+
].join('\n'),
|
|
3129
|
+
hash: 'h-stage4-p3',
|
|
3130
|
+
};
|
|
3131
|
+
const result = await compileLesson(pipeline3Lesson, 'system prompt', deps);
|
|
3132
|
+
expect(result.status).toBe('compiled');
|
|
3133
|
+
if (result.status === 'compiled') {
|
|
3134
|
+
expect(result.rule.confidence).toBe('high');
|
|
3135
|
+
}
|
|
3136
|
+
expect(verifyStage4).toHaveBeenCalledTimes(1);
|
|
3137
|
+
expect(onStage4Outcome).toHaveBeenCalledTimes(1);
|
|
3138
|
+
expect(result.trace).toContainEqual(expect.objectContaining({ layer: 4, action: 'verify', outcome: 'in-scope-bad-example' }));
|
|
3139
|
+
});
|
|
3140
|
+
it('Pipeline 1 (manual rule) bypasses Stage 4 — verifyStage4 not invoked', async () => {
|
|
3141
|
+
// Pipeline 1 manual rules are human-authored and self-evidencing per the
|
|
3142
|
+
// Pipeline 1 / unverified semantics; Stage 4 is a safety net for LLM-
|
|
3143
|
+
// generated patterns. The integration site only invokes Stage 4 from
|
|
3144
|
+
// Pipeline 2 / Pipeline 3 success branches.
|
|
3145
|
+
const verifyStage4 = vi.fn().mockResolvedValue({
|
|
3146
|
+
outcome: 'no-matches',
|
|
3147
|
+
baselineMatches: [],
|
|
3148
|
+
inScopeMatches: [],
|
|
3149
|
+
candidateDebtLines: [],
|
|
3150
|
+
});
|
|
3151
|
+
const deps = {
|
|
3152
|
+
parseCompilerResponse: vi.fn(),
|
|
3153
|
+
runOrchestrator: vi.fn(),
|
|
3154
|
+
existingByHash: new Map(),
|
|
3155
|
+
callbacks: { onWarn: vi.fn(), onDim: vi.fn() },
|
|
3156
|
+
verifyStage4,
|
|
3157
|
+
};
|
|
3158
|
+
const result = await compileLesson(manualLesson, 'system prompt', deps);
|
|
3159
|
+
expect(result.status).toBe('compiled');
|
|
3160
|
+
expect(verifyStage4).not.toHaveBeenCalled();
|
|
3161
|
+
});
|
|
3162
|
+
it('preserves the layer-3 MATCH trace event alongside the new layer-4 verify event', async () => {
|
|
3163
|
+
// Stage 4 appends to the trace; it does not replace existing events.
|
|
3164
|
+
// The CLI --verbose renderer relies on the full sequence.
|
|
3165
|
+
const { deps } = makePipeline2Deps({
|
|
3166
|
+
outcome: 'no-matches',
|
|
3167
|
+
baselineMatches: [],
|
|
3168
|
+
inScopeMatches: [],
|
|
3169
|
+
candidateDebtLines: [],
|
|
3170
|
+
});
|
|
3171
|
+
const result = await compileLesson(makePipeline2Lesson(), 'system prompt', deps);
|
|
3172
|
+
expect(result.trace).toContainEqual(expect.objectContaining({ layer: 3, action: 'verify', outcome: 'MATCH' }));
|
|
3173
|
+
expect(result.trace).toContainEqual(expect.objectContaining({ layer: 3, action: 'result', outcome: 'compiled' }));
|
|
3174
|
+
expect(result.trace).toContainEqual(expect.objectContaining({ layer: 4, action: 'verify', outcome: 'no-matches' }));
|
|
3175
|
+
});
|
|
3176
|
+
it("'in-scope-bad-example' promotes a carry-forward 'untested-against-codebase' rule to 'active' — CR mmnto-ai/totem#1757 R2", async () => {
|
|
3177
|
+
// F6 filters `'untested-against-codebase'` out of the lint path, so
|
|
3178
|
+
// a recompile that produces positive Stage 4 evidence MUST clear the
|
|
3179
|
+
// stale status or the rule stays inert despite high-confidence
|
|
3180
|
+
// matches. Tested for both in-scope-bad-example (this case) and
|
|
3181
|
+
// candidate-debt (next case).
|
|
3182
|
+
const lesson = makePipeline2Lesson();
|
|
3183
|
+
const { deps } = makePipeline2Deps({
|
|
3184
|
+
outcome: 'in-scope-bad-example',
|
|
3185
|
+
baselineMatches: [],
|
|
3186
|
+
inScopeMatches: ['packages/cli/src/foo.ts'],
|
|
3187
|
+
candidateDebtLines: [],
|
|
3188
|
+
});
|
|
3189
|
+
deps.existingByHash = new Map([
|
|
3190
|
+
[
|
|
3191
|
+
lesson.hash,
|
|
3192
|
+
{
|
|
3193
|
+
lessonHash: lesson.hash,
|
|
3194
|
+
message: 'previously-untested',
|
|
3195
|
+
pattern: 'console\\.log',
|
|
3196
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
3197
|
+
status: 'untested-against-codebase',
|
|
3198
|
+
},
|
|
3199
|
+
],
|
|
3200
|
+
]);
|
|
3201
|
+
const result = await compileLesson(lesson, 'system prompt', deps);
|
|
3202
|
+
expect(result.status).toBe('compiled');
|
|
3203
|
+
if (result.status === 'compiled') {
|
|
3204
|
+
expect(result.rule.status).toBe('active');
|
|
3205
|
+
expect(result.rule.confidence).toBe('high');
|
|
3206
|
+
}
|
|
3207
|
+
});
|
|
3208
|
+
it("'candidate-debt' promotes a carry-forward 'untested-against-codebase' rule to 'active' — CR mmnto-ai/totem#1757 R2", async () => {
|
|
3209
|
+
const lesson = makePipeline2Lesson();
|
|
3210
|
+
const { deps } = makePipeline2Deps({
|
|
3211
|
+
outcome: 'candidate-debt',
|
|
3212
|
+
baselineMatches: [],
|
|
3213
|
+
inScopeMatches: ['packages/cli/src/foo.ts'],
|
|
3214
|
+
candidateDebtLines: ['console.log(req.body.id)'],
|
|
3215
|
+
});
|
|
3216
|
+
deps.existingByHash = new Map([
|
|
3217
|
+
[
|
|
3218
|
+
lesson.hash,
|
|
3219
|
+
{
|
|
3220
|
+
lessonHash: lesson.hash,
|
|
3221
|
+
message: 'previously-untested',
|
|
3222
|
+
pattern: 'console\\.log',
|
|
3223
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
3224
|
+
status: 'untested-against-codebase',
|
|
3225
|
+
},
|
|
3226
|
+
],
|
|
3227
|
+
]);
|
|
3228
|
+
const result = await compileLesson(lesson, 'system prompt', deps);
|
|
3229
|
+
expect(result.status).toBe('compiled');
|
|
3230
|
+
if (result.status === 'compiled') {
|
|
3231
|
+
expect(result.rule.status).toBe('active');
|
|
3232
|
+
expect(result.rule.severity).toBe('warning');
|
|
3233
|
+
}
|
|
3234
|
+
});
|
|
3235
|
+
it("'no-matches' preserves a previously archived rule's status (carry-forward) — CR mmnto-ai/totem#1757 R1", async () => {
|
|
3236
|
+
// `preserveLifecycleFields` carries `status: 'archived'` (and its
|
|
3237
|
+
// `archivedReason`/`archivedAt`) forward on `--force` recompile.
|
|
3238
|
+
// Setting `status = 'untested-against-codebase'` unconditionally on
|
|
3239
|
+
// a `'no-matches'` outcome would silently un-archive a rule that
|
|
3240
|
+
// postmerge curation explicitly silenced.
|
|
3241
|
+
const lesson = makePipeline2Lesson();
|
|
3242
|
+
const { deps } = makePipeline2Deps({
|
|
3243
|
+
outcome: 'no-matches',
|
|
3244
|
+
baselineMatches: [],
|
|
3245
|
+
inScopeMatches: [],
|
|
3246
|
+
candidateDebtLines: [],
|
|
3247
|
+
});
|
|
3248
|
+
deps.existingByHash = new Map([
|
|
3249
|
+
[
|
|
3250
|
+
lesson.hash,
|
|
3251
|
+
{
|
|
3252
|
+
lessonHash: lesson.hash,
|
|
3253
|
+
message: 'previously-archived',
|
|
3254
|
+
pattern: 'console\\.log',
|
|
3255
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
3256
|
+
status: 'archived',
|
|
3257
|
+
archivedReason: 'manual archive (postmerge curation)',
|
|
3258
|
+
archivedAt: '2026-02-01T00:00:00.000Z',
|
|
3259
|
+
},
|
|
3260
|
+
],
|
|
3261
|
+
]);
|
|
3262
|
+
const result = await compileLesson(lesson, 'system prompt', deps);
|
|
3263
|
+
expect(result.status).toBe('compiled');
|
|
3264
|
+
if (result.status === 'compiled') {
|
|
3265
|
+
expect(result.rule.status).toBe('archived');
|
|
3266
|
+
expect(result.rule.archivedReason).toBe('manual archive (postmerge curation)');
|
|
3267
|
+
expect(result.rule.archivedAt).toBe('2026-02-01T00:00:00.000Z');
|
|
3268
|
+
}
|
|
3269
|
+
});
|
|
3270
|
+
it("'candidate-debt' sanitizes CSI bytes in debtLines before onWarn — CR mmnto-ai/totem#1757 R1", async () => {
|
|
3271
|
+
// Repository code can carry CSI / control bytes from a tampered
|
|
3272
|
+
// file. `onWarn` lands in terminal output; raw text would let a
|
|
3273
|
+
// hostile pattern spoof cursor moves or color resets. Mirrors the
|
|
3274
|
+
// #1743 R4-R7 sanitization wave on the agent-rendered surface.
|
|
3275
|
+
const { deps, onWarn } = makePipeline2Deps({
|
|
3276
|
+
outcome: 'candidate-debt',
|
|
3277
|
+
baselineMatches: [],
|
|
3278
|
+
inScopeMatches: ['packages/cli/src/foo.ts'],
|
|
3279
|
+
candidateDebtLines: ['console.log(\x1b[31m"red"\x1b[0m)', 'console.log("\x07bell")'],
|
|
3280
|
+
});
|
|
3281
|
+
await compileLesson(makePipeline2Lesson(), 'system prompt', deps);
|
|
3282
|
+
const warnCall = onWarn.mock.calls.find((call) => typeof call[1] === 'string' && call[1].includes('candidate debt'));
|
|
3283
|
+
expect(warnCall).toBeDefined();
|
|
3284
|
+
const message = warnCall[1];
|
|
3285
|
+
// No raw ESC, no raw BEL after sanitization.
|
|
3286
|
+
expect(message).not.toMatch(/\x1b/);
|
|
3287
|
+
expect(message).not.toMatch(/\x07/);
|
|
3288
|
+
// Visible code shape preserved (CSI sequence stripped, payload kept).
|
|
3289
|
+
expect(message).toContain('"red"');
|
|
3290
|
+
});
|
|
3291
|
+
it("'out-of-scope' sanitizes CSI bytes in baselineMatches before archivedReason — CR mmnto-ai/totem#1757 R1", async () => {
|
|
3292
|
+
// Path text persists into compiled-rules.json (`archivedReason`)
|
|
3293
|
+
// and surfaces in `onWarn`. A hostile filename with CSI bytes would
|
|
3294
|
+
// re-emerge whenever the manifest is tailed in a terminal.
|
|
3295
|
+
const { deps } = makePipeline2Deps({
|
|
3296
|
+
outcome: 'out-of-scope',
|
|
3297
|
+
baselineMatches: ['packages/cli/src/\x1b[31mhostile\x1b[0m.test.ts'],
|
|
3298
|
+
inScopeMatches: ['packages/cli/src/foo.ts'],
|
|
3299
|
+
candidateDebtLines: [],
|
|
3300
|
+
});
|
|
3301
|
+
const result = await compileLesson(makePipeline2Lesson(), 'system prompt', deps);
|
|
3302
|
+
expect(result.status).toBe('compiled');
|
|
3303
|
+
if (result.status === 'compiled') {
|
|
3304
|
+
expect(result.rule.archivedReason).toBeDefined();
|
|
3305
|
+
expect(result.rule.archivedReason).not.toMatch(/\x1b/);
|
|
3306
|
+
// Path payload still present, just stripped of escape bytes.
|
|
3307
|
+
expect(result.rule.archivedReason).toContain('hostile');
|
|
3308
|
+
}
|
|
3309
|
+
});
|
|
3310
|
+
});
|
|
2950
3311
|
//# sourceMappingURL=compile-lesson.test.js.map
|