@mmnto/cli 1.10.0 → 1.10.2

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 (42) hide show
  1. package/LICENSE +190 -190
  2. package/dist/commands/extract-shared.d.ts.map +1 -1
  3. package/dist/commands/extract-shared.js +18 -1
  4. package/dist/commands/extract-shared.js.map +1 -1
  5. package/dist/commands/init-detect.js +4 -4
  6. package/dist/commands/init-detect.js.map +1 -1
  7. package/dist/commands/install-hooks.d.ts.map +1 -1
  8. package/dist/commands/install-hooks.js +28 -19
  9. package/dist/commands/install-hooks.js.map +1 -1
  10. package/dist/commands/install-hooks.test.js +4 -7
  11. package/dist/commands/install-hooks.test.js.map +1 -1
  12. package/dist/commands/review-alias.test.js +9 -15
  13. package/dist/commands/review-alias.test.js.map +1 -1
  14. package/dist/exemptions/__tests__/exemption-engine.test.js +9 -0
  15. package/dist/exemptions/__tests__/exemption-engine.test.js.map +1 -1
  16. package/dist/exemptions/exemption-engine.js +1 -1
  17. package/dist/exemptions/exemption-engine.js.map +1 -1
  18. package/dist/orchestrators/orchestrator.test.js +5 -5
  19. package/dist/orchestrators/orchestrator.test.js.map +1 -1
  20. package/dist/utils.test.js +8 -2
  21. package/dist/utils.test.js.map +1 -1
  22. package/package.json +4 -3
  23. package/dist/commands/anchor.d.ts +0 -2
  24. package/dist/commands/anchor.d.ts.map +0 -1
  25. package/dist/commands/anchor.js +0 -84
  26. package/dist/commands/anchor.js.map +0 -1
  27. package/dist/commands/config-cmd.d.ts +0 -4
  28. package/dist/commands/config-cmd.d.ts.map +0 -1
  29. package/dist/commands/config-cmd.js +0 -60
  30. package/dist/commands/config-cmd.js.map +0 -1
  31. package/dist/commands/learn.d.ts +0 -26
  32. package/dist/commands/learn.d.ts.map +0 -1
  33. package/dist/commands/learn.js +0 -325
  34. package/dist/commands/learn.js.map +0 -1
  35. package/dist/commands/learn.test.d.ts +0 -2
  36. package/dist/commands/learn.test.d.ts.map +0 -1
  37. package/dist/commands/learn.test.js +0 -169
  38. package/dist/commands/learn.test.js.map +0 -1
  39. package/dist/orchestrator.test.d.ts +0 -2
  40. package/dist/orchestrator.test.d.ts.map +0 -1
  41. package/dist/orchestrator.test.js +0 -130
  42. package/dist/orchestrator.test.js.map +0 -1
