explorbot 0.4.9 → 0.5.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/README.md +4 -1
- package/bin/mdq.ts +18 -0
- package/boat/api-tester/src/ai/chief.ts +72 -0
- package/boat/api-tester/src/api-client.ts +37 -0
- package/boat/prima/src/prima.ts +41 -2
- package/dist/bin/mdq.js +19 -0
- package/dist/boat/api-tester/src/ai/chief.js +69 -0
- package/dist/boat/api-tester/src/api-client.js +26 -0
- package/dist/boat/prima/src/prima.js +42 -2
- package/dist/package.json +3 -2
- package/dist/src/action.js +4 -2
- package/dist/src/ai/agent.d.ts +3 -1
- package/dist/src/ai/judge-provider.d.ts +17 -0
- package/dist/src/ai/judge-provider.js +56 -0
- package/dist/src/ai/judge-tool.d.ts +2 -0
- package/dist/src/ai/judge-tool.js +33 -0
- package/dist/src/ai/judge.d.ts +28 -0
- package/dist/src/ai/judge.js +71 -0
- package/dist/src/ai/navigator.d.ts +3 -1
- package/dist/src/ai/navigator.js +24 -28
- package/dist/src/ai/pilot.d.ts +4 -0
- package/dist/src/ai/pilot.js +67 -9
- package/dist/src/ai/planner.js +9 -6
- package/dist/src/ai/provider.d.ts +4 -1
- package/dist/src/ai/provider.js +52 -7
- package/dist/src/ai/rerunner.js +7 -0
- package/dist/src/ai/researcher/deep-analysis.js +2 -2
- package/dist/src/ai/researcher/locators.js +2 -2
- package/dist/src/ai/researcher/pagination.js +1 -1
- package/dist/src/ai/researcher/research-result.js +2 -2
- package/dist/src/ai/researcher.js +1 -1
- package/dist/src/ai/task-agent.d.ts +2 -0
- package/dist/src/ai/task-agent.js +3 -1
- package/dist/src/ai/tester.js +19 -15
- package/dist/src/ai/tools.d.ts +4 -3
- package/dist/src/ai/tools.js +35 -7
- package/dist/src/api/request-result.js +2 -1
- package/dist/src/api/xhr-capture.js +2 -1
- package/dist/src/command-handler.d.ts +1 -0
- package/dist/src/command-handler.js +24 -3
- package/dist/src/commands/base-command.d.ts +5 -0
- package/dist/src/commands/base-command.js +3 -0
- package/dist/src/commands/explore-command.d.ts +2 -1
- package/dist/src/commands/explore-command.js +12 -1
- package/dist/src/commands/freesail-command.js +8 -2
- package/dist/src/commands/init-command.js +1 -1
- package/dist/src/commands/navigate-command.d.ts +2 -1
- package/dist/src/commands/navigate-command.js +6 -0
- package/dist/src/commands/plan-load-command.d.ts +2 -1
- package/dist/src/commands/plan-load-command.js +4 -0
- package/dist/src/commands/plans-command.d.ts +3 -9
- package/dist/src/commands/plans-command.js +11 -21
- package/dist/src/commands/rerun-command.d.ts +2 -1
- package/dist/src/commands/rerun-command.js +5 -1
- package/dist/src/commands/research-command.d.ts +2 -1
- package/dist/src/commands/research-command.js +6 -0
- package/dist/src/commands/test-command.d.ts +2 -1
- package/dist/src/commands/test-command.js +4 -1
- package/dist/src/components/Autocomplete.js +26 -12
- package/dist/src/components/InputReadline.js +10 -1
- package/dist/src/config.d.ts +6 -0
- package/dist/src/experience-tracker.js +4 -3
- package/dist/src/explorbot.d.ts +3 -0
- package/dist/src/explorbot.js +8 -0
- package/dist/src/explorer.js +2 -3
- package/dist/src/knowledge-tracker.js +1 -1
- package/dist/src/reporter.js +8 -4
- package/dist/src/state-manager.d.ts +2 -0
- package/dist/src/state-manager.js +16 -0
- package/dist/src/test-plan.d.ts +11 -0
- package/dist/src/test-plan.js +54 -2
- package/dist/src/utils/aria-ref.js +1 -1
- package/dist/src/utils/logger.js +9 -3
- package/dist/src/utils/markdown-query.d.ts +1 -48
- package/dist/src/utils/markdown-query.js +1 -444
- package/dist/src/utils/mdq/cli.d.ts +6 -0
- package/dist/src/utils/mdq/cli.js +122 -0
- package/dist/src/utils/mdq/edit.d.ts +24 -0
- package/dist/src/utils/mdq/edit.js +147 -0
- package/dist/src/utils/mdq/query.d.ts +118 -0
- package/dist/src/utils/mdq/query.js +451 -0
- package/dist/src/utils/step-analyzer.d.ts +3 -0
- package/dist/src/utils/step-analyzer.js +7 -0
- package/dist/src/utils/strings.d.ts +1 -0
- package/dist/src/utils/strings.js +7 -0
- package/dist/src/utils/test-files.d.ts +1 -0
- package/dist/src/utils/test-files.js +5 -2
- package/dist/src/utils/url-matcher.d.ts +1 -0
- package/dist/src/utils/url-matcher.js +7 -0
- package/docs/api-testing/planning.md +1 -1
- package/docs/superpowers/plans/2026-09-15-mdq-package.md +130 -94
- package/docs/superpowers/specs/2026-09-18-judge-decision-model-design.md +79 -0
- package/package.json +3 -2
- package/src/action.ts +4 -2
- package/src/ai/agent.ts +3 -1
- package/src/ai/judge-provider.ts +62 -0
- package/src/ai/judge-tool.ts +35 -0
- package/src/ai/judge.ts +75 -0
- package/src/ai/navigator.ts +24 -27
- package/src/ai/pilot.ts +68 -9
- package/src/ai/planner.ts +9 -6
- package/src/ai/provider.ts +51 -7
- package/src/ai/rerunner.ts +4 -0
- package/src/ai/researcher/deep-analysis.ts +2 -2
- package/src/ai/researcher/locators.ts +2 -2
- package/src/ai/researcher/pagination.ts +1 -1
- package/src/ai/researcher/research-result.ts +2 -2
- package/src/ai/researcher.ts +1 -1
- package/src/ai/task-agent.ts +4 -1
- package/src/ai/tester.ts +19 -16
- package/src/ai/tools.ts +42 -7
- package/src/api/request-result.ts +2 -1
- package/src/api/xhr-capture.ts +2 -1
- package/src/command-handler.ts +28 -3
- package/src/commands/base-command.ts +9 -0
- package/src/commands/explore-command.ts +15 -2
- package/src/commands/freesail-command.ts +9 -2
- package/src/commands/init-command.ts +1 -1
- package/src/commands/navigate-command.ts +8 -1
- package/src/commands/plan-load-command.ts +6 -1
- package/src/commands/plans-command.ts +13 -29
- package/src/commands/rerun-command.ts +7 -2
- package/src/commands/research-command.ts +8 -1
- package/src/commands/test-command.ts +6 -2
- package/src/components/Autocomplete.tsx +39 -10
- package/src/components/InputReadline.tsx +10 -1
- package/src/config.ts +1 -0
- package/src/experience-tracker.ts +4 -3
- package/src/explorbot.ts +8 -0
- package/src/explorer.ts +2 -2
- package/src/knowledge-tracker.ts +1 -1
- package/src/reporter.ts +8 -4
- package/src/state-manager.ts +16 -0
- package/src/test-plan.ts +67 -2
- package/src/utils/aria-ref.ts +1 -1
- package/src/utils/logger.ts +7 -2
- package/src/utils/markdown-query.ts +1 -519
- package/src/utils/mdq/cli.ts +118 -0
- package/src/utils/mdq/edit.ts +158 -0
- package/src/utils/mdq/query.ts +556 -0
- package/src/utils/step-analyzer.ts +8 -0
- package/src/utils/strings.ts +7 -0
- package/src/utils/test-files.ts +5 -2
- package/src/utils/url-matcher.ts +7 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# mdq Package Implementation Plan
|
|
2
2
|
|
|
3
|
-
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
|
|
4
4
|
|
|
5
5
|
**Goal:** Extract `src/utils/markdown-query.ts` into `src/utils/mdq/` as a publish-ready package that can both query and update markdown, then add a jq-like CLI.
|
|
6
6
|
|
|
@@ -68,7 +68,7 @@ Move the parser to its new home and teach it the one thing it gets wrong today:
|
|
|
68
68
|
|
|
69
69
|
The `trailing` field is new and load-bearing: `marked` emits `space` tokens as siblings (a `paragraph` raw is `"para"` with no newline, followed by a separate `space` raw of `"\n\n"`), so every write verb needs to know where a node's separator lives.
|
|
70
70
|
|
|
71
|
-
- [
|
|
71
|
+
- [x] **Step 1: Write the failing test**
|
|
72
72
|
|
|
73
73
|
```ts
|
|
74
74
|
// tests/unit/mdq/frontmatter.test.ts
|
|
@@ -127,12 +127,12 @@ describe('buildTokenIndex', () => {
|
|
|
127
127
|
});
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
- [
|
|
130
|
+
- [x] **Step 2: Run test to verify it fails**
|
|
131
131
|
|
|
132
132
|
Run: `bun test tests/unit/mdq/frontmatter.test.ts`
|
|
133
133
|
Expected: FAIL — cannot resolve `src/utils/mdq/query.ts`
|
|
134
134
|
|
|
135
|
-
- [
|
|
135
|
+
- [x] **Step 3: Create query.ts with the index**
|
|
136
136
|
|
|
137
137
|
Copy `src/utils/markdown-query.ts` to `src/utils/mdq/query.ts` verbatim first, then apply these three changes.
|
|
138
138
|
|
|
@@ -207,12 +207,12 @@ export interface MatchedRange {
|
|
|
207
207
|
}
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
-
- [
|
|
210
|
+
- [x] **Step 4: Run test to verify it passes**
|
|
211
211
|
|
|
212
212
|
Run: `bun test tests/unit/mdq/frontmatter.test.ts`
|
|
213
213
|
Expected: PASS (8 tests)
|
|
214
214
|
|
|
215
|
-
- [
|
|
215
|
+
- [x] **Step 5: Format, lint and commit**
|
|
216
216
|
|
|
217
217
|
```bash
|
|
218
218
|
bun run format && bun run lint:fix
|
|
@@ -236,7 +236,7 @@ Get every existing call site running against the new file with **zero behaviour
|
|
|
236
236
|
- Consumes: `buildTokenIndex`, `splitFrontmatter`, `MatchedRange` from Task 1
|
|
237
237
|
- Produces: `mdq(source: string): MarkdownQuery`, class `MarkdownQuery`, `parseQuery`, all existing methods unchanged
|
|
238
238
|
|
|
239
|
-
- [
|
|
239
|
+
- [x] **Step 1: Move the test file and repoint its import**
|
|
240
240
|
|
|
241
241
|
```bash
|
|
242
242
|
git mv tests/unit/markdown-query.test.ts tests/unit/mdq/query.test.ts
|
|
@@ -254,12 +254,12 @@ to:
|
|
|
254
254
|
import { mdq, parseQuery } from '../../../src/utils/mdq/query.ts';
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
- [
|
|
257
|
+
- [x] **Step 2: Run the suite to verify it fails**
|
|
258
258
|
|
|
259
259
|
Run: `bun test tests/unit/mdq/query.test.ts`
|
|
260
260
|
Expected: FAIL — `mdq` / `parseQuery` are not yet exported from `query.ts`, or section tests fail because `expandSectionRanges` still assumes `space` tokens are present
|
|
261
261
|
|
|
262
|
-
- [
|
|
262
|
+
- [x] **Step 3: Restore the full API in query.ts**
|
|
263
263
|
|
|
264
264
|
Everything from the original `markdown-query.ts` below `buildTokenIndex` — `matchText`, `entryKey`, `getTokenText`, `getHeadingDepth`, `isSectionSelector`, `getSectionDepth`, `selectorToTokenType`, `computeSections`, `extractListItems`, `applyIndexSlice`, `expandSectionRanges`, `executeSegments`, `class MarkdownQuery`, `mdq` — carries over unchanged, except:
|
|
265
265
|
|
|
@@ -277,23 +277,23 @@ for (let j = i + 1; j < candidates.length; j++) {
|
|
|
277
277
|
|
|
278
278
|
Move every `export interface` / `export type` to the end of the file, and replace the ternaries at the original lines 30, 34, 129-130 and 295 with early returns.
|
|
279
279
|
|
|
280
|
-
- [
|
|
280
|
+
- [x] **Step 4: Run the suite to verify it passes**
|
|
281
281
|
|
|
282
282
|
Run: `bun test tests/unit/mdq/query.test.ts`
|
|
283
283
|
Expected: PASS — **110 tests**, the same count as before the move
|
|
284
284
|
|
|
285
|
-
- [
|
|
285
|
+
- [x] **Step 5: Replace markdown-query.ts with a shim**
|
|
286
286
|
|
|
287
287
|
```ts
|
|
288
288
|
export * from './mdq/query.ts';
|
|
289
289
|
```
|
|
290
290
|
|
|
291
|
-
- [
|
|
291
|
+
- [x] **Step 6: Verify every existing call site still works**
|
|
292
292
|
|
|
293
293
|
Run: `bun test tests/unit/`
|
|
294
294
|
Expected: PASS, no new failures versus the pre-task run
|
|
295
295
|
|
|
296
|
-
- [
|
|
296
|
+
- [x] **Step 7: Format, lint and commit**
|
|
297
297
|
|
|
298
298
|
```bash
|
|
299
299
|
bun run format && bun run lint:fix
|
|
@@ -327,7 +327,7 @@ The one risky task. It ends with the repo green and every break fixed.
|
|
|
327
327
|
- `mdq(source: Markdown): MarkdownDoc`
|
|
328
328
|
- Deprecated alias `MarkdownQuery = Selection`
|
|
329
329
|
|
|
330
|
-
- [
|
|
330
|
+
- [x] **Step 1: Write the failing test**
|
|
331
331
|
|
|
332
332
|
```ts
|
|
333
333
|
// append to tests/unit/mdq/query.test.ts
|
|
@@ -359,12 +359,12 @@ describe('MarkdownDoc chaining', () => {
|
|
|
359
359
|
});
|
|
360
360
|
```
|
|
361
361
|
|
|
362
|
-
- [
|
|
362
|
+
- [x] **Step 2: Run test to verify it fails**
|
|
363
363
|
|
|
364
364
|
Run: `bun test tests/unit/mdq/query.test.ts -t 'MarkdownDoc chaining'`
|
|
365
365
|
Expected: FAIL — `.query is not a function` on the string returned by `replace`
|
|
366
366
|
|
|
367
|
-
- [
|
|
367
|
+
- [x] **Step 3: Split MarkdownQuery into MarkdownDoc and Selection**
|
|
368
368
|
|
|
369
369
|
`MarkdownDoc` holds the source. `Selection` holds source plus matches. Every write on `Selection` ends by wrapping its result:
|
|
370
370
|
|
|
@@ -411,6 +411,9 @@ export function mdq(source: Markdown): MarkdownDoc {
|
|
|
411
411
|
}
|
|
412
412
|
```
|
|
413
413
|
|
|
414
|
+
`query` takes only a selector here. The optional second `matcher` argument arrives in
|
|
415
|
+
Task 5 — it is not missing.
|
|
416
|
+
|
|
414
417
|
At the end of the file:
|
|
415
418
|
|
|
416
419
|
```ts
|
|
@@ -420,12 +423,20 @@ export type Markdown = string | MarkdownDoc;
|
|
|
420
423
|
export const MarkdownQuery = Selection;
|
|
421
424
|
```
|
|
422
425
|
|
|
423
|
-
|
|
426
|
+
Before relying on that alias, confirm nothing imports the class by name:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
git grep -ln "MarkdownQuery" -- src bin boat tests
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Expected: only `src/utils/mdq/query.ts`. Any other file needs its import checked.
|
|
433
|
+
|
|
434
|
+
- [x] **Step 4: Run test to verify it passes**
|
|
424
435
|
|
|
425
436
|
Run: `bun test tests/unit/mdq/query.test.ts -t 'MarkdownDoc chaining'`
|
|
426
437
|
Expected: PASS (4 tests)
|
|
427
438
|
|
|
428
|
-
- [
|
|
439
|
+
- [x] **Step 5: Fix the write assertions in the ported suite**
|
|
429
440
|
|
|
430
441
|
The existing `replace` tests assert against a string. Wrap each in `String(...)`, for example:
|
|
431
442
|
|
|
@@ -442,7 +453,7 @@ Apply the same to every assertion in the `replace`, `setKeyValue` and `edge case
|
|
|
442
453
|
Run: `bun test tests/unit/mdq/query.test.ts`
|
|
443
454
|
Expected: PASS — 114 tests
|
|
444
455
|
|
|
445
|
-
- [
|
|
456
|
+
- [x] **Step 6: Migrate the four breakage classes**
|
|
446
457
|
|
|
447
458
|
**(a) Assignment into a `string`-typed target** — append `.toString()`:
|
|
448
459
|
|
|
@@ -490,7 +501,7 @@ Expected: PASS — 114 tests
|
|
|
490
501
|
|
|
491
502
|
**Leave alone** — these already work because `mdq()` accepts a `MarkdownDoc`: `planner.ts:303`, `planner.ts:405`.
|
|
492
503
|
|
|
493
|
-
- [
|
|
504
|
+
- [x] **Step 7: Fix the one regex call site**
|
|
494
505
|
|
|
495
506
|
`src/ai/researcher.ts:316` relies on regex matching being implicitly case-insensitive. Task 4 removes that. Make the flag explicit now so the two changes never overlap:
|
|
496
507
|
|
|
@@ -498,27 +509,33 @@ Expected: PASS — 114 tests
|
|
|
498
509
|
const summaryText = mdq(result.text).query('section2(/^summary/i)').query('paragraph[0]').text().trim();
|
|
499
510
|
```
|
|
500
511
|
|
|
501
|
-
- [
|
|
512
|
+
- [x] **Step 8: Verify with the scoped type check**
|
|
513
|
+
|
|
514
|
+
Grep by **type name, repo-wide** — not by file path. A path-scoped grep misses test
|
|
515
|
+
helpers and bin scripts, and those break too:
|
|
502
516
|
|
|
503
517
|
```bash
|
|
504
|
-
bunx tsc -p tsconfig.json --noEmit 2>&1 | grep -E "
|
|
518
|
+
bunx tsc -p tsconfig.json --noEmit 2>&1 | grep -E "MarkdownDoc|MarkdownQuery|Selection"
|
|
505
519
|
```
|
|
506
520
|
|
|
507
|
-
Expected: **
|
|
521
|
+
Expected: **no output at all.**
|
|
508
522
|
|
|
509
|
-
|
|
510
|
-
src/ai/researcher/locators.ts(247,41): error TS2339: Property 'playwrightLocatorCount' does not exist on type 'Explorer'.
|
|
511
|
-
src/ai/researcher/locators.ts(247,65): error TS7006: Parameter 'page' implicitly has an 'any' type.
|
|
512
|
-
```
|
|
523
|
+
Any line is an unmigrated call site. CI will not catch it, because CI runs `tsc --noCheck`.
|
|
513
524
|
|
|
514
|
-
|
|
525
|
+
The audit in Step 6 lists eleven sites; the real count is **seventeen**. The extras are all
|
|
526
|
+
the same shape — a function whose declared return type is `string` now returns a
|
|
527
|
+
`MarkdownDoc` — in `boat/prima/src/prima.ts`, `planner.ts` (three chains),
|
|
528
|
+
`deep-analysis.ts:542` and `experience-tracker.ts:511`, plus a test helper in
|
|
529
|
+
`tests/unit/research-parser-pagination.test.ts` that feeds the result to `marked.lexer`,
|
|
530
|
+
which does **not** coerce and throws `e.replace is not a function` at runtime. Trust the
|
|
531
|
+
grep, not the list.
|
|
515
532
|
|
|
516
|
-
- [
|
|
533
|
+
- [x] **Step 9: Run the full unit suite**
|
|
517
534
|
|
|
518
535
|
Run: `bun test tests/unit/`
|
|
519
536
|
Expected: PASS, no new failures
|
|
520
537
|
|
|
521
|
-
- [
|
|
538
|
+
- [x] **Step 10: Format, lint and commit**
|
|
522
539
|
|
|
523
540
|
```bash
|
|
524
541
|
bun run format && bun run lint:fix
|
|
@@ -540,7 +557,7 @@ Four grammar changes, all additive now that Task 3 pre-fixed the one regex call
|
|
|
540
557
|
- Consumes: `parseQuery`, `getTokenText`, `selectorToTokenType`, `matchText` from Task 2
|
|
541
558
|
- Produces: `class MdqError extends Error`, `class MdqSelectorError extends MdqError` (with `index: number`), selectors `comment` and `html`
|
|
542
559
|
|
|
543
|
-
- [
|
|
560
|
+
- [x] **Step 1: Write the failing test**
|
|
544
561
|
|
|
545
562
|
```ts
|
|
546
563
|
// tests/unit/mdq/selectors.test.ts
|
|
@@ -629,7 +646,7 @@ describe('selector errors', () => {
|
|
|
629
646
|
mdq(doc).query('h2("A") secton("B")');
|
|
630
647
|
expect.unreachable();
|
|
631
648
|
} catch (error) {
|
|
632
|
-
expect(error.index).toBe(
|
|
649
|
+
expect(error.index).toBe(8);
|
|
633
650
|
}
|
|
634
651
|
});
|
|
635
652
|
|
|
@@ -639,12 +656,12 @@ describe('selector errors', () => {
|
|
|
639
656
|
});
|
|
640
657
|
```
|
|
641
658
|
|
|
642
|
-
- [
|
|
659
|
+
- [x] **Step 2: Run test to verify it fails**
|
|
643
660
|
|
|
644
661
|
Run: `bun test tests/unit/mdq/selectors.test.ts`
|
|
645
662
|
Expected: FAIL — `MdqSelectorError` is not exported
|
|
646
663
|
|
|
647
|
-
- [
|
|
664
|
+
- [x] **Step 3: Implement the four changes**
|
|
648
665
|
|
|
649
666
|
Error classes, at the top of the class section:
|
|
650
667
|
|
|
@@ -769,17 +786,17 @@ export interface NodeInfo {
|
|
|
769
786
|
}
|
|
770
787
|
```
|
|
771
788
|
|
|
772
|
-
- [
|
|
789
|
+
- [x] **Step 4: Run test to verify it passes**
|
|
773
790
|
|
|
774
791
|
Run: `bun test tests/unit/mdq/selectors.test.ts`
|
|
775
792
|
Expected: PASS (13 tests)
|
|
776
793
|
|
|
777
|
-
- [
|
|
794
|
+
- [x] **Step 5: Verify nothing regressed**
|
|
778
795
|
|
|
779
796
|
Run: `bun test tests/unit/`
|
|
780
797
|
Expected: PASS — in particular `query.test.ts` still at 114, since Task 3 already fixed `researcher.ts:316`
|
|
781
798
|
|
|
782
|
-
- [
|
|
799
|
+
- [x] **Step 6: Format, lint and commit**
|
|
783
800
|
|
|
784
801
|
```bash
|
|
785
802
|
bun run format && bun run lint:fix
|
|
@@ -806,7 +823,7 @@ Removes the hand-escaping wart: `section.name.replace(/"/g, '\\"')` at `research
|
|
|
806
823
|
- Sugar on both classes: `section` `heading` `paragraph` `table` `list` `item` `code` `blockquote` `comment` `html` `hr`
|
|
807
824
|
- `at(n: number): Selection` and `slice(from?: number, to?: number): Selection` on `Selection`
|
|
808
825
|
|
|
809
|
-
- [
|
|
826
|
+
- [x] **Step 1: Write the failing test**
|
|
810
827
|
|
|
811
828
|
```ts
|
|
812
829
|
// tests/unit/mdq/sugar.test.ts
|
|
@@ -918,12 +935,12 @@ describe('canonical read names', () => {
|
|
|
918
935
|
});
|
|
919
936
|
```
|
|
920
937
|
|
|
921
|
-
- [
|
|
938
|
+
- [x] **Step 2: Run test to verify it fails**
|
|
922
939
|
|
|
923
940
|
Run: `bun test tests/unit/mdq/sugar.test.ts`
|
|
924
941
|
Expected: FAIL — `mdq(...).heading is not a function`
|
|
925
942
|
|
|
926
|
-
- [
|
|
943
|
+
- [x] **Step 3: Implement matchers and the shared sugar base**
|
|
927
944
|
|
|
928
945
|
A value matcher bypasses the grammar entirely, so it needs its own `TextMatcher` mode:
|
|
929
946
|
|
|
@@ -1027,7 +1044,7 @@ On `Selection`, add:
|
|
|
1027
1044
|
|
|
1028
1045
|
At the end of the file add `Matcher`, `SelectorOptions`, `predicate?: (text: string) => boolean` on `TextMatcher`, and widen its `mode` to include `'predicate'`.
|
|
1029
1046
|
|
|
1030
|
-
- [
|
|
1047
|
+
- [x] **Step 4: Run test to verify it passes**
|
|
1031
1048
|
|
|
1032
1049
|
Run: `bun test tests/unit/mdq/sugar.test.ts`
|
|
1033
1050
|
Expected: PASS (21 tests)
|
|
@@ -1035,7 +1052,7 @@ Expected: PASS (21 tests)
|
|
|
1035
1052
|
The `canonical read names` block fails until Step 5 adds the renames — that is expected.
|
|
1036
1053
|
Run Step 5 before treating those four as real failures.
|
|
1037
1054
|
|
|
1038
|
-
- [
|
|
1055
|
+
- [x] **Step 5: Add the read renames and their deprecated aliases**
|
|
1039
1056
|
|
|
1040
1057
|
Canonical names, with the old ones kept and marked:
|
|
1041
1058
|
|
|
@@ -1060,7 +1077,7 @@ Each alias is one line, for example:
|
|
|
1060
1077
|
Run: `bun test tests/unit/`
|
|
1061
1078
|
Expected: PASS — the ported suite still calls the deprecated names and must keep working
|
|
1062
1079
|
|
|
1063
|
-
- [
|
|
1080
|
+
- [x] **Step 6: Format, lint and commit**
|
|
1064
1081
|
|
|
1065
1082
|
```bash
|
|
1066
1083
|
bun run format && bun run lint:fix
|
|
@@ -1093,7 +1110,7 @@ This matters because `marked` separators are uneven: a `heading` raw is `"# A\n\
|
|
|
1093
1110
|
- Produces, on `Selection`: `remove()`, `insertBefore(md)`, `insertAfter(md)`, `prepend(md)`, `append(md)`
|
|
1094
1111
|
- Produces, on `MarkdownDoc`: `append(md)`, `prepend(md)`
|
|
1095
1112
|
|
|
1096
|
-
- [
|
|
1113
|
+
- [x] **Step 1: Write the failing test**
|
|
1097
1114
|
|
|
1098
1115
|
```ts
|
|
1099
1116
|
// tests/unit/mdq/edit.test.ts
|
|
@@ -1199,12 +1216,12 @@ describe('chained edits', () => {
|
|
|
1199
1216
|
});
|
|
1200
1217
|
```
|
|
1201
1218
|
|
|
1202
|
-
- [
|
|
1219
|
+
- [x] **Step 2: Run test to verify it fails**
|
|
1203
1220
|
|
|
1204
1221
|
Run: `bun test tests/unit/mdq/edit.test.ts`
|
|
1205
1222
|
Expected: FAIL — `.remove is not a function`
|
|
1206
1223
|
|
|
1207
|
-
- [
|
|
1224
|
+
- [x] **Step 3: Write edit.ts**
|
|
1208
1225
|
|
|
1209
1226
|
```ts
|
|
1210
1227
|
import type { MatchedRange } from './query.ts';
|
|
@@ -1238,20 +1255,28 @@ export function removeRanges(source: string, ranges: MatchedRange[]): string {
|
|
|
1238
1255
|
let result = source;
|
|
1239
1256
|
for (let i = ordered.length - 1; i >= 0; i--) {
|
|
1240
1257
|
const range = ordered[i];
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
if (
|
|
1244
|
-
|
|
1258
|
+
const head = result.slice(0, range.start);
|
|
1259
|
+
const tail = result.slice(blockEnd(range));
|
|
1260
|
+
if (tail) {
|
|
1261
|
+
result = head + tail;
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
if (!head) {
|
|
1265
|
+
result = '';
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
result = `${head.replace(/\n+$/, '')}\n`;
|
|
1245
1269
|
}
|
|
1246
1270
|
return result;
|
|
1247
1271
|
}
|
|
1248
1272
|
|
|
1249
1273
|
export function insertAt(source: string, offset: number, markdown: string): string {
|
|
1250
1274
|
const block = normalizeBlock(markdown);
|
|
1251
|
-
const before = source.slice(0, offset);
|
|
1252
|
-
const after = source.slice(offset);
|
|
1253
|
-
if (!
|
|
1254
|
-
return `${before}
|
|
1275
|
+
const before = source.slice(0, offset).replace(/\n+$/, '');
|
|
1276
|
+
const after = source.slice(offset).replace(/^\n+/, '');
|
|
1277
|
+
if (!before) return `${block}\n${after}`;
|
|
1278
|
+
if (!after) return `${before}\n\n${block}`;
|
|
1279
|
+
return `${before}\n\n${block}\n${after}`;
|
|
1255
1280
|
}
|
|
1256
1281
|
|
|
1257
1282
|
function dedupeRanges(ranges: MatchedRange[]): MatchedRange[] {
|
|
@@ -1266,17 +1291,26 @@ function dedupeRanges(ranges: MatchedRange[]): MatchedRange[] {
|
|
|
1266
1291
|
return kept;
|
|
1267
1292
|
}
|
|
1268
1293
|
|
|
1269
|
-
function trimPrecedingBlankLine(source: string, start: number): number {
|
|
1270
|
-
let cursor = start;
|
|
1271
|
-
while (cursor > 0 && source[cursor - 1] === '\n') cursor--;
|
|
1272
|
-
if (cursor === 0) return 0;
|
|
1273
|
-
return cursor + 1;
|
|
1274
|
-
}
|
|
1275
1294
|
```
|
|
1276
1295
|
|
|
1277
|
-
|
|
1296
|
+
Both rules below were derived from real `marked` output, not assumed. **Do not "simplify"
|
|
1297
|
+
either one.**
|
|
1298
|
+
|
|
1299
|
+
**`insertAt` normalizes only the seam.** It strips newlines from the end of `before` and
|
|
1300
|
+
the start of `after`, then rebuilds the join. The tempting alternative — a global
|
|
1301
|
+
`.replace(/\n{3,}/g, '\n\n')` over the document — is wrong: a fenced code block's raw really
|
|
1302
|
+
does contain runs of blank lines (`marked` lexes a ```js block holding `a\n\n\nb` as one
|
|
1303
|
+
`code` token whose raw carries `\n\n\n`), so a global collapse silently rewrites user code.
|
|
1304
|
+
|
|
1305
|
+
**`removeRanges` collapses only at end-of-document.** `marked` bakes the separator into
|
|
1306
|
+
some raws and not others: a mid-document `paragraph` raw is `"para"` with a sibling `space`
|
|
1307
|
+
token, a document-final `paragraph` raw is `"last\n"` with no sibling, and every `heading`
|
|
1308
|
+
raw carries its own `"\n\n"`. So "no trailing space, therefore trim backwards" is wrong — on
|
|
1309
|
+
`'# A\n\n## B\n\ntext\n'` it eats a blank line and yields `'# A\ntext\n'`. Deleting
|
|
1310
|
+
`[start, blockEnd)` is already correct whenever anything follows; only a node removed from
|
|
1311
|
+
the very end needs repair.
|
|
1278
1312
|
|
|
1279
|
-
- [
|
|
1313
|
+
- [x] **Step 4: Wire the verbs onto Selection and MarkdownDoc**
|
|
1280
1314
|
|
|
1281
1315
|
On `Selection`, four public verbs delegating to one private helper:
|
|
1282
1316
|
|
|
@@ -1355,14 +1389,14 @@ export class MdqOperationError extends MdqError {
|
|
|
1355
1389
|
}
|
|
1356
1390
|
```
|
|
1357
1391
|
|
|
1358
|
-
- [
|
|
1392
|
+
- [x] **Step 5: Run test to verify it passes**
|
|
1359
1393
|
|
|
1360
1394
|
Run: `bun test tests/unit/mdq/edit.test.ts`
|
|
1361
1395
|
Expected: PASS (19 tests)
|
|
1362
1396
|
|
|
1363
1397
|
If a whitespace assertion fails, print the actual output with `JSON.stringify` before changing anything — the difference is almost always one newline, and guessing at it will break a different case.
|
|
1364
1398
|
|
|
1365
|
-
- [
|
|
1399
|
+
- [x] **Step 6: Run the whole suite and commit**
|
|
1366
1400
|
|
|
1367
1401
|
```bash
|
|
1368
1402
|
bun test tests/unit/
|
|
@@ -1385,7 +1419,7 @@ git commit -m "feat(mdq): remove and insert verbs with whitespace normalization"
|
|
|
1385
1419
|
- Produces in `edit.ts`: `renderTable(headers: string[], rows: string[][], align: (string | null)[]): string`, `renderItem(listRaw: string, text: string): string`
|
|
1386
1420
|
- Produces on `Selection`: `addRow(row: Record<string, string>): MarkdownDoc`, `addItem(text: string): MarkdownDoc`
|
|
1387
1421
|
|
|
1388
|
-
- [
|
|
1422
|
+
- [x] **Step 1: Write the failing test**
|
|
1389
1423
|
|
|
1390
1424
|
```ts
|
|
1391
1425
|
// tests/unit/mdq/structural.test.ts
|
|
@@ -1451,12 +1485,12 @@ describe('addItem', () => {
|
|
|
1451
1485
|
});
|
|
1452
1486
|
```
|
|
1453
1487
|
|
|
1454
|
-
- [
|
|
1488
|
+
- [x] **Step 2: Run test to verify it fails**
|
|
1455
1489
|
|
|
1456
1490
|
Run: `bun test tests/unit/mdq/structural.test.ts`
|
|
1457
1491
|
Expected: FAIL — `.addRow is not a function`
|
|
1458
1492
|
|
|
1459
|
-
- [
|
|
1493
|
+
- [x] **Step 3: Add the renderers to edit.ts**
|
|
1460
1494
|
|
|
1461
1495
|
```ts
|
|
1462
1496
|
export function renderTable(headers: string[], rows: string[][], align: (string | null)[]): string {
|
|
@@ -1484,7 +1518,7 @@ function dashes(alignment: string | null, width: number): string {
|
|
|
1484
1518
|
}
|
|
1485
1519
|
```
|
|
1486
1520
|
|
|
1487
|
-
- [
|
|
1521
|
+
- [x] **Step 4: Wire the verbs onto Selection**
|
|
1488
1522
|
|
|
1489
1523
|
```ts
|
|
1490
1524
|
addRow(row: Record<string, string>): MarkdownDoc {
|
|
@@ -1510,12 +1544,12 @@ function dashes(alignment: string | null, width: number): string {
|
|
|
1510
1544
|
}
|
|
1511
1545
|
```
|
|
1512
1546
|
|
|
1513
|
-
- [
|
|
1547
|
+
- [x] **Step 5: Run test to verify it passes**
|
|
1514
1548
|
|
|
1515
1549
|
Run: `bun test tests/unit/mdq/structural.test.ts`
|
|
1516
1550
|
Expected: PASS (10 tests)
|
|
1517
1551
|
|
|
1518
|
-
- [
|
|
1552
|
+
- [x] **Step 6: Run the whole suite and commit**
|
|
1519
1553
|
|
|
1520
1554
|
```bash
|
|
1521
1555
|
bun test tests/unit/
|
|
@@ -1541,7 +1575,7 @@ git commit -m "feat(mdq): addRow and addItem structural inserts"
|
|
|
1541
1575
|
|
|
1542
1576
|
Reading and writing both go through `yaml`'s **Document API** (`YAML.parseDocument`), never `parse`/`stringify`. That is what preserves comments through a write — verified behaviour, not an assumption.
|
|
1543
1577
|
|
|
1544
|
-
- [
|
|
1578
|
+
- [x] **Step 1: Write the failing test**
|
|
1545
1579
|
|
|
1546
1580
|
```ts
|
|
1547
1581
|
// append to tests/unit/mdq/frontmatter.test.ts
|
|
@@ -1612,19 +1646,19 @@ describe('entries and setEntry', () => {
|
|
|
1612
1646
|
});
|
|
1613
1647
|
```
|
|
1614
1648
|
|
|
1615
|
-
- [
|
|
1649
|
+
- [x] **Step 2: Run test to verify it fails**
|
|
1616
1650
|
|
|
1617
1651
|
Run: `bun test tests/unit/mdq/frontmatter.test.ts`
|
|
1618
1652
|
Expected: FAIL — `.frontmatter is not a function`
|
|
1619
1653
|
|
|
1620
|
-
- [
|
|
1654
|
+
- [x] **Step 3: Implement in edit.ts**
|
|
1621
1655
|
|
|
1622
1656
|
```ts
|
|
1623
1657
|
import YAML from 'yaml';
|
|
1624
1658
|
|
|
1625
1659
|
export function writeFrontmatter(source: string, key: string, value: unknown): string {
|
|
1626
1660
|
const { raw, body, offset } = splitFrontmatter(source);
|
|
1627
|
-
const document = YAML.parseDocument(raw
|
|
1661
|
+
const document = raw ? YAML.parseDocument(raw) : new YAML.Document({});
|
|
1628
1662
|
if (value === null) document.delete(key);
|
|
1629
1663
|
if (value !== null) document.set(key, value);
|
|
1630
1664
|
const rendered = document.toString().replace(/\s+$/, '');
|
|
@@ -1662,7 +1696,7 @@ export function rewriteEntries(tokenText: string, isBlockquote: boolean, key: st
|
|
|
1662
1696
|
|
|
1663
1697
|
`entryKey` moves to `edit.ts` alongside it.
|
|
1664
1698
|
|
|
1665
|
-
- [
|
|
1699
|
+
- [x] **Step 4: Wire onto the classes**
|
|
1666
1700
|
|
|
1667
1701
|
```ts
|
|
1668
1702
|
frontmatter(): Record<string, unknown> {
|
|
@@ -1685,12 +1719,12 @@ export function rewriteEntries(tokenText: string, isBlockquote: boolean, key: st
|
|
|
1685
1719
|
}
|
|
1686
1720
|
```
|
|
1687
1721
|
|
|
1688
|
-
- [
|
|
1722
|
+
- [x] **Step 5: Run test to verify it passes**
|
|
1689
1723
|
|
|
1690
1724
|
Run: `bun test tests/unit/mdq/frontmatter.test.ts`
|
|
1691
1725
|
Expected: PASS (21 tests)
|
|
1692
1726
|
|
|
1693
|
-
- [
|
|
1727
|
+
- [x] **Step 6: Confirm knowledge and experience files now parse correctly**
|
|
1694
1728
|
|
|
1695
1729
|
This is the real-world check that motivated the feature:
|
|
1696
1730
|
|
|
@@ -1710,7 +1744,7 @@ Expected: frontmatter parsed as an object on each file, and **no heading whose t
|
|
|
1710
1744
|
|
|
1711
1745
|
If either directory is empty, skip this step and note it.
|
|
1712
1746
|
|
|
1713
|
-
- [
|
|
1747
|
+
- [x] **Step 7: Run the whole suite and commit**
|
|
1714
1748
|
|
|
1715
1749
|
```bash
|
|
1716
1750
|
bun test tests/unit/
|
|
@@ -1732,7 +1766,7 @@ The package is publish-ready only if someone can use it without reading the sour
|
|
|
1732
1766
|
- Consumes: the complete API from Tasks 3-8
|
|
1733
1767
|
- Produces: nothing code depends on
|
|
1734
1768
|
|
|
1735
|
-
- [
|
|
1769
|
+
- [x] **Step 1: Write the README**
|
|
1736
1770
|
|
|
1737
1771
|
Cover, in this order:
|
|
1738
1772
|
|
|
@@ -1750,7 +1784,7 @@ Do **not** document the deprecated aliases (`get` `toJson` `keyValue` `setKeyVal
|
|
|
1750
1784
|
|
|
1751
1785
|
Follow the repo docs style: show each format example once, and do not close with a "Why this matters" section.
|
|
1752
1786
|
|
|
1753
|
-
- [
|
|
1787
|
+
- [x] **Step 2: Verify every example in the README actually runs**
|
|
1754
1788
|
|
|
1755
1789
|
Extract each fenced `js` block and execute it. Any example that throws or prints something other than what the README claims is a documentation bug — fix the README, not the test.
|
|
1756
1790
|
|
|
@@ -1761,7 +1795,7 @@ import { mdq } from "./src/utils/mdq/query.ts";
|
|
|
1761
1795
|
'
|
|
1762
1796
|
```
|
|
1763
1797
|
|
|
1764
|
-
- [
|
|
1798
|
+
- [x] **Step 3: Commit**
|
|
1765
1799
|
|
|
1766
1800
|
```bash
|
|
1767
1801
|
git add src/utils/mdq/README.md
|
|
@@ -1786,7 +1820,7 @@ git commit -m "docs(mdq): package README"
|
|
|
1786
1820
|
|
|
1787
1821
|
Note a deliberate deviation from `CLAUDE.md`: command logic normally lives in `src/commands/`, but mdq must not import from anywhere in explorbot. Its CLI ships with the package.
|
|
1788
1822
|
|
|
1789
|
-
- [
|
|
1823
|
+
- [x] **Step 1: Write the failing test**
|
|
1790
1824
|
|
|
1791
1825
|
```ts
|
|
1792
1826
|
// tests/unit/mdq/cli.test.ts
|
|
@@ -1853,7 +1887,9 @@ describe('edits', () => {
|
|
|
1853
1887
|
});
|
|
1854
1888
|
|
|
1855
1889
|
it('sets an entry', async () => {
|
|
1856
|
-
|
|
1890
|
+
const result = await runMdq(['blockquote', '--set', 'Container=.x'], '> Container: .old\n');
|
|
1891
|
+
expect(result.output).toContain('.x');
|
|
1892
|
+
expect(result.output).not.toContain('.old');
|
|
1857
1893
|
});
|
|
1858
1894
|
});
|
|
1859
1895
|
|
|
@@ -1874,12 +1910,12 @@ describe('exit codes', () => {
|
|
|
1874
1910
|
});
|
|
1875
1911
|
```
|
|
1876
1912
|
|
|
1877
|
-
- [
|
|
1913
|
+
- [x] **Step 2: Run test to verify it fails**
|
|
1878
1914
|
|
|
1879
1915
|
Run: `bun test tests/unit/mdq/cli.test.ts`
|
|
1880
1916
|
Expected: FAIL — cannot resolve `cli.ts`
|
|
1881
1917
|
|
|
1882
|
-
- [
|
|
1918
|
+
- [x] **Step 3: Implement cli.ts**
|
|
1883
1919
|
|
|
1884
1920
|
Use Commander with `exitOverride()` and `.configureOutput()` so a parse failure surfaces as a return value rather than killing the process. Shape:
|
|
1885
1921
|
|
|
@@ -1926,12 +1962,12 @@ export interface CliResult {
|
|
|
1926
1962
|
}
|
|
1927
1963
|
```
|
|
1928
1964
|
|
|
1929
|
-
- [
|
|
1965
|
+
- [x] **Step 4: Run test to verify it passes**
|
|
1930
1966
|
|
|
1931
1967
|
Run: `bun test tests/unit/mdq/cli.test.ts`
|
|
1932
1968
|
Expected: PASS (13 tests)
|
|
1933
1969
|
|
|
1934
|
-
- [
|
|
1970
|
+
- [x] **Step 5: Add the thin bin entry**
|
|
1935
1971
|
|
|
1936
1972
|
```ts
|
|
1937
1973
|
#!/usr/bin/env bun
|
|
@@ -1950,7 +1986,7 @@ Add to `package.json` `bin`:
|
|
|
1950
1986
|
"mdq": "./dist/bin/mdq.js"
|
|
1951
1987
|
```
|
|
1952
1988
|
|
|
1953
|
-
- [
|
|
1989
|
+
- [x] **Step 6: Smoke-test the real binary**
|
|
1954
1990
|
|
|
1955
1991
|
```bash
|
|
1956
1992
|
echo '# A
|
|
@@ -1971,7 +2007,7 @@ bun run bin/mdq.ts 'nonsense' CLAUDE.md; echo "exit=$?"
|
|
|
1971
2007
|
```
|
|
1972
2008
|
Expected: `Unknown selector "nonsense"` and `exit=2`
|
|
1973
2009
|
|
|
1974
|
-
- [
|
|
2010
|
+
- [x] **Step 7: Run the whole suite and commit**
|
|
1975
2011
|
|
|
1976
2012
|
```bash
|
|
1977
2013
|
bun test tests/unit/
|
|
@@ -1987,12 +2023,12 @@ git commit -m "feat(mdq): jq-like CLI"
|
|
|
1987
2023
|
**Files:**
|
|
1988
2024
|
- Modify: `CHANGELOG.md`
|
|
1989
2025
|
|
|
1990
|
-
- [
|
|
2026
|
+
- [x] **Step 1: Run the full unit suite**
|
|
1991
2027
|
|
|
1992
2028
|
Run: `bun test tests/unit/`
|
|
1993
2029
|
Expected: PASS, no failures
|
|
1994
2030
|
|
|
1995
|
-
- [
|
|
2031
|
+
- [x] **Step 2: Re-run the scoped type check**
|
|
1996
2032
|
|
|
1997
2033
|
```bash
|
|
1998
2034
|
bunx tsc -p tsconfig.json --noEmit 2>&1 | grep -E "^(src/utils/mdq/|src/utils/markdown-query|src/experience-tracker|src/ai/planner|src/ai/researcher|bin/mdq)"
|
|
@@ -2000,7 +2036,7 @@ bunx tsc -p tsconfig.json --noEmit 2>&1 | grep -E "^(src/utils/mdq/|src/utils/ma
|
|
|
2000
2036
|
|
|
2001
2037
|
Expected: **exactly the two known `locators.ts(247,...)` lines.** Anything else is a real defect that CI will not catch.
|
|
2002
2038
|
|
|
2003
|
-
- [
|
|
2039
|
+
- [x] **Step 3: Confirm the package has no explorbot imports**
|
|
2004
2040
|
|
|
2005
2041
|
```bash
|
|
2006
2042
|
grep -rn "^import\|from '" src/utils/mdq/*.ts | grep -v "'marked'" | grep -v "'yaml'" | grep -v "'commander'" | grep -v "'./"
|
|
@@ -2008,11 +2044,11 @@ grep -rn "^import\|from '" src/utils/mdq/*.ts | grep -v "'marked'" | grep -v "'y
|
|
|
2008
2044
|
|
|
2009
2045
|
Expected: **no output.** Any line here breaks extractability, which is the whole point of the package.
|
|
2010
2046
|
|
|
2011
|
-
- [
|
|
2047
|
+
- [x] **Step 4: Update the changelog**
|
|
2012
2048
|
|
|
2013
2049
|
Use the `/changelog` skill, per `CLAUDE.md`.
|
|
2014
2050
|
|
|
2015
|
-
- [
|
|
2051
|
+
- [x] **Step 5: Commit**
|
|
2016
2052
|
|
|
2017
2053
|
```bash
|
|
2018
2054
|
git add CHANGELOG.md
|