codewhale.history 2.9.3 → 2.10.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 CHANGED
@@ -87,6 +87,7 @@ current project. Two independent dimensions across five difficulty levels:
87
87
  **Content theme** (what it's about):
88
88
  - **Standard** (default) — Code comprehension or line reconstruction.
89
89
  - **SOLID** (`teach me solid`) — Anti-pattern detection and design restoration.
90
+ - **Spec** (`teach me spec`) — Spec-to-code traceability using office docs as source.
90
91
 
91
92
  | Trigger | Response | Theme | What happens |
92
93
  |---------|----------|-------|-------------|
@@ -94,6 +95,8 @@ current project. Two independent dimensions across five difficulty levels:
94
95
  | `teach me interactive` | interactive | standard | Lines removed; you restore them |
95
96
  | `teach me solid` | passive | solid | Degraded code; you describe fixes |
96
97
  | `teach me solid interactive` | interactive | solid | Degraded code; you edit to fix it |
98
+ | `teach me spec` | passive | spec | Spec or code presented; you trace the link |
99
+ | `teach me spec interactive` | interactive | spec | Trace spec to code; you edit code to match |
97
100
 
98
101
  **Triggers:** `teach me` · `quiz me` · `test my knowledge` · `code quiz` ·
99
102
  `drill me`
@@ -106,6 +109,7 @@ current project. Two independent dimensions across five difficulty levels:
106
109
  | Level | `teach me level 3` or `teach me l3` | Set difficulty 1–5 (default: 3) |
107
110
  | Response | `teach me interactive` | Edit the file instead of describing |
108
111
  | Theme | `teach me solid` | Anti-pattern detection — fix degraded design |
112
+ | Theme | `teach me spec` | Spec-to-code traceability via office docs |
109
113
  | Scope | `teach me services/` | Narrow to a specific file or folder |
110
114
  | Concept | `teach me decorators` | Target: decorators, async, generators, context managers, comprehensions, error handling, type hints, threading |
111
115
 
@@ -121,7 +125,7 @@ current project. Two independent dimensions across five difficulty levels:
121
125
 
122
126
  **Mid-round commands:** `hint` (get a nudge) · `skip` (see the answer) ·
123
127
  `next` (draw a new snippet) · `level N` · `easier` · `harder` ·
124
- `interactive` · `passive` · `solid` · `done` (verify work) · `stop`
128
+ `interactive` · `passive` · `solid` · `spec` · `done` (verify work) · `stop`
125
129
 
126
130
  **At the end** you get a session summary: rounds completed, mode, files covered,
127
131
  what you're strong on, what to review, and a suggested next level.
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Office Document Text Extractor
4
+ *
5
+ * Extracts plain text from .docx, .pptx, and .xlsx files.
6
+ * Pure Node.js — no external dependencies. Handles ZIP/Open XML internally.
7
+ *
8
+ * Usage:
9
+ * node _extract_office.js <file.docx|pptx|xlsx>
10
+ * codewhale-doc-extract <file.docx|pptx|xlsx>
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const zlib = require('zlib');
15
+
16
+ const filePath = process.argv[2];
17
+ if (!filePath) {
18
+ console.error('Usage: codewhale-doc-extract <file.docx|pptx|xlsx>');
19
+ process.exit(1);
20
+ }
21
+
22
+ const ext = filePath.toLowerCase().split('.').pop();
23
+ const buf = fs.readFileSync(filePath);
24
+
25
+ // ── ZIP parser ─────────────────────────────────────────────────
26
+ function readZip(buffer) {
27
+ const entries = [];
28
+ let offset = 0;
29
+
30
+ while (offset < buffer.length - 4) {
31
+ const sig = buffer.readUInt32LE(offset);
32
+ if (sig !== 0x04034b50) break;
33
+
34
+ const compression = buffer.readUInt16LE(offset + 8);
35
+ const compressedSize = buffer.readUInt32LE(offset + 18);
36
+ const nameLen = buffer.readUInt16LE(offset + 26);
37
+ const extraLen = buffer.readUInt16LE(offset + 28);
38
+
39
+ const name = buffer.toString('utf-8', offset + 30, offset + 30 + nameLen);
40
+ const dataStart = offset + 30 + nameLen + extraLen;
41
+
42
+ let data;
43
+ if (compression === 0) {
44
+ data = buffer.slice(dataStart, dataStart + compressedSize);
45
+ } else if (compression === 8) {
46
+ const raw = buffer.slice(dataStart, dataStart + compressedSize);
47
+ data = zlib.inflateRawSync(raw);
48
+ } else {
49
+ offset = dataStart + compressedSize;
50
+ continue;
51
+ }
52
+
53
+ entries.push({ name, data: data.toString('utf-8') });
54
+ offset = dataStart + compressedSize;
55
+ }
56
+
57
+ return entries;
58
+ }
59
+
60
+ // ── XML text stripper ──────────────────────────────────────────
61
+ function stripXml(xml) {
62
+ return xml
63
+ .replace(/<[^>]+>/g, ' ')
64
+ .replace(/&amp;/g, '&')
65
+ .replace(/&lt;/g, '<')
66
+ .replace(/&gt;/g, '>')
67
+ .replace(/&quot;/g, '"')
68
+ .replace(/&apos;/g, "'")
69
+ .replace(/&#?\w+;/g, ' ')
70
+ .replace(/\s+/g, ' ')
71
+ .trim();
72
+ }
73
+
74
+ // ── Format-specific extraction ─────────────────────────────────
75
+ const entries = readZip(buf);
76
+ let text = '';
77
+
78
+ if (ext === 'docx') {
79
+ const doc = entries.find(e => e.name === 'word/document.xml');
80
+ if (doc) text = stripXml(doc.data);
81
+
82
+ for (const e of entries) {
83
+ if (e.name.startsWith('word/header') || e.name.startsWith('word/footer')) {
84
+ text += '\n' + stripXml(e.data);
85
+ }
86
+ }
87
+ } else if (ext === 'pptx') {
88
+ const slides = entries
89
+ .filter(e => /^ppt\/slides\/slide\d+\.xml$/.test(e.name))
90
+ .sort((a, b) => {
91
+ const na = parseInt(a.name.match(/slide(\d+)/)[1]);
92
+ const nb = parseInt(b.name.match(/slide(\d+)/)[1]);
93
+ return na - nb;
94
+ });
95
+
96
+ for (let i = 0; i < slides.length; i++) {
97
+ text += '\n--- Slide ' + (i + 1) + ' ---\n';
98
+ text += stripXml(slides[i].data) + '\n';
99
+ }
100
+
101
+ for (const e of entries) {
102
+ if (e.name.startsWith('ppt/notesSlides/notesSlide')) {
103
+ text += '\n[Notes]\n' + stripXml(e.data) + '\n';
104
+ }
105
+ }
106
+ } else if (ext === 'xlsx') {
107
+ const sst = entries.find(e => e.name === 'xl/sharedStrings.xml');
108
+ const strings = sst ? extractSharedStrings(sst.data) : [];
109
+
110
+ const sheets = entries
111
+ .filter(e => /^xl\/worksheets\/sheet\d+\.xml$/.test(e.name))
112
+ .sort((a, b) => {
113
+ const na = parseInt(a.name.match(/sheet(\d+)/)[1]);
114
+ const nb = parseInt(b.name.match(/sheet(\d+)/)[1]);
115
+ return na - nb;
116
+ });
117
+
118
+ for (let i = 0; i < sheets.length; i++) {
119
+ text += '\n--- Sheet ' + (i + 1) + ' ---\n';
120
+ text += renderSheet(sheets[i].data, strings) + '\n';
121
+ }
122
+ } else {
123
+ console.error('Unsupported format. Use .docx, .pptx, or .xlsx');
124
+ process.exit(1);
125
+ }
126
+
127
+ console.log(text.trim());
128
+
129
+ // ── XLSX helpers ───────────────────────────────────────────────
130
+ function extractSharedStrings(xml) {
131
+ const matches = xml.match(/<si>([\s\S]*?)<\/si>/g) || [];
132
+ return matches.map(si => stripXml(si));
133
+ }
134
+
135
+ function renderSheet(sheetXml, strings) {
136
+ const rows = sheetXml.match(/<row[\s\S]*?<\/row>/g) || [];
137
+ let output = '';
138
+ for (const row of rows) {
139
+ const cells = row.match(/<c[^>]*>[\s\S]*?<\/c>/g) || [];
140
+ const rowValues = [];
141
+ for (const cell of cells) {
142
+ const tMatch = cell.match(/t="([^"]*)"/);
143
+ const type = tMatch ? tMatch[1] : null;
144
+ const vMatch = cell.match(/<v>([^<]*)<\/v>/);
145
+ if (!vMatch) continue;
146
+ if (type === 's') {
147
+ const idx = parseInt(vMatch[1]);
148
+ rowValues.push(strings[idx] || '');
149
+ } else {
150
+ rowValues.push(vMatch[1]);
151
+ }
152
+ }
153
+ if (rowValues.some(v => v.trim())) {
154
+ output += rowValues.join(' | ') + '\n';
155
+ }
156
+ }
157
+ return output;
158
+ }
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "codewhale.history",
3
- "version": "2.9.3",
3
+ "version": "2.10.0",
4
4
  "description": "CodeWhale utility commands: session history, tool listing, file snapshot, interactive code quiz — global install",
5
5
  "bin": {
6
6
  "codewhale-history": "./_list_sessions.js",
7
- "codewhale-tools-install": "./tools-install.js"
7
+ "codewhale-tools-install": "./tools-install.js",
8
+ "codewhale-doc-extract": "./_extract_office.js"
8
9
  },
9
10
  "files": [
10
11
  "_list_sessions.js",
12
+ "_extract_office.js",
11
13
  "tools-install.js",
12
14
  "README.md",
13
15
  "instructions.md",
@@ -12,9 +12,12 @@ current project. Two independent dimensions combine to form each exercise:
12
12
  - **`passive`** (default) — You describe your answer in the console.
13
13
  - **`interactive`** — You edit the actual file, then say `done` for verification.
14
14
 
15
- **Content theme** (what the exercise is about — trigger keyword `solid`):
15
+ **Content theme** (what the exercise is about — trigger keywords `solid`, `spec`):
16
16
  - **Standard** (default) — Code comprehension or line reconstruction.
17
17
  - **`solid`** / **`SOLID`** — Anti-pattern detection and design restoration.
18
+ - **`spec`** — Spec-to-code traceability. Exercise shapes alternate randomly:
19
+ spec → code (show a requirement, ask what code implements it) or code → spec
20
+ (show code, ask what requirement it satisfies).
18
21
 
19
22
  | Trigger | Response | Theme | What happens |
20
23
  |---------|----------|-------|-------------|
@@ -22,6 +25,8 @@ current project. Two independent dimensions combine to form each exercise:
22
25
  | `teach me interactive` | interactive | standard | Lines removed from file; you restore them, say `done` |
23
26
  | `teach me solid` | passive | solid | Code rewritten with anti-patterns; you describe design fixes |
24
27
  | `teach me solid interactive` | interactive | solid | Code rewritten with anti-patterns; you edit the file to restore good design, say `done` |
28
+ | `teach me spec` | passive | spec | Present spec or code; you describe the link between them |
29
+ | `teach me spec interactive` | interactive | spec | Spec-to-code traceability; you edit code to satisfy a spec, say `done` |
25
30
 
26
31
  Level, scope, language, and concept modifiers combine freely with any
27
32
  combination above.
@@ -48,12 +53,14 @@ Optionally followed by modifiers:
48
53
  `teach me error handling`, `teach me type hints`, `teach me threading`
49
54
  - `teach me interactive` — edit the file (instead of describing)
50
55
  - `teach me solid` / `teach me SOLID` — anti-pattern restoration theme
56
+ - `teach me spec` — spec-to-code traceability theme
51
57
 
52
58
  Modifiers combine freely: `teach me decorators level 4`,
53
59
  `teach me async services/`, `teach me generators l2`,
54
60
  `teach me level 2 interactive`, `teach me interactive decorators`,
55
61
  `teach me solid level 3`, `teach me SOLID`,
56
- `teach me solid interactive`
62
+ `teach me solid interactive`,
63
+ `teach me spec`, `teach me spec interactive`
57
64
 
58
65
  If no level is specified, default to **level 3** and adjust based on
59
66
  performance across rounds.
@@ -328,6 +335,94 @@ Type `hint` for a clue, `skip` to see the answer, or `stop` to end.
328
335
  ---
329
336
  ```
330
337
 
338
+ ### 2d. Spec Theme — Spec-to-Code Traceability
339
+
340
+ When content theme is `spec`, the exercise bridges office documents (`.docx`,
341
+ `.pptx`, `.xlsx`) and source code. Each round randomly alternates between two
342
+ exercise shapes.
343
+
344
+ #### Discovery (Spec Theme)
345
+
346
+ In addition to the standard code discovery:
347
+ 1. Scan the workspace for `.docx`, `.pptx`, and `.xlsx` files.
348
+ 2. Extract text from each using `codewhale-doc-extract <file>` (global bin).
349
+ 3. Split extracted text into spec fragments: paragraphs, requirement bullets,
350
+ table rows, slide contents.
351
+ 4. Build a combined index: code fragments + spec fragments.
352
+
353
+ #### Exercise Shapes
354
+
355
+ Randomly choose one per round (50/50):
356
+
357
+ **A. Spec → Code** — "Trace forward"
358
+ - Present a spec fragment (a requirement, acceptance criterion, or spec
359
+ paragraph from an office document).
360
+ - Ask: "What code in this project implements this specification?"
361
+ - The user identifies or describes the matching code.
362
+
363
+ **B. Code → Spec** — "Trace backward"
364
+ - Present a code snippet from the project.
365
+ - Ask: "What requirement or specification does this code satisfy?"
366
+ - The user identifies the matching spec from the office documents.
367
+
368
+ The model must verify the link in both directions — does the code actually
369
+ implement the spec, and does the spec actually describe the code?
370
+
371
+ #### Spec Theme Per-Round Flow
372
+
373
+ 0. **Snapshot git state.** Run `git status --porcelain` on all source files.
374
+ 1. **Pick direction** randomly (A or B). Select a fragment from the
375
+ appropriate index (spec index for A, code index for B).
376
+ 2. **Present** the fragment with its source (filename for code, document name
377
+ + section for specs).
378
+ 3. **Instruct** (depends on response mode):
379
+ - *Passive:* "Trace the link. What [code implements this spec / spec does
380
+ this code satisfy]? Describe in detail."
381
+ - *Interactive:* Same prompt, but also: "If the link is broken or missing,
382
+ edit the code to correctly implement the spec. Say `done` when ready."
383
+ For interactive: run `//snapshot on` first if the workspace is NOT a git
384
+ repo.
385
+ 4. **Wait.** Do not evaluate until the user signals completion.
386
+ 5. **Evaluate** (depends on response mode):
387
+ - *Passive:* Assess the user's trace — did they correctly identify the
388
+ link? Is their reasoning sound?
389
+ - *Interactive:* Re-read the modified files. Compare against the spec.
390
+ Does the code now correctly implement the requirement?
391
+ 6. **Feedback** — structure:
392
+ ```
393
+ **Trace accuracy:** [Correct / Partially correct / Incorrect]
394
+ **What you got right:** [the link they correctly identified]
395
+ **What you missed:** [missed connections between spec and code]
396
+ ```
397
+ 7. **Restore** (interactive only). If files were modified: `git checkout -- <file>`.
398
+ If not a git repo, `//snapshot off` to stop backing up.
399
+
400
+ #### Thinking Suppression (Spec Theme)
401
+
402
+ Use **Skip** thinking during selection and presentation (steps 1–3). Resume
403
+ normal depth at step 4. Never reveal the correct trace link in reasoning.
404
+
405
+ #### Spec Theme Presentation Format
406
+
407
+ ```
408
+ ---
409
+ ## Round N — Level X · passive · spec | Spec → Code
410
+
411
+ **From:** `QA Cinemas Requirements.pptx` — Slide 4, "Website Requirements (1)"
412
+
413
+ > The QA Cinemas site needs a home page. The home page shall: Be generally
414
+ > attractive. Be the default for the entire site. Allow site users to navigate
415
+ > to other areas of the site. Have a picture or graphic evocative of the
416
+ > movies or the cinema on it.
417
+
418
+ **Your turn:** (Passive) What code in this project implements the QA Cinemas
419
+ home page requirement? Trace the link — describe which file(s) and how they
420
+ satisfy these criteria.
421
+
422
+ Type `hint` for a clue, `skip` to see the answer, or `stop` to end.
423
+ ---
424
+ ```
425
+
331
426
  ### 3. Presentation — Show the Snippet
332
427
 
333
428
  For each round, present the snippet with its filename:
@@ -565,14 +660,15 @@ nailed everything at-level, say so and highlight the nuance they caught.
565
660
  | `harder` | Increase level by 1 (maximum 5). |
566
661
  | `interactive` | Switch to interactive (reconstruction) mode for subsequent rounds. |
567
662
  | `passive` | Switch to passive (explanation) mode for subsequent rounds. |
568
- | `solid` / `SOLID` | Switch to SOLID (anti-pattern reconstruction) mode for subsequent rounds. |
569
- | `done` / `check my work` | (Interactive/SOLID mode) Signal that work is complete. Triggers verification. |
663
+ | `solid` / `SOLID` | Switch to SOLID (anti-pattern reconstruction) theme for subsequent rounds. |
664
+ | `spec` | Switch to spec (spec-to-code traceability) theme for subsequent rounds. |
665
+ | `done` / `check my work` | (Interactive/SOLID/Spec mode) Signal that work is complete. Triggers verification. |
570
666
  | `stop` / `end` | End session. Deliver summary. |
571
667
 
572
668
  ### 5. Loop — Keep Going
573
669
 
574
670
  After a successful evaluation or a `next` skip, ask:
575
- "Another round? (yes / no / level N / interactive / passive / solid / stop)"
671
+ "Another round? (yes / no / level N / interactive / passive / solid / spec / stop)"
576
672
 
577
673
  After a strike-3 reveal, do **not** ask — proceed directly to the next
578
674
  round with:
@@ -641,8 +737,11 @@ criteria for the Mechanics axis:
641
737
  restore the original lines manually via `edit_file` instead and warn the user.
642
738
  - **Restore on session end.** When the session ends, check whether any files
643
739
  still contain `# ... N lines removed ...` placeholders or deconstructed
644
- code from interactive/SOLID mode. If so, restore them with
740
+ code from interactive/SOLID/spec mode. If so, restore them with
645
741
  `git checkout -- <file>` (or `edit_file` if pre-existing changes).
742
+ - **Spec theme snapshot.** In interactive+spec mode, run `//snapshot on`
743
+ before file edits if the workspace is not a git repo. Run `//snapshot off`
744
+ after restoration.
646
745
 
647
746
  ## Verification
648
747
 
@@ -664,3 +763,8 @@ After each round, confirm:
664
763
  - (SOLID mode) Individual anti-patterns were NOT annotated or revealed before evaluation
665
764
  - (SOLID mode) The file was re-read before evaluating the user's restoration
666
765
  - (SOLID mode) Deconstructed files were restored with `git checkout` on round end
766
+ - (Spec theme) Office docs were scanned and extracted during discovery
767
+ - (Spec theme) Exercise direction (spec→code or code→spec) was chosen randomly each round
768
+ - (Spec theme) The trace link was verified in both directions by the model
769
+ - (Spec theme) Interactive mode ran `//snapshot on/off` for non-git workspaces
770
+ - (Spec theme) Modified files were restored after the round