codewhale.history 2.6.0 → 2.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.
- package/README.md +3 -1
- package/instructions.md +3 -0
- package/package.json +2 -2
- package/skills/snapshot/SKILL.md +1 -1
- package/skills/teach-me/SKILL.md +284 -0
- package/tools-install.js +6 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CodeWhale Tools Pack
|
|
2
2
|
|
|
3
|
-
A portable pack of global CodeWhale commands: `//tools`, `//history`, and `//
|
|
3
|
+
A portable pack of global CodeWhale commands: `//tools`, `//history`, `//snapshot`, and `//teach-me`.
|
|
4
4
|
|
|
5
5
|
Cross-platform — works on Windows, Mac, Linux.
|
|
6
6
|
|
|
@@ -14,6 +14,7 @@ tools_for_codewhale/
|
|
|
14
14
|
├── skills/
|
|
15
15
|
│ ├── history/SKILL.md # //history — calls codewhale-history
|
|
16
16
|
│ ├── snapshot/SKILL.md # //snapshot — pre-edit file backups
|
|
17
|
+
│ ├── teach-me/SKILL.md # //teach-me — interactive code quiz
|
|
17
18
|
│ └── tools/tools-skill/SKILL.md # //tools — lists all available tools
|
|
18
19
|
├── instructions.md # Trigger config (drop into any .codewhale/)
|
|
19
20
|
└── README.md # This file
|
|
@@ -40,6 +41,7 @@ codewhale-tools-install -p <target-path> # specific workspace
|
|
|
40
41
|
- **`//tools`** — lists every tool available in the current session
|
|
41
42
|
- **`//history`** — lists all chat sessions for the current workspace with cost totals
|
|
42
43
|
- **`//snapshot`** — toggle pre-edit file backups (`on`, `off`, `status`)
|
|
44
|
+
- **`//teach-me`** — interactive code quiz that tests your knowledge of the current codebase
|
|
43
45
|
|
|
44
46
|
## Requirements
|
|
45
47
|
- Node.js (for the `codewhale-history` command)
|
package/instructions.md
CHANGED
|
@@ -8,3 +8,6 @@ When the user types `//history`, immediately load the `history` skill (from `his
|
|
|
8
8
|
|
|
9
9
|
## //snapshot Command
|
|
10
10
|
When the user types `//snapshot`, immediately load the `snapshot` skill (from `snapshot/SKILL.md`) and follow its instructions. `//snapshot on` enables automatic pre-edit file backups to `_snapshots/` when no Git repo is present. `//snapshot off` disables it. `//snapshot status` reports the current state. This works in all sessions once the skill is installed globally.
|
|
11
|
+
|
|
12
|
+
## //teach-me Command
|
|
13
|
+
When the user types `//teach-me` (or any trigger phrase from the teach-me skill, including `teach me`, `quiz me`, `test my knowledge`, `code quiz`, `drill me`), immediately load the `teach-me` skill (from `teach-me/SKILL.md`) and start an interactive code-teaching quiz. Supports optional modifiers: `teach me <language>`, `teach me level N`, `teach me <file/module>`. This works in all sessions once the skill is installed globally.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codewhale.history",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "CodeWhale utility commands: session history, tool listing, file snapshot — global install",
|
|
3
|
+
"version": "2.8.0",
|
|
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
7
|
"codewhale-tools-install": "./tools-install.js"
|
package/skills/snapshot/SKILL.md
CHANGED
|
@@ -17,7 +17,7 @@ When the user types `//snapshot on`, `//snapshot off`, or `//snapshot status`, m
|
|
|
17
17
|
- **Windows**: `if (Test-Path .git) { ... }`
|
|
18
18
|
- **macOS/Linux**: `if [ -d .git ]; then ...`
|
|
19
19
|
3. **If Git repo exists**: reply "This workspace has a Git repo — snapshotting is not needed. Use `git restore <file>` to undo changes."
|
|
20
|
-
4. **If no Git repo**: create `_snapshots/` directory, write a note to persist `snapshot_enabled=true
|
|
20
|
+
4. **If no Git repo**: create `_snapshots/` directory, write a note to persist `snapshot_enabled=true`, then confirm to the user.
|
|
21
21
|
5. From this point forward, before editing any **existing** file:
|
|
22
22
|
- **Ensure `_snapshots/` exists** — create it if missing (don't assume it still exists from init time)
|
|
23
23
|
**Windows**: `if (-not (Test-Path '_snapshots')) { New-Item -ItemType Directory -Path '_snapshots' -Force | Out-Null }`
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: teach-me
|
|
3
|
+
description: Interactive code-teaching quiz. Selects random snippets from the current project and quizzes the user on application logic and language semantics. Triggered by 'teach me', 'quiz me', 'test my knowledge', 'code quiz', or 'drill me'. Supports difficulty levels 1–5.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Teach Me — Interactive Code Quiz
|
|
7
|
+
|
|
8
|
+
An interactive teaching session that randomly selects code snippets from the
|
|
9
|
+
current project, presents them to the user, asks the user to explain them, and
|
|
10
|
+
provides constructive feedback on both **application logic** (what the code
|
|
11
|
+
accomplishes) and **language mechanics** (syntax and semantics).
|
|
12
|
+
|
|
13
|
+
## Session Trigger
|
|
14
|
+
|
|
15
|
+
Activate when the user says any of:
|
|
16
|
+
|
|
17
|
+
- `teach me`
|
|
18
|
+
- `quiz me`
|
|
19
|
+
- `test my knowledge`
|
|
20
|
+
- `code quiz`
|
|
21
|
+
- `drill me`
|
|
22
|
+
- `explain this codebase`
|
|
23
|
+
|
|
24
|
+
Optionally followed by modifiers:
|
|
25
|
+
|
|
26
|
+
- `teach me python` — restrict to a specific language
|
|
27
|
+
- `teach me level 3` / `teach me l3` — set difficulty (1–5)
|
|
28
|
+
- `teach me <file or module>` — narrow scope to a specific file, directory, or module
|
|
29
|
+
|
|
30
|
+
If no level is specified, default to **level 3** and adjust based on
|
|
31
|
+
performance across rounds.
|
|
32
|
+
|
|
33
|
+
## Session Lifecycle
|
|
34
|
+
|
|
35
|
+
### 1. Discovery — Map the Project
|
|
36
|
+
|
|
37
|
+
Before presenting the first snippet:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
A. Use file_search or list_dir to find source directories.
|
|
41
|
+
B. Use grep_files to count function/class definitions per file.
|
|
42
|
+
C. Build a mental index: file path → rough function/class count.
|
|
43
|
+
D. Determine the primary language(s) from file extensions.
|
|
44
|
+
E. Report: "Found ~N candidates across M files. Starting at level X. Ready."
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If the user specified a scope, apply it during discovery. If the user
|
|
48
|
+
specified a language, filter by extension.
|
|
49
|
+
|
|
50
|
+
### 2. Selection — Pick a Snippet
|
|
51
|
+
|
|
52
|
+
Randomly select a file from the index, then randomly select a function,
|
|
53
|
+
method, or logical block from that file. Use these rules:
|
|
54
|
+
|
|
55
|
+
#### Good Snippets (select these)
|
|
56
|
+
- Functions or methods 5–55 lines long (after stripping docstrings and blank lines)
|
|
57
|
+
- Classes with 10–60 lines of method bodies
|
|
58
|
+
- Blocks containing: decorators, comprehensions, generators, context managers,
|
|
59
|
+
async/await, error handling, design patterns, algorithm implementations,
|
|
60
|
+
non-obvious control flow, or domain-specific logic
|
|
61
|
+
- Code that can be understood with at most one level of external context
|
|
62
|
+
|
|
63
|
+
#### Avoid (skip these)
|
|
64
|
+
- Pure getters/setters/properties (single-line `return self._x`)
|
|
65
|
+
- Imports, module-level constants, config dicts, `__init__.py` with only imports
|
|
66
|
+
- Boilerplate: `if __name__ == "__main__"`, argument parsers, logging setup
|
|
67
|
+
- Dunder methods that only delegate
|
|
68
|
+
- Functions shorter than 5 effective lines or longer than 60 lines
|
|
69
|
+
- Code that requires reading 3+ other files to understand
|
|
70
|
+
|
|
71
|
+
#### Difficulty → Line Ranges
|
|
72
|
+
|
|
73
|
+
| Level | Lines | Suitable patterns |
|
|
74
|
+
|-------|--------|-------------------|
|
|
75
|
+
| 1 | 5–15 | Straight-line logic, `if`/`else`, basic function calls |
|
|
76
|
+
| 2 | 8–20 | Loops, list/dict operations, simple `try`/`except` |
|
|
77
|
+
| 3 | 12–30 | Comprehensions, decorators, `with` statements, multiple branches |
|
|
78
|
+
| 4 | 18–45 | Generators, `async`/`await`, descriptors, threading, closures |
|
|
79
|
+
| 5 | 25–55 | Metaclasses, complex async patterns, multi-threading with synchronization, architectural glue code |
|
|
80
|
+
|
|
81
|
+
#### No-repeats rule
|
|
82
|
+
Track which snippets have been shown this session (file + line range).
|
|
83
|
+
Do not repeat a snippet unless the user explicitly asks or all candidates
|
|
84
|
+
are exhausted. Prefer cycling through files before returning to the same
|
|
85
|
+
file.
|
|
86
|
+
|
|
87
|
+
### 3. Presentation — Show the Snippet
|
|
88
|
+
|
|
89
|
+
For each round, present the snippet with its filename:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
---
|
|
93
|
+
## Round N — Level X | `path/to/file.py` (lines A–B)
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
42 def calculate_position_size(
|
|
97
|
+
43 capital: float,
|
|
98
|
+
44 risk_per_trade: float,
|
|
99
|
+
45 entry_price: float,
|
|
100
|
+
46 stop_loss: float
|
|
101
|
+
47 ) -> int:
|
|
102
|
+
48 risk_amount = capital * (risk_per_trade / 100)
|
|
103
|
+
49 price_risk = abs(entry_price - stop_loss)
|
|
104
|
+
50 if price_risk == 0:
|
|
105
|
+
51 return 0
|
|
106
|
+
52 shares = int(risk_amount / price_risk)
|
|
107
|
+
53 return max(shares, 0)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Your turn:** Explain what this code does (application logic) AND how it
|
|
111
|
+
works (syntax and semantics). Include any edge cases you notice.
|
|
112
|
+
|
|
113
|
+
Type `hint` for a clue, `skip` to see the answer, or `stop` to end.
|
|
114
|
+
---
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Guidelines:
|
|
118
|
+
- Include realistic line numbers (1-based from the actual file)
|
|
119
|
+
- Strip only excessive blank lines; keep natural spacing
|
|
120
|
+
- Show the function/class signature with its decorators
|
|
121
|
+
- Show the filename and line range in the header
|
|
122
|
+
- If the snippet depends on one obvious external type or constant, include a
|
|
123
|
+
brief inline note
|
|
124
|
+
- **Before presenting, scan for secrets.** Redact API keys, tokens, passwords,
|
|
125
|
+
connection strings with `[REDACTED]`. Skip snippets that are entirely
|
|
126
|
+
secrets or contain PII/email addresses/personal identifiers.
|
|
127
|
+
|
|
128
|
+
### 4. Evaluation — Assess the Answer
|
|
129
|
+
|
|
130
|
+
Evaluate across two dimensions, scaled to the current difficulty level.
|
|
131
|
+
|
|
132
|
+
#### A. Application Logic (what the code does in the project)
|
|
133
|
+
|
|
134
|
+
| Level | Expectation |
|
|
135
|
+
|-------|-------------|
|
|
136
|
+
| 1 | Names what the function does at a basic level |
|
|
137
|
+
| 2 | Identifies inputs, outputs, and at least one edge case |
|
|
138
|
+
| 3 | Explains the module role, failure modes, and upstream/downstream connections |
|
|
139
|
+
| 4 | Identifies the design pattern, multi-component interaction, and concurrency edge cases |
|
|
140
|
+
| 5 | Explains architectural role across system layers, performance implications, and subtle bugs or limitations |
|
|
141
|
+
|
|
142
|
+
#### B. Language Mechanics (syntax and semantics)
|
|
143
|
+
|
|
144
|
+
| Level | Expectation |
|
|
145
|
+
|-------|-------------|
|
|
146
|
+
| 1 | Basic types, function definition syntax, simple `if`/`else` |
|
|
147
|
+
| 2 | Loop mechanics, list/dict operations, basic `try`/`except`, string formatting |
|
|
148
|
+
| 3 | Comprehensions, decorator syntax and effect, `with` / context managers, type hints, exception chaining |
|
|
149
|
+
| 4 | Generator mechanics (`yield`), `async`/`await` internals, descriptor protocol, closures, threading primitives |
|
|
150
|
+
| 5 | Coroutine internals, metaclass programming, GIL implications, memory model, `__slots__`, MRO, weak references |
|
|
151
|
+
|
|
152
|
+
#### The Three-Strikes Rule
|
|
153
|
+
|
|
154
|
+
If the user's answer does **not** meet the expectations for the current
|
|
155
|
+
difficulty level:
|
|
156
|
+
|
|
157
|
+
**Strike 1** — Give a category-level nudge:
|
|
158
|
+
```
|
|
159
|
+
Good start, but at Level N I'd expect you to also notice [category — e.g.,
|
|
160
|
+
"how errors are handled" or "what the decorator is doing"]. Try again —
|
|
161
|
+
what about [specific nudge — e.g., "the return type on line 47"]?
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Strike 2** — Give a near-explicit hint:
|
|
165
|
+
```
|
|
166
|
+
Getting closer. One more thing — [nearly names the concept — e.g., "that
|
|
167
|
+
@retry decorator wraps the function"]. Look at line X.
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Strike 3** — Reveal the full answer and move on:
|
|
171
|
+
```
|
|
172
|
+
Let me walk you through it.
|
|
173
|
+
```
|
|
174
|
+
Then deliver the complete evaluation (both axes) as if they had skipped.
|
|
175
|
+
After the evaluation, proceed directly to the next round — do not ask
|
|
176
|
+
"another round?" after a strike-3 reveal; just present the next snippet.
|
|
177
|
+
|
|
178
|
+
If the user gets it on attempt 2 or 3:
|
|
179
|
+
```
|
|
180
|
+
There you go! Now let me fill in what else is notable.
|
|
181
|
+
```
|
|
182
|
+
Then provide the remaining feedback they missed, and ask "Another round?".
|
|
183
|
+
|
|
184
|
+
If the user's answer **meets** expectations (any attempt):
|
|
185
|
+
- Highlight 2–4 specific things they got right
|
|
186
|
+
- Add 1–3 things they could sharpen (even a strong answer has nuance)
|
|
187
|
+
- Reveal the file context
|
|
188
|
+
- Ask "Another round?"
|
|
189
|
+
|
|
190
|
+
#### Feedback structure (success or strike-3)
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
**What you got right:**
|
|
194
|
+
- [2–4 specific things correctly identified]
|
|
195
|
+
|
|
196
|
+
**What you missed or could sharpen:**
|
|
197
|
+
- [1–3 things, with brief explanation]
|
|
198
|
+
|
|
199
|
+
**Context:** `services/position_sizer.py` — called by the OrderManager before
|
|
200
|
+
placing any trade. Sits between the signal generator and the exchange adapter.
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Keep feedback constructive. The goal is learning, not grading. If they
|
|
204
|
+
nailed everything at-level, say so and highlight the nuance they caught.
|
|
205
|
+
|
|
206
|
+
#### Handling commands mid-round
|
|
207
|
+
|
|
208
|
+
| Command | Behavior |
|
|
209
|
+
|---------|----------|
|
|
210
|
+
| `hint` | Give one strike-1 level nudge (counts as an attempt in the strike system) |
|
|
211
|
+
| `skip` / `reveal` | Show full evaluation immediately. Move to next round. |
|
|
212
|
+
| `next` / `another` | Skip this snippet, draw a new one. No strike counted. |
|
|
213
|
+
| `level N` / `lN` (e.g. `level 5`, `l2`) | Adjust difficulty for subsequent rounds. |
|
|
214
|
+
| `easier` | Decrease level by 1 (minimum 1). |
|
|
215
|
+
| `harder` | Increase level by 1 (maximum 5). |
|
|
216
|
+
| `stop` / `done` / `end` | End session. Deliver summary. |
|
|
217
|
+
|
|
218
|
+
### 5. Loop — Keep Going
|
|
219
|
+
|
|
220
|
+
After a successful evaluation or a `next` skip, ask:
|
|
221
|
+
"Another round? (yes / no / level N / stop)"
|
|
222
|
+
|
|
223
|
+
After a strike-3 reveal, do **not** ask — proceed directly to the next
|
|
224
|
+
round with: "Round N+1 — Level X | `file.py`"
|
|
225
|
+
|
|
226
|
+
### End-of-Session Summary
|
|
227
|
+
|
|
228
|
+
When the user says `stop`:
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
## Session Summary
|
|
232
|
+
|
|
233
|
+
Rounds completed: 5
|
|
234
|
+
Level played: 3
|
|
235
|
+
Files covered:
|
|
236
|
+
- services/position_sizer.py (rounds 1, 4)
|
|
237
|
+
- agents/news_agent.py (round 2)
|
|
238
|
+
- utils/fuzzy_dedup.py (round 3)
|
|
239
|
+
- database.py (round 5)
|
|
240
|
+
|
|
241
|
+
What you're strong on: [concepts consistently identified]
|
|
242
|
+
What to review: [concepts missed across multiple rounds]
|
|
243
|
+
Suggested next level: [3 → 4, or stay, or 3 → 2]
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Multilingual Projects
|
|
247
|
+
|
|
248
|
+
If the project contains multiple languages, let the user's `teach me <lang>`
|
|
249
|
+
filter govern. If no filter, sample all languages proportionally but announce
|
|
250
|
+
the language in each round header. Use language-appropriate evaluation
|
|
251
|
+
criteria for the Mechanics axis:
|
|
252
|
+
|
|
253
|
+
- **Python:** decorators, generators, descriptors, `async`/`await`, type hints,
|
|
254
|
+
context managers, MRO, slots, data classes
|
|
255
|
+
- **JavaScript/TypeScript:** closures, prototypes, `this` binding, promises,
|
|
256
|
+
async/await, destructuring, spread, arrow functions, TypeScript generics
|
|
257
|
+
- **Rust:** ownership, borrowing, lifetimes, pattern matching, `Result`/`Option`,
|
|
258
|
+
traits, generics, macros
|
|
259
|
+
- **Go:** goroutines, channels, interfaces, defer, error handling conventions,
|
|
260
|
+
zero values
|
|
261
|
+
- **Java:** streams, generics, annotations, try-with-resources, concurrency
|
|
262
|
+
primitives, inheritance vs composition
|
|
263
|
+
|
|
264
|
+
## Guardrails
|
|
265
|
+
|
|
266
|
+
- **Never show secrets.** Scan every snippet before display. Redact API keys,
|
|
267
|
+
tokens, passwords, connection strings with `[REDACTED]`. Skip entire snippets
|
|
268
|
+
that are only secrets/config.
|
|
269
|
+
- **Never show user data.** Skip snippets containing PII, email addresses,
|
|
270
|
+
phone numbers, or personal identifiers.
|
|
271
|
+
- **One snippet per turn.** Wait for the user's response before showing the next.
|
|
272
|
+
- **Respect stop immediately.** If the user says `stop`, deliver the summary —
|
|
273
|
+
do not squeeze in another round.
|
|
274
|
+
- **Stay in teaching mode.** Do not fix bugs, refactor, or edit code during a
|
|
275
|
+
teaching session unless the user explicitly asks to switch modes.
|
|
276
|
+
|
|
277
|
+
## Verification
|
|
278
|
+
|
|
279
|
+
After each round, confirm:
|
|
280
|
+
- The snippet was actually read from the file (not hallucinated)
|
|
281
|
+
- The line numbers match the source
|
|
282
|
+
- The feedback accurately describes what the code does
|
|
283
|
+
- No secrets or PII were exposed
|
|
284
|
+
- The strike count was tracked correctly
|
package/tools-install.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* CodeWhale Tools Installer
|
|
4
4
|
*
|
|
5
|
-
* Installs global skills and workspace triggers for //tools, //history, and //
|
|
5
|
+
* Installs global skills and workspace triggers for //tools, //history, //snapshot, and //teach-me.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* node tools-install.js # current directory
|
|
@@ -55,12 +55,12 @@ if (fs.existsSync(instructionsSource)) {
|
|
|
55
55
|
if (fs.existsSync(instructionsDest)) {
|
|
56
56
|
const existingContent = fs.readFileSync(instructionsDest, 'utf-8');
|
|
57
57
|
// Only append if it's not already there (idempotent)
|
|
58
|
-
if (!existingContent.includes('## //tools Command') && !existingContent.includes('## //history Command') && !existingContent.includes('## //snapshot Command')) {
|
|
58
|
+
if (!existingContent.includes('## //tools Command') && !existingContent.includes('## //history Command') && !existingContent.includes('## //snapshot Command') && !existingContent.includes('## //teach-me Command')) {
|
|
59
59
|
const separator = `\n\n---\n\n`;
|
|
60
60
|
fs.writeFileSync(instructionsDest, existingContent + separator + newContent, 'utf-8');
|
|
61
|
-
console.log(' Appended //tools, //history, and //
|
|
61
|
+
console.log(' Appended //tools, //history, //snapshot, and //teach-me instructions to existing instructions.md');
|
|
62
62
|
} else {
|
|
63
|
-
console.log(' //tools, //history, and //
|
|
63
|
+
console.log(' //tools, //history, //snapshot, and //teach-me already present in instructions.md — skipped.');
|
|
64
64
|
}
|
|
65
65
|
} else {
|
|
66
66
|
fs.writeFileSync(instructionsDest, newContent, 'utf-8');
|
|
@@ -78,6 +78,7 @@ const checklist = {
|
|
|
78
78
|
'history skill': path.join(skillsDest, 'history', 'SKILL.md'),
|
|
79
79
|
'tools skill': path.join(skillsDest, 'tools', 'tools-skill', 'SKILL.md'),
|
|
80
80
|
'snapshot skill': path.join(skillsDest, 'snapshot', 'SKILL.md'),
|
|
81
|
+
'teach-me skill': path.join(skillsDest, 'teach-me', 'SKILL.md'),
|
|
81
82
|
'workspace instructions': instructionsDest
|
|
82
83
|
};
|
|
83
84
|
|
|
@@ -94,7 +95,7 @@ console.log('');
|
|
|
94
95
|
if (errors === 0) {
|
|
95
96
|
console.log('=== Installation complete! ===');
|
|
96
97
|
console.log('Make sure codewhale-history is installed globally: npm install -g <path-to-tools_for_codewhale>');
|
|
97
|
-
console.log('Then type //tools, //history, or //
|
|
98
|
+
console.log('Then type //tools, //history, //snapshot, or //teach-me in any CodeWhale session.');
|
|
98
99
|
} else {
|
|
99
100
|
console.log(`=== Installation finished with ${errors} error(s). ===`);
|
|
100
101
|
}
|