@@ -1,325 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import * as path from 'node:path';
3
- import * as readline from 'node:readline/promises';
4
- import { createEmbedder, LanceStore, runSync } from '@mmnto/totem';
5
- import { GitHubCliPrAdapter } from '../adapters/github-cli-pr.js';
6
- import { log } from '../ui.js';
7
- import { formatResults, getSystemPrompt, loadConfig, loadEnv, resolveConfigPath, runOrchestrator, sanitize, wrapXml, } from '../utils.js';
8
- // ─── Constants ──────────────────────────────────────────
9
- const TAG = 'Learn';
10
- const MAX_EXISTING_LESSONS = 10;
11
- const MAX_REVIEW_BODY_CHARS = 50_000;
12
- const MAX_INPUTS = 5;
13
- // ─── System prompt ──────────────────────────────────────
14
- const SYSTEM_PROMPT = `# Learn System Prompt — PR Lesson Extraction
15
-
16
- ## Purpose
17
- Extract tactical lessons from a pull request's review comments and discussion.
18
-
19
- ## Role
20
- You are a knowledge curator analyzing a PR's review threads. Your job is to distill non-obvious lessons — traps, patterns, decisions with rationale — that will prevent future mistakes.
21
-
22
- ## Rules
23
- - Extract ONLY non-obvious lessons (traps, surprising behaviors, pattern decisions with rationale)
24
- - Ignore GCA boilerplate, simple acknowledgments, nits, and formatting suggestions
25
- - When a suggestion was DECLINED, the author's rationale is often the most valuable lesson
26
- - Each lesson should be 1-2 sentences capturing WHAT happened and WHY it matters
27
- - Tags should be lowercase, comma-separated, reflecting the technical domain
28
- - If existing lessons are provided, do NOT extract duplicates or near-duplicates
29
- - If no lessons are worth extracting, output exactly: NONE
30
-
31
- ## Output Format
32
- For each lesson, use this exact delimiter format:
33
-
34
- ---LESSON---
35
- Tags: tag1, tag2, tag3
36
- The lesson text. One or two sentences capturing the trap/pattern and WHY it matters.
37
- ---END---
38
-
39
- If no lessons found, output exactly: NONE
40
- `;
41
- function groupIntoThreads(comments) {
42
- const byId = new Map();
43
- for (const c of comments)
44
- byId.set(c.id, c);
45
- const threadMap = new Map();
46
- for (const c of comments) {
47
- const rootId = c.inReplyToId ?? c.id;
48
- const thread = threadMap.get(rootId) ?? [];
49
- thread.push(c);
50
- threadMap.set(rootId, thread);
51
- }
52
- const threads = [];
53
- for (const [rootId, threadComments] of threadMap) {
54
- threadComments.sort((a, b) => {
55
- if (!a.createdAt || !b.createdAt)
56
- return 0;
57
- return a.createdAt.localeCompare(b.createdAt);
58
- });
59
- const root = byId.get(rootId) ?? threadComments[0];
60
- threads.push({
61
- path: root.path,
62
- diffHunk: root.diffHunk,
63
- comments: threadComments.map((c) => ({ author: c.author, body: c.body })),
64
- });
65
- }
66
- return threads;
67
- }
68
- // ─── LanceDB retrieval ─────────────────────────────────
69
- async function retrieveExistingLessons(store) {
70
- return store.search({
71
- query: 'lesson trap pattern decision',
72
- typeFilter: 'spec',
73
- maxResults: MAX_EXISTING_LESSONS,
74
- });
75
- }
76
- // ─── Prompt assembly ────────────────────────────────────
77
- const GCA_MARKERS = ['Using Gemini Code Assist', 'Gemini Code Assist'];
78
- function isGcaBoilerplate(body) {
79
- return GCA_MARKERS.some((marker) => body.includes(marker));
80
- }
81
- function assemblePrompt(pr, threads, existingLessons, systemPrompt) {
82
- const sections = [systemPrompt];
83
- // PR metadata
84
- sections.push('=== PR METADATA ===');
85
- sections.push(`PR #${pr.number}: ${pr.title}`);
86
- sections.push(`State: ${pr.state}`);
87
- if (pr.body) {
88
- sections.push('');
89
- sections.push(wrapXml('pr_body', pr.body));
90
- }
91
- // Review summaries (non-empty review bodies)
92
- const reviewBodies = pr.reviews.filter((r) => r.body.trim());
93
- if (reviewBodies.length > 0) {
94
- sections.push('\n=== REVIEW SUMMARIES ===');
95
- for (const r of reviewBodies) {
96
- sections.push(`[${r.author} — ${r.state}]`);
97
- sections.push(wrapXml('review_body', r.body));
98
- sections.push('');
99
- }
100
- }
101
- // Regular PR comments (filter GCA boilerplate)
102
- const prComments = pr.comments.filter((c) => !isGcaBoilerplate(c.body));
103
- if (prComments.length > 0) {
104
- sections.push('\n=== PR COMMENTS ===');
105
- for (const c of prComments) {
106
- sections.push(`[${c.author}]`);
107
- sections.push(wrapXml('comment_body', c.body));
108
- sections.push('');
109
- }
110
- }
111
- // Inline review comment threads
112
- if (threads.length > 0) {
113
- sections.push('\n=== INLINE REVIEW THREADS ===');
114
- for (const thread of threads) {
115
- sections.push(`--- ${thread.path} ---`);
116
- sections.push(wrapXml('diff_hunk', thread.diffHunk));
117
- for (const c of thread.comments) {
118
- sections.push(`[${c.author}]:\n${wrapXml('comment_body', c.body)}`);
119
- }
120
- sections.push('');
121
- }
122
- }
123
- // Existing lessons for dedup context
124
- const lessonSection = formatResults(existingLessons, 'EXISTING LESSONS (do NOT duplicate)');
125
- if (lessonSection) {
126
- sections.push('\n=== DEDUP CONTEXT ===');
127
- sections.push(lessonSection);
128
- }
129
- // Truncate if needed
130
- let prompt = sections.join('\n');
131
- if (prompt.length > MAX_REVIEW_BODY_CHARS) {
132
- prompt = prompt.slice(0, MAX_REVIEW_BODY_CHARS) + '\n\n... [content truncated] ...';
133
- }
134
- return prompt;
135
- }
136
- const LESSON_RE = /---LESSON---\s*\nTags:\s*(.+)\n([\s\S]+?)---END---/g;
137
- export function parseLessons(llmOutput) {
138
- if (llmOutput.trim() === 'NONE')
139
- return [];
140
- const lessons = [];
141
- let match;
142
- while ((match = LESSON_RE.exec(llmOutput)) !== null) {
143
- const tags = match[1]
144
- .split(',')
145
- .map((t) => t.trim())
146
- .filter(Boolean);
147
- const text = match[2].trim();
148
- if (text) {
149
- lessons.push({ tags, text });
150
- }
151
- }
152
- return lessons;
153
- }
154
- // ─── Lesson writer ──────────────────────────────────────
155
- export function appendLessons(lessons, lessonsPath) {
156
- const dir = path.dirname(lessonsPath);
157
- if (!fs.existsSync(dir)) {
158
- fs.mkdirSync(dir, { recursive: true });
159
- }
160
- const entries = lessons
161
- .map((l) => {
162
- const timestamp = new Date().toISOString();
163
- const tags = l.tags.join(', ');
164
- return `\n## Lesson — ${timestamp}\n\n**Tags:** ${tags}\n\n${l.text}\n`;
165
- })
166
- .join('');
167
- fs.appendFileSync(lessonsPath, entries, 'utf-8');
168
- }
169
- // ─── Confirmation gate ──────────────────────────────────
170
- /**
171
- * Returns true if lessons should be written, false to abort.
172
- * Throws in non-interactive environments without --yes.
173
- */
174
- export async function confirmLessons(count, opts) {
175
- if (opts.yes)
176
- return true;
177
- if (!opts.isTTY) {
178
- throw new Error(`[Totem Error] Refusing to write lessons in non-interactive mode. Use --yes to bypass confirmation.`);
179
- }
180
- const rl = readline.createInterface({
181
- input: opts.input ?? process.stdin,
182
- output: opts.output ?? process.stderr,
183
- });
184
- try {
185
- const answer = await rl.question(`[${TAG}] Write ${count} lesson(s) to lessons.md? [Y/n] `);
186
- if (answer.trim().toLowerCase() === 'n') {
187
- return false;
188
- }
189
- return true;
190
- }
191
- finally {
192
- rl.close();
193
- }
194
- }
195
- export async function learnCommand(prNumbers, options) {
196
- // Validate and deduplicate PR numbers
197
- const unique = [...new Set(prNumbers)];
198
- if (unique.length > MAX_INPUTS) {
199
- throw new Error(`[Totem Error] Too many PR numbers (${unique.length}). Maximum is ${MAX_INPUTS}.`);
200
- }
201
- const nums = [];
202
- for (const prNumber of unique) {
203
- const num = parseInt(prNumber, 10);
204
- if (isNaN(num) || num <= 0) {
205
- throw new Error(`[Totem Error] Invalid PR number: '${prNumber}'. Must be a positive integer.`);
206
- }
207
- nums.push(num);
208
- }
209
- const cwd = process.cwd();
210
- const configPath = resolveConfigPath(cwd);
211
- loadEnv(cwd);
212
- const config = await loadConfig(configPath);
213
- // Connect to LanceDB for dedup context
214
- const embedder = createEmbedder(config.embedding);
215
- const store = new LanceStore(path.join(cwd, config.lanceDir), embedder);
216
- await store.connect();
217
- log.info(TAG, 'Querying existing lessons for dedup...');
218
- const existingLessons = await retrieveExistingLessons(store);
219
- log.info(TAG, `Found ${existingLessons.length} existing lessons for context`);
220
- // Resolve system prompt (allow .totem/prompts/learn.md override)
221
- const systemPrompt = getSystemPrompt('learn', SYSTEM_PROMPT, cwd, config.totemDir);
222
- // Process each PR sequentially, accumulating lessons
223
- const allLessons = [];
224
- const adapter = new GitHubCliPrAdapter(cwd);
225
- for (const num of nums) {
226
- // Fetch PR data
227
- log.info(TAG, `Fetching PR #${num}...`);
228
- const pr = adapter.fetchPr(num);
229
- log.info(TAG, `Title: ${pr.title}`);
230
- // Fetch inline review comments
231
- log.info(TAG, 'Fetching review comments...');
232
- const reviewComments = adapter.fetchReviewComments(num);
233
- log.info(TAG, `Found ${reviewComments.length} inline review comments`);
234
- // Filter GCA boilerplate from inline comments
235
- const filteredComments = reviewComments.filter((c) => !isGcaBoilerplate(c.body));
236
- // Skip if no review content
237
- const hasReviewContent = pr.reviews.some((r) => r.body.trim()) ||
238
- pr.comments.some((c) => !isGcaBoilerplate(c.body)) ||
239
- filteredComments.length > 0;
240
- if (!hasReviewContent) {
241
- log.dim(TAG, `No review content found in PR #${num}. Skipping.`);
242
- continue;
243
- }
244
- // Group inline comments into threads
245
- const threads = groupIntoThreads(filteredComments);
246
- log.info(TAG, `Grouped into ${threads.length} review threads`);
247
- // Assemble prompt
248
- const prompt = assemblePrompt(pr, threads, existingLessons, systemPrompt);
249
- log.dim(TAG, `Prompt: ${(prompt.length / 1024).toFixed(0)}KB`);
250
- // Run orchestrator (handles --raw mode, validation, invocation, telemetry)
251
- const content = runOrchestrator({ prompt, tag: TAG, options, config, cwd });
252
- if (content == null)
253
- continue; // --raw mode — prompt already output, process next PR
254
- // Parse lessons from LLM output
255
- const lessons = parseLessons(content);
256
- if (lessons.length === 0) {
257
- log.dim(TAG, `No lessons extracted from PR #${num}.`);
258
- }
259
- else {
260
- log.success(TAG, `Extracted ${lessons.length} lesson(s) from PR #${num}`);
261
- allLessons.push(...lessons);
262
- }
263
- }
264
- // In --raw mode, prompts were already output during the loop
265
- if (options.raw)
266
- return;
267
- if (allLessons.length === 0) {
268
- log.dim(TAG, 'No lessons extracted from any PR.');
269
- return;
270
- }
271
- log.success(TAG, `Total: ${allLessons.length} lesson(s) from ${nums.length} PR(s)`);
272
- // Display extracted lessons for review
273
- console.error('');
274
- log.warn(TAG, 'WARNING: These lessons were extracted from PR comments, which may include content from untrusted contributors.');
275
- log.warn(TAG, 'Review each lesson carefully before accepting.\n');
276
- for (let i = 0; i < allLessons.length; i++) {
277
- const lesson = allLessons[i];
278
- console.error(` [${i + 1}] Tags: ${sanitize(lesson.tags.join(', ')).replace(/\n/g, ' ')}`);
279
- console.error(` ${sanitize(lesson.text).replace(/\n/g, '\n ')}`);
280
- console.error('');
281
- }
282
- // --dry-run mode: preview lessons to stdout (pipeable) without writing
283
- if (options.dryRun) {
284
- log.dim(TAG, 'Dry run — lessons not written.');
285
- for (const lesson of allLessons) {
286
- console.log(`\n Tags: ${sanitize(lesson.tags.join(', ')).replace(/\n/g, ' ')}`);
287
- console.log(` ${sanitize(lesson.text).replace(/\n/g, '\n ')}`);
288
- }
289
- return;
290
- }
291
- // Confirmation gate
292
- const confirmed = await confirmLessons(allLessons.length, {
293
- yes: options.yes,
294
- isTTY: !!process.stdin.isTTY,
295
- });
296
- if (!confirmed) {
297
- log.dim(TAG, 'Aborted — no lessons written.');
298
- return;
299
- }
300
- // Sanitize before persisting — strip any terminal injection from stored lessons
301
- const sanitizedLessons = allLessons.map((l) => ({
302
- tags: l.tags.map((t) => sanitize(t)),
303
- text: sanitize(l.text),
304
- }));
305
- // Append lessons to .totem/lessons.md
306
- const lessonsPath = path.join(cwd, config.totemDir, 'lessons.md');
307
- appendLessons(sanitizedLessons, lessonsPath);
308
- log.success(TAG, `Appended ${allLessons.length} lesson(s) to ${config.totemDir}/lessons.md`);
309
- // Run incremental sync so lessons are immediately searchable
310
- log.info(TAG, 'Running incremental sync...');
311
- const syncResult = await runSync(config, {
312
- projectRoot: cwd,
313
- incremental: true,
314
- onProgress: (msg) => log.dim(TAG, msg),
315
- });
316
- log.success(TAG, `Sync complete: ${syncResult.chunksProcessed} chunks from ${syncResult.filesProcessed} files`);
317
- // Print summary
318
- const prLabel = nums.length === 1 ? `PR #${nums[0]}` : `${nums.length} PRs`;
319
- console.log(`\nExtracted ${sanitizedLessons.length} lesson(s) from ${prLabel}:`);
320
- for (const lesson of sanitizedLessons) {
321
- console.log(`\n Tags: ${lesson.tags.join(', ').replace(/\n/g, ' ')}`);
322
- console.log(` ${lesson.text.replace(/\n/g, '\n ')}`);
323
- }
324
- }
325
- //# sourceMappingURL=learn.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"learn.js","sourceRoot":"","sources":["../../src/commands/learn.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=learn.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"learn.test.d.ts","sourceRoot":"","sources":["../../src/commands/learn.test.ts"],"names":[],"mappings":""}
@@ -1,169 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import * as os from 'node:os';
3
- import * as path from 'node:path';
4
- import { Readable, Writable } from 'node:stream';
5
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
- import { appendLessons, confirmLessons, parseLessons } from './learn.js';
7
- // ─── parseLessons ───────────────────────────────────────
8
- describe('parseLessons', () => {
9
- it('extracts a single lesson', () => {
10
- const output = `---LESSON---
11
- Tags: git, cli, trap
12
- Always check for ENOENT separately from other errors.
13
- ---END---`;
14
- const lessons = parseLessons(output);
15
- expect(lessons).toHaveLength(1);
16
- expect(lessons[0]).toEqual({
17
- tags: ['git', 'cli', 'trap'],
18
- text: 'Always check for ENOENT separately from other errors.',
19
- });
20
- });
21
- it('extracts multiple lessons', () => {
22
- const output = `---LESSON---
23
- Tags: adapter, DRY
24
- Extract shared fetch logic into a helper immediately.
25
- ---END---
26
-
27
- ---LESSON---
28
- Tags: security, input
29
- Sanitize all user input before writing to files.
30
- ---END---`;
31
- const lessons = parseLessons(output);
32
- expect(lessons).toHaveLength(2);
33
- expect(lessons[0].tags).toEqual(['adapter', 'DRY']);
34
- expect(lessons[1].tags).toEqual(['security', 'input']);
35
- });
36
- it('returns empty array for NONE', () => {
37
- expect(parseLessons('NONE')).toEqual([]);
38
- });
39
- it('returns empty array for NONE with whitespace', () => {
40
- expect(parseLessons(' NONE ')).toEqual([]);
41
- });
42
- it('handles multi-line lesson text', () => {
43
- const output = `---LESSON---
44
- Tags: architecture
45
- First line of the lesson.
46
- Second line with more detail.
47
- ---END---`;
48
- const lessons = parseLessons(output);
49
- expect(lessons).toHaveLength(1);
50
- expect(lessons[0].text).toBe('First line of the lesson.\nSecond line with more detail.');
51
- });
52
- it('strips empty tags', () => {
53
- const output = `---LESSON---
54
- Tags: git, , cli,
55
- A lesson about git.
56
- ---END---`;
57
- const lessons = parseLessons(output);
58
- expect(lessons[0].tags).toEqual(['git', 'cli']);
59
- });
60
- it('skips lessons with empty text', () => {
61
- const output = `---LESSON---
62
- Tags: empty
63
- ---END---`;
64
- const lessons = parseLessons(output);
65
- expect(lessons).toEqual([]);
66
- });
67
- });
68
- // sanitize tests are in utils.test.ts (sanitize now lives in utils.ts)
69
- // ─── appendLessons ──────────────────────────────────────
70
- describe('appendLessons', () => {
71
- let tmpDir;
72
- let lessonsPath;
73
- beforeEach(() => {
74
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-learn-'));
75
- lessonsPath = path.join(tmpDir, '.totem', 'lessons.md');
76
- });
77
- afterEach(() => {
78
- fs.rmSync(tmpDir, { recursive: true, force: true });
79
- });
80
- it('creates directory and file if they do not exist', () => {
81
- appendLessons([{ tags: ['test'], text: 'A test lesson.' }], lessonsPath);
82
- expect(fs.existsSync(lessonsPath)).toBe(true);
83
- const content = fs.readFileSync(lessonsPath, 'utf-8');
84
- expect(content).toContain('**Tags:** test');
85
- expect(content).toContain('A test lesson.');
86
- });
87
- it('appends to existing file', () => {
88
- fs.mkdirSync(path.dirname(lessonsPath), { recursive: true });
89
- fs.writeFileSync(lessonsPath, '# Existing content\n');
90
- appendLessons([{ tags: ['new'], text: 'New lesson.' }], lessonsPath);
91
- const content = fs.readFileSync(lessonsPath, 'utf-8');
92
- expect(content).toContain('# Existing content');
93
- expect(content).toContain('New lesson.');
94
- });
95
- it('writes multiple lessons', () => {
96
- appendLessons([
97
- { tags: ['a', 'b'], text: 'First.' },
98
- { tags: ['c'], text: 'Second.' },
99
- ], lessonsPath);
100
- const content = fs.readFileSync(lessonsPath, 'utf-8');
101
- expect(content).toContain('**Tags:** a, b');
102
- expect(content).toContain('First.');
103
- expect(content).toContain('**Tags:** c');
104
- expect(content).toContain('Second.');
105
- });
106
- it('includes ISO timestamp in heading', () => {
107
- appendLessons([{ tags: ['test'], text: 'Timestamped.' }], lessonsPath);
108
- const content = fs.readFileSync(lessonsPath, 'utf-8');
109
- // Match ISO 8601 pattern in heading
110
- expect(content).toMatch(/## Lesson — \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
111
- });
112
- });
113
- // ─── confirmLessons ─────────────────────────────────────
114
- function makeInput(response) {
115
- const input = new Readable({ read() { } });
116
- // Push response after a tick so readline can consume it
117
- setImmediate(() => {
118
- input.push(response + '\n');
119
- input.push(null);
120
- });
121
- return input;
122
- }
123
- const nullOutput = new Writable({
124
- write(_chunk, _enc, cb) {
125
- cb();
126
- },
127
- });
128
- describe('confirmLessons', () => {
129
- it('returns true when --yes is set', async () => {
130
- const result = await confirmLessons(3, { yes: true, isTTY: false });
131
- expect(result).toBe(true);
132
- });
133
- it('throws in non-TTY without --yes', async () => {
134
- await expect(confirmLessons(3, { isTTY: false })).rejects.toThrow('[Totem Error] Refusing to write lessons in non-interactive mode. Use --yes to bypass confirmation.');
135
- });
136
- it('returns true when user confirms with empty input (default Y)', async () => {
137
- const result = await confirmLessons(2, {
138
- isTTY: true,
139
- input: makeInput(''),
140
- output: nullOutput,
141
- });
142
- expect(result).toBe(true);
143
- });
144
- it('returns true when user types y', async () => {
145
- const result = await confirmLessons(2, {
146
- isTTY: true,
147
- input: makeInput('y'),
148
- output: nullOutput,
149
- });
150
- expect(result).toBe(true);
151
- });
152
- it('returns false when user types n', async () => {
153
- const result = await confirmLessons(2, {
154
- isTTY: true,
155
- input: makeInput('n'),
156
- output: nullOutput,
157
- });
158
- expect(result).toBe(false);
159
- });
160
- it('returns false when user types N', async () => {
161
- const result = await confirmLessons(1, {
162
- isTTY: true,
163
- input: makeInput('N'),
164
- output: nullOutput,
165
- });
166
- expect(result).toBe(false);
167
- });
168
- });
169
- //# sourceMappingURL=learn.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"learn.test.js","sourceRoot":"","sources":["../../src/commands/learn.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=orchestrator.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"orchestrator.test.d.ts","sourceRoot":"","sources":["../src/orchestrator.test.ts"],"names":[],"mappings":""}