@pennyfarthing/core 7.7.0 → 7.8.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.
Files changed (56) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/packages/core/dist/cli/commands/doctor.js +114 -0
  5. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  6. package/pennyfarthing-dist/agents/sm-setup.md +37 -2
  7. package/pennyfarthing-dist/agents/sm.md +68 -22
  8. package/pennyfarthing-dist/agents/workflow-status-check.md +11 -1
  9. package/pennyfarthing-dist/commands/git-cleanup.md +43 -308
  10. package/pennyfarthing-dist/commands/solo.md +31 -0
  11. package/pennyfarthing-dist/guides/patterns/approval-gates-pattern.md +1 -1
  12. package/pennyfarthing-dist/personas/themes/gilligans-island.yaml +83 -83
  13. package/pennyfarthing-dist/personas/themes/the-expanse.yaml +11 -11
  14. package/pennyfarthing-dist/scripts/core/check-context.sh +3 -0
  15. package/pennyfarthing-dist/scripts/core/handoff-marker.sh +13 -2
  16. package/pennyfarthing-dist/scripts/core/prime.sh +3 -157
  17. package/pennyfarthing-dist/scripts/core/run.sh +9 -0
  18. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  19. package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +117 -20
  20. package/pennyfarthing-dist/scripts/jira/README.md +10 -7
  21. package/pennyfarthing-dist/scripts/misc/add-short-names.sh +13 -0
  22. package/pennyfarthing-dist/scripts/misc/add_short_names.py +226 -0
  23. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +6 -5
  24. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +319 -0
  25. package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.sh +6 -5
  26. package/pennyfarthing-dist/scripts/sprint/import_epic_to_future.py +270 -0
  27. package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +59 -0
  28. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +8 -6
  29. package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +402 -0
  30. package/pennyfarthing-dist/scripts/workflow/check.sh +3 -476
  31. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +61 -0
  32. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +13 -0
  33. package/pennyfarthing-dist/skills/judge/SKILL.md +57 -0
  34. package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +4 -22
  35. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +83 -0
  36. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-02-categorize.md +116 -0
  37. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-03-execute.md +210 -0
  38. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +88 -0
  39. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +71 -0
  40. package/pennyfarthing-dist/workflows/git-cleanup.yaml +59 -0
  41. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.mjs +0 -393
  42. package/pennyfarthing-dist/scripts/hooks/tests/question-reflector.test.mjs +0 -545
  43. package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.mjs +0 -327
  44. package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.test.mjs +0 -503
  45. package/pennyfarthing-dist/scripts/jira/jira-lib.mjs +0 -443
  46. package/pennyfarthing-dist/scripts/jira/jira-sync-story.mjs +0 -208
  47. package/pennyfarthing-dist/scripts/jira/jira-sync.mjs +0 -198
  48. package/pennyfarthing-dist/scripts/misc/add-short-names.mjs +0 -264
  49. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.mjs +0 -474
  50. package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.mjs +0 -377
  51. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.js +0 -492
  52. /package/pennyfarthing-dist/guides/{AGENT-COORDINATION.md → agent-coordination.md} +0 -0
  53. /package/pennyfarthing-dist/guides/{HOOKS.md → hooks.md} +0 -0
  54. /package/pennyfarthing-dist/guides/{PROMPT-PATTERNS.md → prompt-patterns.md} +0 -0
  55. /package/pennyfarthing-dist/guides/{SESSION-ARTIFACTS.md → session-artifacts.md} +0 -0
  56. /package/pennyfarthing-dist/guides/{XML-TAGS.md → xml-tags.md} +0 -0
@@ -1,545 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * question-reflector.test.mjs - Tests for question reflector enforcement hook
4
- *
5
- * Story: MSSCI-12393
6
- * TDD Phase: RED
7
- *
8
- * Run with: node --test pennyfarthing-dist/scripts/hooks/tests/question-reflector.test.mjs
9
- */
10
-
11
- import { describe, it, beforeEach, mock } from 'node:test';
12
- import assert from 'node:assert';
13
-
14
- // The module under test
15
- import {
16
- detectQuestion,
17
- hasReflectorMarker,
18
- shouldSkipEnforcement,
19
- extractLastAssistantMessage,
20
- checkQuestionReflector,
21
- checkAskUserQuestion,
22
- } from '../question-reflector-check.mjs';
23
-
24
- // =============================================================================
25
- // Test Fixtures
26
- // =============================================================================
27
-
28
- const MARKERS = {
29
- yesno: '<!-- CYCLIST:QUESTION:yesno -->',
30
- open: '<!-- CYCLIST:QUESTION:open -->',
31
- choices: '<!-- CYCLIST:CHOICES:option1,option2,option3 -->',
32
- };
33
-
34
- const CONFIG_MANUAL = {
35
- workflow: { permission_mode: 'manual' },
36
- };
37
-
38
- const CONFIG_ACCEPT = {
39
- workflow: { permission_mode: 'accept' },
40
- };
41
-
42
- const CONFIG_TURBO_LEGACY = {
43
- workflow: { permission_mode: 'turbo' },
44
- };
45
-
46
- const CONFIG_RELAY_ON = {
47
- workflow: { permission_mode: 'accept', relay_mode: true },
48
- };
49
-
50
- const CONFIG_RELAY_OFF = {
51
- workflow: { permission_mode: 'accept', relay_mode: false },
52
- };
53
-
54
- // =============================================================================
55
- // Relay/Turbo Mode Bypass Tests
56
- // =============================================================================
57
-
58
- describe('shouldSkipEnforcement', () => {
59
- it('should skip enforcement when permission_mode is turbo (legacy)', () => {
60
- const result = shouldSkipEnforcement(CONFIG_TURBO_LEGACY);
61
- assert.strictEqual(result, true);
62
- });
63
-
64
- it('should skip enforcement when relay_mode is true', () => {
65
- const result = shouldSkipEnforcement(CONFIG_RELAY_ON);
66
- assert.strictEqual(result, true);
67
- });
68
-
69
- it('should NOT skip enforcement when relay_mode is false', () => {
70
- const result = shouldSkipEnforcement(CONFIG_RELAY_OFF);
71
- assert.strictEqual(result, false);
72
- });
73
-
74
- it('should NOT skip enforcement in manual mode without relay', () => {
75
- const result = shouldSkipEnforcement(CONFIG_MANUAL);
76
- assert.strictEqual(result, false);
77
- });
78
-
79
- it('should NOT skip enforcement in accept mode without relay', () => {
80
- const result = shouldSkipEnforcement(CONFIG_ACCEPT);
81
- assert.strictEqual(result, false);
82
- });
83
- });
84
-
85
- // =============================================================================
86
- // Question Detection Tests - Direct Questions
87
- // =============================================================================
88
-
89
- describe('detectQuestion - direct questions', () => {
90
- it('should detect question mark at end of message', () => {
91
- const msg = 'What would you like me to do?';
92
- const result = detectQuestion(msg);
93
- assert.strictEqual(result.detected, true);
94
- assert.strictEqual(result.type, 'direct');
95
- });
96
-
97
- it('should detect question mark mid-message followed by new sentence', () => {
98
- const msg = 'What do you need? I can help with several things.';
99
- const result = detectQuestion(msg);
100
- assert.strictEqual(result.detected, true);
101
- assert.strictEqual(result.type, 'direct');
102
- });
103
-
104
- it('should detect question mark followed by newline and more content', () => {
105
- const msg = 'What do you need?\n\nHere are your options:';
106
- const result = detectQuestion(msg);
107
- assert.strictEqual(result.detected, true);
108
- assert.strictEqual(result.type, 'direct');
109
- });
110
-
111
- it('should detect multiple questions in message', () => {
112
- const msg = 'Should I proceed? Or would you prefer a different approach?';
113
- const result = detectQuestion(msg);
114
- assert.strictEqual(result.detected, true);
115
- });
116
- });
117
-
118
- // =============================================================================
119
- // Question Detection Tests - Implicit Questions
120
- // =============================================================================
121
-
122
- describe('detectQuestion - implicit questions', () => {
123
- it('should detect "would you like"', () => {
124
- const msg = 'I can fix this. Would you like me to proceed.';
125
- const result = detectQuestion(msg);
126
- assert.strictEqual(result.detected, true);
127
- assert.strictEqual(result.type, 'implicit');
128
- });
129
-
130
- it('should detect "should I"', () => {
131
- const msg = 'The tests are passing. Should I commit the changes.';
132
- const result = detectQuestion(msg);
133
- assert.strictEqual(result.detected, true);
134
- assert.strictEqual(result.type, 'implicit');
135
- });
136
-
137
- it('should detect "do you want"', () => {
138
- const msg = 'Do you want me to run the full test suite.';
139
- const result = detectQuestion(msg);
140
- assert.strictEqual(result.detected, true);
141
- assert.strictEqual(result.type, 'implicit');
142
- });
143
-
144
- it('should detect "let me know if"', () => {
145
- const msg = 'I made the changes. Let me know if you need anything else.';
146
- const result = detectQuestion(msg);
147
- assert.strictEqual(result.detected, true);
148
- assert.strictEqual(result.type, 'implicit');
149
- });
150
-
151
- it('should detect "what do you think"', () => {
152
- const msg = 'Here is my proposed solution. What do you think.';
153
- const result = detectQuestion(msg);
154
- assert.strictEqual(result.detected, true);
155
- assert.strictEqual(result.type, 'implicit');
156
- });
157
-
158
- it('should detect "your preference"', () => {
159
- const msg = 'Both approaches work. Your preference on which to use.';
160
- const result = detectQuestion(msg);
161
- assert.strictEqual(result.detected, true);
162
- assert.strictEqual(result.type, 'implicit');
163
- });
164
-
165
- it('should detect "ready to proceed"', () => {
166
- const msg = 'Everything is set up. Ready to proceed with the deployment.';
167
- const result = detectQuestion(msg);
168
- assert.strictEqual(result.detected, true);
169
- assert.strictEqual(result.type, 'implicit');
170
- });
171
- });
172
-
173
- // =============================================================================
174
- // Question Detection Tests - Choice Offerings
175
- // =============================================================================
176
-
177
- describe('detectQuestion - choice offerings', () => {
178
- it('should detect "option A" style choices', () => {
179
- const msg = 'We have two paths: Option A uses Redis, Option B uses memory.';
180
- const result = detectQuestion(msg);
181
- assert.strictEqual(result.detected, true);
182
- assert.strictEqual(result.type, 'choices');
183
- });
184
-
185
- it('should detect "we could either"', () => {
186
- const msg = 'We could either refactor now or defer to next sprint.';
187
- const result = detectQuestion(msg);
188
- assert.strictEqual(result.detected, true);
189
- assert.strictEqual(result.type, 'choices');
190
- });
191
-
192
- it('should detect "alternatively"', () => {
193
- const msg = 'I can add it inline. Alternatively, we create a helper function.';
194
- const result = detectQuestion(msg);
195
- assert.strictEqual(result.detected, true);
196
- assert.strictEqual(result.type, 'choices');
197
- });
198
-
199
- it('should detect "or would you prefer"', () => {
200
- const msg = 'I can use async/await, or would you prefer callbacks.';
201
- const result = detectQuestion(msg);
202
- assert.strictEqual(result.detected, true);
203
- assert.strictEqual(result.type, 'choices');
204
- });
205
-
206
- it('should detect "choose between"', () => {
207
- const msg = 'You can choose between TypeScript or JavaScript.';
208
- const result = detectQuestion(msg);
209
- assert.strictEqual(result.detected, true);
210
- assert.strictEqual(result.type, 'choices');
211
- });
212
- });
213
-
214
- // =============================================================================
215
- // Marker Detection Tests
216
- // =============================================================================
217
-
218
- describe('hasReflectorMarker', () => {
219
- it('should detect QUESTION:yesno marker', () => {
220
- const msg = `${MARKERS.yesno}\nShould I proceed?`;
221
- const result = hasReflectorMarker(msg);
222
- assert.strictEqual(result, true);
223
- });
224
-
225
- it('should detect QUESTION:open marker', () => {
226
- const msg = `${MARKERS.open}\nWhat approach would you prefer?`;
227
- const result = hasReflectorMarker(msg);
228
- assert.strictEqual(result, true);
229
- });
230
-
231
- it('should detect CHOICES marker', () => {
232
- const msg = `${MARKERS.choices}\nOption 1 or Option 2?`;
233
- const result = hasReflectorMarker(msg);
234
- assert.strictEqual(result, true);
235
- });
236
-
237
- it('should detect marker with extra whitespace', () => {
238
- const msg = '<!-- CYCLIST:QUESTION:yesno -->\nShould I proceed?';
239
- const result = hasReflectorMarker(msg);
240
- assert.strictEqual(result, true);
241
- });
242
-
243
- it('should return false when no marker present', () => {
244
- const msg = 'Should I proceed?';
245
- const result = hasReflectorMarker(msg);
246
- assert.strictEqual(result, false);
247
- });
248
- });
249
-
250
- // =============================================================================
251
- // False Positive Prevention Tests - Code Blocks
252
- // =============================================================================
253
-
254
- describe('detectQuestion - code block immunity', () => {
255
- it('should NOT detect questions inside fenced code blocks', () => {
256
- const msg = `Here is the code:
257
- \`\`\`javascript
258
- // What should this function return?
259
- function test() { return true; }
260
- \`\`\`
261
- The implementation is complete.`;
262
- const result = detectQuestion(msg);
263
- assert.strictEqual(result.detected, false);
264
- });
265
-
266
- it('should NOT detect questions inside inline code', () => {
267
- const msg = 'The function `shouldIProceed()` returns a boolean.';
268
- const result = detectQuestion(msg);
269
- assert.strictEqual(result.detected, false);
270
- });
271
-
272
- it('should detect questions OUTSIDE code blocks', () => {
273
- const msg = `Here is the code:
274
- \`\`\`javascript
275
- function test() { return true; }
276
- \`\`\`
277
- What do you think?`;
278
- const result = detectQuestion(msg);
279
- assert.strictEqual(result.detected, true);
280
- });
281
-
282
- it('should handle multiple code blocks correctly', () => {
283
- const msg = `First block:
284
- \`\`\`
285
- code here?
286
- \`\`\`
287
- Second block:
288
- \`\`\`
289
- more code?
290
- \`\`\`
291
- Done.`;
292
- const result = detectQuestion(msg);
293
- assert.strictEqual(result.detected, false);
294
- });
295
- });
296
-
297
- // =============================================================================
298
- // False Positive Prevention Tests - Rhetorical Questions
299
- // =============================================================================
300
-
301
- describe('detectQuestion - rhetorical question immunity', () => {
302
- it('should NOT detect "the question was"', () => {
303
- const msg = 'The question was whether to use async or sync. I chose async.';
304
- const result = detectQuestion(msg);
305
- assert.strictEqual(result.detected, false);
306
- });
307
-
308
- it('should NOT detect "the question is"', () => {
309
- const msg = 'The question is rhetorical. Here is the answer.';
310
- const result = detectQuestion(msg);
311
- assert.strictEqual(result.detected, false);
312
- });
313
-
314
- it('should NOT detect "asked whether"', () => {
315
- const msg = 'You asked whether this was possible. Yes, it is.';
316
- const result = detectQuestion(msg);
317
- assert.strictEqual(result.detected, false);
318
- });
319
-
320
- it('should NOT detect "wondering if"', () => {
321
- const msg = 'I was wondering if this approach would work. It does.';
322
- const result = detectQuestion(msg);
323
- assert.strictEqual(result.detected, false);
324
- });
325
- });
326
-
327
- // =============================================================================
328
- // No Question Tests
329
- // =============================================================================
330
-
331
- describe('detectQuestion - no question present', () => {
332
- it('should NOT detect statements without questions', () => {
333
- const msg = 'I have completed the implementation. All tests pass.';
334
- const result = detectQuestion(msg);
335
- assert.strictEqual(result.detected, false);
336
- });
337
-
338
- it('should NOT detect exclamations', () => {
339
- const msg = 'Done! The feature is ready.';
340
- const result = detectQuestion(msg);
341
- assert.strictEqual(result.detected, false);
342
- });
343
-
344
- it('should NOT detect periods only', () => {
345
- const msg = 'Task complete. Moving on to the next item.';
346
- const result = detectQuestion(msg);
347
- assert.strictEqual(result.detected, false);
348
- });
349
- });
350
-
351
- // =============================================================================
352
- // Transcript Extraction Tests
353
- // =============================================================================
354
-
355
- describe('extractLastAssistantMessage', () => {
356
- it('should extract last assistant message from JSONL', () => {
357
- const transcript = [
358
- { role: 'user', content: 'Hello' },
359
- { role: 'assistant', content: 'Hi there!' },
360
- { role: 'user', content: 'Help me' },
361
- { role: 'assistant', content: 'What do you need?' },
362
- ];
363
- const result = extractLastAssistantMessage(transcript);
364
- assert.strictEqual(result, 'What do you need?');
365
- });
366
-
367
- it('should handle content as array of text blocks', () => {
368
- const transcript = [
369
- {
370
- role: 'assistant',
371
- content: [
372
- { type: 'text', text: 'First part. ' },
373
- { type: 'text', text: 'What do you think?' },
374
- ],
375
- },
376
- ];
377
- const result = extractLastAssistantMessage(transcript);
378
- assert.strictEqual(result, 'First part. What do you think?');
379
- });
380
-
381
- it('should return empty string when no assistant message', () => {
382
- const transcript = [{ role: 'user', content: 'Hello' }];
383
- const result = extractLastAssistantMessage(transcript);
384
- assert.strictEqual(result, '');
385
- });
386
-
387
- it('should skip tool_use content blocks', () => {
388
- const transcript = [
389
- {
390
- role: 'assistant',
391
- content: [
392
- { type: 'text', text: 'Let me check.' },
393
- { type: 'tool_use', id: '123', name: 'Read' },
394
- ],
395
- },
396
- ];
397
- const result = extractLastAssistantMessage(transcript);
398
- assert.strictEqual(result, 'Let me check.');
399
- });
400
- });
401
-
402
- // =============================================================================
403
- // Integration Tests - Full Hook Logic
404
- // =============================================================================
405
-
406
- describe('checkQuestionReflector - integration', () => {
407
- it('should return ok:true when no question detected', () => {
408
- const input = {
409
- transcript_path: '/tmp/test.jsonl',
410
- stop_hook_active: false,
411
- };
412
- const config = CONFIG_MANUAL;
413
- const lastMessage = 'Task complete. All done.';
414
- const result = checkQuestionReflector(input, config, lastMessage);
415
- assert.deepStrictEqual(result, { ok: true });
416
- });
417
-
418
- it('should return ok:true when question has marker', () => {
419
- const input = {
420
- transcript_path: '/tmp/test.jsonl',
421
- stop_hook_active: false,
422
- };
423
- const config = CONFIG_MANUAL;
424
- const lastMessage = `${MARKERS.yesno}\nShould I proceed?`;
425
- const result = checkQuestionReflector(input, config, lastMessage);
426
- assert.deepStrictEqual(result, { ok: true });
427
- });
428
-
429
- it('should return ok:true when in turbo mode (legacy)', () => {
430
- const input = {
431
- transcript_path: '/tmp/test.jsonl',
432
- stop_hook_active: false,
433
- };
434
- const config = CONFIG_TURBO_LEGACY;
435
- const lastMessage = 'What do you need?'; // No marker, but turbo mode
436
- const result = checkQuestionReflector(input, config, lastMessage);
437
- assert.deepStrictEqual(result, { ok: true });
438
- });
439
-
440
- it('should return ok:true when relay_mode is true', () => {
441
- const input = {
442
- transcript_path: '/tmp/test.jsonl',
443
- stop_hook_active: false,
444
- };
445
- const config = CONFIG_RELAY_ON;
446
- const lastMessage = 'What do you need?'; // No marker, but relay on
447
- const result = checkQuestionReflector(input, config, lastMessage);
448
- assert.deepStrictEqual(result, { ok: true });
449
- });
450
-
451
- it('should return ok:true when stop_hook_active is true', () => {
452
- const input = {
453
- transcript_path: '/tmp/test.jsonl',
454
- stop_hook_active: true, // Prevent infinite loops
455
- };
456
- const config = CONFIG_MANUAL;
457
- const lastMessage = 'What do you need?';
458
- const result = checkQuestionReflector(input, config, lastMessage);
459
- assert.deepStrictEqual(result, { ok: true });
460
- });
461
-
462
- it('should block when question detected without marker', () => {
463
- const input = {
464
- transcript_path: '/tmp/test.jsonl',
465
- stop_hook_active: false,
466
- };
467
- const config = CONFIG_MANUAL;
468
- const lastMessage = 'What do you need?';
469
- const result = checkQuestionReflector(input, config, lastMessage);
470
- assert.strictEqual(result.decision, 'block');
471
- assert.ok(result.reason.includes('CYCLIST:QUESTION'));
472
- });
473
-
474
- it('should include appropriate marker hint for direct questions', () => {
475
- const input = {
476
- transcript_path: '/tmp/test.jsonl',
477
- stop_hook_active: false,
478
- };
479
- const config = CONFIG_MANUAL;
480
- const lastMessage = 'Should I proceed?';
481
- const result = checkQuestionReflector(input, config, lastMessage);
482
- assert.strictEqual(result.decision, 'block');
483
- assert.ok(result.reason.includes('CYCLIST:QUESTION:yesno'));
484
- });
485
-
486
- it('should include appropriate marker hint for choices', () => {
487
- const input = {
488
- transcript_path: '/tmp/test.jsonl',
489
- stop_hook_active: false,
490
- };
491
- const config = CONFIG_MANUAL;
492
- const lastMessage = 'We could either use Option A or Option B.';
493
- const result = checkQuestionReflector(input, config, lastMessage);
494
- assert.strictEqual(result.decision, 'block');
495
- assert.ok(result.reason.includes('CYCLIST:CHOICES'));
496
- });
497
- });
498
-
499
- // =============================================================================
500
- // AskUserQuestion PreToolUse Hook Tests
501
- // =============================================================================
502
-
503
- describe('checkAskUserQuestion - PreToolUse hook', () => {
504
- it('should return ok:true when relay_mode is true', () => {
505
- const input = {
506
- tool_name: 'AskUserQuestion',
507
- tool_input: { questions: [{ question: 'Which option?' }] },
508
- };
509
- const config = CONFIG_RELAY_ON;
510
- const result = checkAskUserQuestion(input, config);
511
- assert.deepStrictEqual(result, { ok: true });
512
- });
513
-
514
- it('should return ok:true when in turbo mode (legacy)', () => {
515
- const input = {
516
- tool_name: 'AskUserQuestion',
517
- tool_input: { questions: [{ question: 'Which option?' }] },
518
- };
519
- const config = CONFIG_TURBO_LEGACY;
520
- const result = checkAskUserQuestion(input, config);
521
- assert.deepStrictEqual(result, { ok: true });
522
- });
523
-
524
- it('should block AskUserQuestion without prior marker in transcript', () => {
525
- const input = {
526
- tool_name: 'AskUserQuestion',
527
- tool_input: { questions: [{ question: 'Which option?' }] },
528
- };
529
- const config = CONFIG_MANUAL;
530
- const transcriptWithoutMarker = 'Here are some choices.';
531
- const result = checkAskUserQuestion(input, config, transcriptWithoutMarker);
532
- assert.strictEqual(result.decision, 'block');
533
- });
534
-
535
- it('should allow AskUserQuestion when marker present in recent output', () => {
536
- const input = {
537
- tool_name: 'AskUserQuestion',
538
- tool_input: { questions: [{ question: 'Which option?' }] },
539
- };
540
- const config = CONFIG_MANUAL;
541
- const transcriptWithMarker = `${MARKERS.choices}\nHere are some choices.`;
542
- const result = checkAskUserQuestion(input, config, transcriptWithMarker);
543
- assert.deepStrictEqual(result, { ok: true });
544
- });
545
- });