From c25641f0c9c17998121892016e1b1887499e249b Mon Sep 17 00:00:00 2001 From: Yury Bayda Date: Sat, 15 Aug 2026 00:36:23 -0700 Subject: [PATCH 1/3] build: add lint toolchain and generate the index page Add prettier and markdownlint-cli2 behind `make check`, plus the reformat and the two long-line and heading-level fixes they require in the Legacy Code decks. Build `_site/index.html` from the README curriculum through a pandoc template and Lua filter instead of maintaining a second copy by hand, and stop tracking the generated output. Replace the `**/*.md` deck glob, which relies on globstar that GNU make does not implement, with a find that keeps decks at any depth. --- .gitignore | 2 + .markdownlint.json | 1 + .prettierignore | 1 + Makefile | 27 +- README.md | 39 +- _site/index.html | 355 ----- index.lua | 48 + index.template.html | 31 + legacy-code/01-understanding-legacy.md | 11 +- legacy-code/02-kata-identifying.md | 23 +- legacy-code/03-safety-net.md | 15 +- .../04-kata-characterization-testing.md | 1 + legacy-code/05-safe-changes.md | 26 +- legacy-code/06-kata-sprouting-wrapping.md | 1 + legacy-code/07-dependency-breaking.md | 3 + legacy-code/08-kata-dependency-breaking.md | 1 + legacy-code/09-advanced-topics.md | 3 + package-lock.json | 1385 +++++++++++++++++ package.json | 17 + 19 files changed, 1585 insertions(+), 405 deletions(-) create mode 100644 .prettierignore delete mode 100644 _site/index.html create mode 100644 index.lua create mode 100644 index.template.html create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore index e983420..ebc4505 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.pdf .DS_Store +node_modules/ +_site/ diff --git a/.markdownlint.json b/.markdownlint.json index b5698ae..0709d1e 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -2,6 +2,7 @@ "default": true, "MD013": { "line_length": 150 }, "MD024": false, + "MD025": false, "MD033": false, "MD041": false } diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..57510a2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +_site/ diff --git a/Makefile b/Makefile index 8486d92..34ca764 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,32 @@ OUTDIR ?= _site -MARKDOWNS := $(wildcard **/*.md) + +# Decks live in per-curriculum subdirectories; -mindepth 2 keeps README.md out. +MARKDOWNS := $(shell find . -mindepth 2 -name '*.md' \ + -not -path './node_modules/*' -not -path './$(OUTDIR)/*' | sed 's|^\./||' | sort) PDFS := $(patsubst %.md,$(OUTDIR)/%.pdf,$(MARKDOWNS)) MDFLAGS := -f markdown -t beamer -s -H include.tex -V aspectratio:169 -V urlcolor:red +HTMLFLAGS := -f markdown -t html5 -s --template index.template.html --lua-filter index.lua + +.PHONY: all check clean format format-check lint + +all: $(PDFS) $(OUTDIR)/index.html + +check: format-check lint -all: $(PDFS) +format: + npm run format + +format-check: + npm run format-check + +lint: + npm run lint $(OUTDIR)/%.pdf: %.md mkdir -p $(dir $@) && pandoc $(MDFLAGS) --output $@ $^ -.PHONY: clean +$(OUTDIR)/index.html: README.md index.template.html index.lua + mkdir -p $(dir $@) && pandoc $(HTMLFLAGS) --output $@ $< + clean: - rm -f $(PDFS) + rm -f $(PDFS) $(OUTDIR)/index.html diff --git a/README.md b/README.md index 65834f4..079412a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,21 @@ Here you can find slides for Clean Code conversations or classes. +## Development + +Install Node.js 24 or later, Pandoc, and LaTeX. Then install the development dependencies and run +the validation and rendering steps: + +```shell +npm ci --ignore-scripts +make check +make -j +``` + +Everything under `_site` is generated. Each deck renders to a PDF, and the published index page is +built from the Curriculum section below via `index.template.html` and `index.lua`, so the tables +here are the only place the curriculum is edited. + ## Curriculum ### Fundamentals (Yellow Belt) @@ -94,15 +109,15 @@ Here you can find slides for Clean Code conversations or classes. ### Legacy Code (Blue Belt) -| # | Session Type | Name | -| --: | ------------ | ------------------------------------------------------------------------------------------------------ | -| 1 | Discussion | [Understanding Legacy Code](legacy-code/01-understanding-legacy.md) | -| 2 | Kata | [Identifying Legacy Code Kata](legacy-code/02-kata-identifying.md) | -| 3 | Discussion | [Building a Safety Net](legacy-code/03-safety-net.md) | -| 4 | Kata | [Characterization Testing Kata](legacy-code/04-kata-characterization-testing.md) | -| 5 | Discussion | [Safe, Non-Invasive Changes](legacy-code/05-safe-changes.md) | -| 6 | Kata | [Sprouting & Wrapping Kata](legacy-code/06-kata-sprouting-wrapping.md) | -| 7 | Discussion | [Core Dependency Breaking Techniques](legacy-code/07-dependency-breaking.md) | -| 8 | Kata | [Dependency Breaking Kata](legacy-code/08-kata-dependency-breaking.md) | -| 9 | Discussion | [Advanced Topics & Large-Scale Strategy](legacy-code/09-advanced-topics.md) | -| 10 | Kata | [Large-Scale Refactoring Kata](legacy-code/10-kata-large-scale-refactoring.md) | +| # | Session Type | Name | +| --: | ------------ | -------------------------------------------------------------------------------- | +| 1 | Discussion | [Understanding Legacy Code](legacy-code/01-understanding-legacy.md) | +| 2 | Kata | [Identifying Legacy Code Kata](legacy-code/02-kata-identifying.md) | +| 3 | Discussion | [Building a Safety Net](legacy-code/03-safety-net.md) | +| 4 | Kata | [Characterization Testing Kata](legacy-code/04-kata-characterization-testing.md) | +| 5 | Discussion | [Safe, Non-Invasive Changes](legacy-code/05-safe-changes.md) | +| 6 | Kata | [Sprouting & Wrapping Kata](legacy-code/06-kata-sprouting-wrapping.md) | +| 7 | Discussion | [Core Dependency Breaking Techniques](legacy-code/07-dependency-breaking.md) | +| 8 | Kata | [Dependency Breaking Kata](legacy-code/08-kata-dependency-breaking.md) | +| 9 | Discussion | [Advanced Topics & Large-Scale Strategy](legacy-code/09-advanced-topics.md) | +| 10 | Kata | [Large-Scale Refactoring Kata](legacy-code/10-kata-large-scale-refactoring.md) | diff --git a/_site/index.html b/_site/index.html deleted file mode 100644 index 9c3f589..0000000 --- a/_site/index.html +++ /dev/null @@ -1,355 +0,0 @@ - - - - - - - - Clean Code Slides - - - - -
- - -

Curriculum

-

Fundamentals (Yellow Belt)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#Session TypeName
1DiscussionIntroduction
2DiscussionNames++
3Coding Dojo - "Change" Refactoring Kata (Python, C++) -
4DiscussionFunctions
5Coding Dojo - FizzBuzz Kata (Python, - C++) -
6Discussion - Function Structure -
7Coding Dojo - Tennis Kata (Python, - C++) -
8DiscussionForm
9Coding Dojo - Bowling Kata (Python, - C++) -
10DiscussionTDD
11Coding DojoRepeat after Uncle Bob
12Discussion - "Polly want a message" -
13Coding DojoParticipant choice
- -

SOLID (Orange Belt)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#Session TypeName
1Discussion - Foundations of the SOLID principles -
2Discussion - The Single Responsibility Principle -
3Coding Dojo - Gilded Rose Refactoring Kata (Python, C++) -
4DiscussionThe Dependency Inversion Principle
5Coding Dojo - Smart Home Refactoring Kata (Python, C++) -
6DiscussionThe Interface Segregation Principle
7Coding Dojo - Media Player Refactoring Kata (Python, C++) -
8DiscussionThe Open-Closed Principle
9Coding Dojo - Expense Report Refactoring Kata (Python, C++) -
10DiscussionThe Liskov Substitution Principle
11Coding Dojo - Game of Life Refactoring Kata (Python, C++) -
- -

Advanced TDD (Green Belt)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#Session TypeName
1DiscussionAdvanced TDD
2Coding Dojo - Roman Numerals Kata (Python, - C++) -
3DiscussionClean Tests
4Coding Dojo - Mars Rover Kata (Python, - C++) -
5Coding Mob - Mars Rover Kata (Python, - C++) -
6DiscussionTest Design / Test Process
7Coding Dojo - Hyper-optimized Telemetry Kata (Python, C++) -
8DiscussionStudent Code Demo
9DiscussionMocking
10Coding Dojo - Tire Pressure Monitoring Kata (Python, C++) -
11DiscussionTransformation Priority Premise
12Coding Dojo - "99 Bottles of Beer" Kata (Python, C++) -
13DiscussionStudent Code Demo 2
-
- - - diff --git a/index.lua b/index.lua new file mode 100644 index 0000000..61919d8 --- /dev/null +++ b/index.lua @@ -0,0 +1,48 @@ +-- Renders the index page from README.md: keeps the Curriculum section, points +-- deck links at the rendered PDFs, and restores the Bootstrap table classes. + +function Link(link) + if link.target:match("^%a[%w+.-]*:") then + return link + end + + local path = link.target:match("^([^#]+)") + if path and path:match("%.md$") then + link.target = path:gsub("%.md$", ".pdf") + end + + return link +end + +function Table(tbl) + tbl.attr.classes = { "table", "table-striped" } + + -- Pipe tables carry column widths derived from the source layout, which + -- Bootstrap should decide instead. + for i in ipairs(tbl.colspecs) do + tbl.colspecs[i] = { pandoc.AlignDefault, nil } + end + + return tbl +end + +function Pandoc(doc) + local blocks = {} + local keeping = false + + for _, block in ipairs(doc.blocks) do + if block.t == "Header" and block.level <= 2 then + keeping = pandoc.utils.stringify(block.content) == "Curriculum" + end + + if keeping then + table.insert(blocks, block) + end + end + + if #blocks == 0 then + error("README.md has no Curriculum section") + end + + return pandoc.Pandoc(blocks, doc.meta) +end diff --git a/index.template.html b/index.template.html new file mode 100644 index 0000000..2d1be2d --- /dev/null +++ b/index.template.html @@ -0,0 +1,31 @@ + + + + + + + + Clean Code Slides + + + + +
+ + + $body$ +
+ + + diff --git a/legacy-code/01-understanding-legacy.md b/legacy-code/01-understanding-legacy.md index f2daa47..68fc94e 100644 --- a/legacy-code/01-understanding-legacy.md +++ b/legacy-code/01-understanding-legacy.md @@ -36,19 +36,19 @@ class DataProcessor { Database db; Logger logger; - + public: void process(const std::string& data) { db.connect("hardcoded:connection:string"); logger.log("Processing started"); - + // Complex business logic intertwined with // database calls and logging if (data.length() > 0) { db.insert(data); logger.log("Data inserted"); } - + db.disconnect(); } }; @@ -65,12 +65,12 @@ class ReportGenerator: database="reports_db", user="admin" ) - + # Business logic mixed with data access cursor = conn.cursor() cursor.execute("SELECT * FROM sales") results = cursor.fetchall() - + # Direct file system access with open('report.pdf', 'wb') as f: pdf = PDF() @@ -97,6 +97,7 @@ class ReportGenerator: # Next Week Prepare for hands-on kata: + - Identifying legacy code characteristics - Initial assessment techniques - Setting up a testing strategy diff --git a/legacy-code/02-kata-identifying.md b/legacy-code/02-kata-identifying.md index 7685e22..c34c546 100644 --- a/legacy-code/02-kata-identifying.md +++ b/legacy-code/02-kata-identifying.md @@ -20,7 +20,7 @@ private: static OrderProcessor* instance; Database* db; EmailService* emailService; - + OrderProcessor() { db = new Database("production_db"); emailService = new EmailService(); @@ -38,16 +38,16 @@ public: if (!db->isConnected()) { db->connect(); } - + bool success = false; if (order.getTotal() > 0) { success = db->executeQuery( - "INSERT INTO orders VALUES (" + - order.getId() + "," + + "INSERT INTO orders VALUES (" + + order.getId() + "," + order.getCustomerId() + "," + order.getTotal() + ")" ); - + if (success) { emailService->sendEmail( order.getCustomerEmail(), @@ -68,21 +68,21 @@ class ReportGenerator: def __init__(self): self.db = Database() self.template_engine = TemplateEngine() - + def generate_monthly_report(self, month, year): # Connect to database self.db.connect() - + # Get data sales_data = self.db.execute_query( f"SELECT * FROM sales WHERE MONTH(date) = {month} " f"AND YEAR(date) = {year}" ) - + # Process data total = sum(sale['amount'] for sale in sales_data) avg = total / len(sales_data) if sales_data else 0 - + # Generate report report = self.template_engine.load_template('monthly_report') report.set_data({ @@ -92,11 +92,11 @@ class ReportGenerator: 'total': total, 'average': avg }) - + # Save to file system filename = f"report_{month}_{year}.pdf" report.save_as_pdf(filename) - + # Send email email = EmailService() email.send( @@ -146,6 +146,7 @@ class ReportGenerator: # Follow-up Prepare for next week's session on: + - Building a safety net - Writing characterization tests - Understanding test coverage strategies diff --git a/legacy-code/03-safety-net.md b/legacy-code/03-safety-net.md index a9eb977..3c8425e 100644 --- a/legacy-code/03-safety-net.md +++ b/legacy-code/03-safety-net.md @@ -11,11 +11,13 @@ # Understanding Characterization Tests ## What are they? + - Tests that describe current behavior - Document "what is" rather than "what should be" - First line of defense when working with legacy code ## Why do we need them? + - Capture existing behavior - Provide safety for refactoring - Document system behavior @@ -60,10 +62,10 @@ TEST_CASE("InvoiceCalculator preserves existing behavior") { {11, 100.0}, // Bulk discount case {1, 1001.0} // Large order case }; - + // Capture current behavior double result = calc.calculateTotal(items); - + // Document the exact current behavior REQUIRE(result == Approx(1850.95)); // Note: This might not be correct behavior, @@ -89,13 +91,13 @@ class TaxCalculator: base_rate += 0.04 else: base_rate = 0.01 - + return income * base_rate # Characterization Tests def test_capture_current_tax_behavior(): calc = TaxCalculator() - + # Test cases to capture current behavior test_cases = [ (30000, "NY"), @@ -105,13 +107,13 @@ def test_capture_current_tax_behavior(): (80000, "CA"), (45000, "TX") ] - + # Store current behavior results = { case: calc.calculate_tax(*case) for case in test_cases } - + # Verify behavior remains unchanged for case in test_cases: assert calc.calculate_tax(*case) == results[case], \ @@ -147,6 +149,7 @@ def test_capture_current_tax_behavior(): # Next Week Prepare for hands-on kata: + - Writing effective characterization tests - Creating seams in legacy code - Organizing test suites effectively diff --git a/legacy-code/04-kata-characterization-testing.md b/legacy-code/04-kata-characterization-testing.md index 534cf68..f366871 100644 --- a/legacy-code/04-kata-characterization-testing.md +++ b/legacy-code/04-kata-characterization-testing.md @@ -77,6 +77,7 @@ class ShippingCostCalculator: # Follow-up Prepare for next week's session on: + - Safe, non-invasive changes - Sprouting and wrapping techniques - Planning incremental improvements diff --git a/legacy-code/05-safe-changes.md b/legacy-code/05-safe-changes.md index a0fa8d8..ece802b 100644 --- a/legacy-code/05-safe-changes.md +++ b/legacy-code/05-safe-changes.md @@ -5,7 +5,7 @@ # Session Timetable | Activity | Time | -|-----------------------|--------| +| --------------------- | ------ | | Greetings, Warmup | 5 min | | Sprout Method & Class | 15 min | | Exercise 1 | 15 min | @@ -97,7 +97,8 @@ class EVLogger: # Exercise 1 -- When refactoring legacy code, how do you decide whether to extract a Sprout Method versus creating a Sprout Class? What are the trade-offs in terms of testability, cohesion, and future maintenance? +- When refactoring legacy code, how do you decide whether to use a Sprout Method or a Sprout Class? +- What are the trade-offs in testability, cohesion, and future maintenance? - Time limit: 15 minutes # Wrap Method @@ -163,7 +164,8 @@ engine.calculate_torque(3000, 70) # Exercise 2 -- How do you recognize when legacy behavior should remain within the current class versus when it’s time to sprout a new class for better separation of concerns? +- How do you decide whether legacy behavior should remain in its current class or move into a Sprout Class? +- What separation-of-concerns signals inform that choice? - Time limit: 15 minutes # Decorators and the Wrap Method @@ -171,7 +173,7 @@ engine.calculate_torque(3000, 70) - Decorators are a special form of wrapping that allows for dynamic extension. - In the context of the Wrap Method, decorators provide an elegant solution for layering additional behavior. -### Python Decorator Pattern Example +## Python Decorator Pattern Example ```python class Vehicle: @@ -198,19 +200,19 @@ car.drive() # Comparison & Benefits -| Technique | Location
of Change | Scope | Purpose | Risk | Code Impact | -|---------------|------------|-----------------|-------------------------------|------|---------------------------| -| Sprout Method | Same class | One method | Isolate new logic | Low | Add method,
call it | -| Sprout Class | New class | Functionality | Extract cohesive
behavior | Low | New class,
inject it | -| Wrap Method | Same class | One method | Insert logic around
method| Low | Rename + wrap
method | -| Wrap Class | Subclass | Multiple methods| Modify/extend
behavior | Med | New subclass
created | +| Technique | Location
of Change | Scope | Purpose | Risk | Code Impact | +| ------------- | ----------------------- | ---------------- | ------------------------------ | ---- | ------------------------- | +| Sprout Method | Same class | One method | Isolate new logic | Low | Add method,
call it | +| Sprout Class | New class | Functionality | Extract cohesive
behavior | Low | New class,
inject it | +| Wrap Method | Same class | One method | Insert logic around
method | Low | Rename + wrap
method | +| Wrap Class | Subclass | Multiple methods | Modify/extend
behavior | Med | New subclass
created | # Summary - **Sprout** = add new code that old code calls - **Wrap** = write code that calls into the old code -- Use *Method* for simple logic, *Class* for complex or stateful logic -- These give you *safe entry points* into legacy code +- Use _Method_ for simple logic, _Class_ for complex or stateful logic +- These give you _safe entry points_ into legacy code # Final Thought diff --git a/legacy-code/06-kata-sprouting-wrapping.md b/legacy-code/06-kata-sprouting-wrapping.md index c9d8cde..c987d2b 100644 --- a/legacy-code/06-kata-sprouting-wrapping.md +++ b/legacy-code/06-kata-sprouting-wrapping.md @@ -67,6 +67,7 @@ class UserManager: # Follow-up Prepare for next week's session on: + - Breaking dependencies - Using adapters and seams - Refactoring for flexibility diff --git a/legacy-code/07-dependency-breaking.md b/legacy-code/07-dependency-breaking.md index 68d4a04..0094f43 100644 --- a/legacy-code/07-dependency-breaking.md +++ b/legacy-code/07-dependency-breaking.md @@ -11,10 +11,12 @@ # Dependency Breaking Techniques ## Adapters + - Introduce new interfaces to decouple code - Allow substitution of dependencies for testing ## Seams + - Places where you can alter behavior without editing code - Types: Preprocessor, Link, Object, and Parameter seams @@ -108,6 +110,7 @@ class PaymentProcessor: # Next Week Prepare for hands-on kata: + - Implementing adapters and seams - Refactoring for testability - Managing risk during dependency breaking diff --git a/legacy-code/08-kata-dependency-breaking.md b/legacy-code/08-kata-dependency-breaking.md index 7245c54..e4c8990 100644 --- a/legacy-code/08-kata-dependency-breaking.md +++ b/legacy-code/08-kata-dependency-breaking.md @@ -73,6 +73,7 @@ class EmailSender: # Follow-up Prepare for next week's session on: + - Large-scale refactoring - Managing technical debt - Strategic improvement planning diff --git a/legacy-code/09-advanced-topics.md b/legacy-code/09-advanced-topics.md index e4387a3..5000aeb 100644 --- a/legacy-code/09-advanced-topics.md +++ b/legacy-code/09-advanced-topics.md @@ -11,11 +11,13 @@ # Large-Scale Refactoring ## Approaches + - Incremental vs. Big Bang - Strangler Fig Pattern - Parallel Change (Expand and Contract) ## Managing Technical Debt + - Identifying and prioritizing debt - Communicating debt to stakeholders - Balancing business needs and code quality @@ -100,6 +102,7 @@ def calculate_billing(invoice, use_new=False): # Next Week Prepare for hands-on kata: + - Applying large-scale refactoring - Managing risk and business continuity - Measuring success diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9870ffc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1385 @@ +{ + "name": "coding-cuddles-slides", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "coding-cuddles-slides", + "devDependencies": { + "markdownlint-cli2": "0.23.2", + "prettier": "3.9.6" + }, + "engines": { + "node": ">=24" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.2.tgz", + "integrity": "sha512-NLvV9ubZ6NDsJaOpKPy3cQeJpKi9DcWiyCiFUpJPA0YihRqiE6RWaLUmgNNPr8MgPpLZjnBjSmou7uZBRJv9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "is-path-inside": "^4.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.2.tgz", + "integrity": "sha512-dayzUzKkJ1MkuUtZglSebU43utNXH0OWQByK9rKOOuYIO8M5TV1y+n8ALMdG0rdzBnfNkOmZEqrURepb0ejqBw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.mjs" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/linkify-it": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/markdown-it": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", + "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdownlint": { + "version": "0.41.1", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.41.1.tgz", + "integrity": "sha512-qHKeU2E1bdyNAT077go2FVTNXvYcktN5IHtF6XyeD1l0PClxzSp2tUApAV14ORI8DGX4H9bNKZEzelZp4qn8IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "4.0.2", + "micromark-core-commonmark": "2.0.3", + "micromark-extension-directive": "4.0.0", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.1", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.2", + "string-width": "8.2.1" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.23.2.tgz", + "integrity": "sha512-eUhcnkSpzURo/o4htSqc7LPDszgOOTknhU4eY/sPHvMCLxnTCYscv1gw1/js/idmaZPisv9ECVEIORcllqjTUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "globby": "16.2.2", + "js-yaml": "5.2.2", + "jsonc-parser": "3.3.1", + "jsonpointer": "5.0.1", + "markdown-it": "14.3.0", + "markdownlint": "0.41.1", + "markdownlint-cli2-formatter-default": "0.0.6", + "micromatch": "4.0.8", + "smol-toml": "1.7.0" + }, + "bin": { + "markdownlint-cli2": "markdownlint-cli2-bin.mjs" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2-formatter-default": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.6.tgz", + "integrity": "sha512-VVDGKsq9sgzu378swJ0fcHfSicUnMxnL8gnLm/Q4J/xsNJ4e5bA6lvAz7PCzIl0/No0lHyaWdqVD2jotxOSFMQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + }, + "peerDependencies": { + "markdownlint-cli2": ">=0.0.4" + } + }, + "node_modules/mdurl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz", + "integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smol-toml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz", + "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3a3634c --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "coding-cuddles-slides", + "private": true, + "scripts": { + "check": "npm run format-check && npm run lint", + "format": "prettier --write .", + "format-check": "prettier --check .", + "lint": "markdownlint-cli2 \"**/*.md\" \"#node_modules\"" + }, + "devDependencies": { + "markdownlint-cli2": "0.23.2", + "prettier": "3.9.6" + }, + "engines": { + "node": ">=24" + } +} From 8f2c50d406c56ed46b1dfdf13c744423d91ad7b3 Mon Sep 17 00:00:00 2001 From: Yury Bayda Date: Sat, 15 Aug 2026 00:36:57 -0700 Subject: [PATCH 2/3] ci: gate deploy on lint and scope workflow permissions Drop the blanket workflow permissions in favour of per-job grants, so only the deploy job holds `pages: write` and `id-token: write`. Run `make check` as a job that build depends on, so a lint failure stops the deployment on pushes to main and not only on pull requests. Serialize the deploy job on a `pages` concurrency group to stop overlapping pushes racing each other. Add CODEOWNERS and a monthly Dependabot schedule for npm and actions. --- .github/CODEOWNERS | 1 + .github/dependabot.yml | 22 ++++++++++++++++++++ .github/workflows/main.yml | 42 ++++++++++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/dependabot.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..abf1fbe --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @Coding-Cuddles/slides-maintainers diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8e8d3e4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "monthly" + time: "09:00" + timezone: "America/Los_Angeles" + versioning-strategy: "increase" + commit-message: + prefix: "chore" + include: "scope" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + time: "09:00" + timezone: "America/Los_Angeles" + commit-message: + prefix: "chore" + include: "scope" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 863ff2e..0111e34 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,19 +6,34 @@ on: - main pull_request: -permissions: - contents: read - pages: write - id-token: write +permissions: {} jobs: - build: + lint: runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + + - run: npm ci --ignore-scripts - - uses: actions/configure-pages@v5 + - run: make check + + build: + needs: lint + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@v7 - name: Install Pandoc and LaTeX run: | @@ -33,12 +48,21 @@ jobs: run: make -j - name: Upload artifacts - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 deploy: needs: build runs-on: ubuntu-latest if: ${{ github.ref == 'refs/heads/main' }} + permissions: + pages: write + id-token: write + + # Job-level rather than workflow-level: only the deployment needs + # serializing, and a shared group would queue unrelated pull request runs. + concurrency: + group: pages + cancel-in-progress: false environment: name: github-pages @@ -47,4 +71,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 From b7e325705f387d838c1dca63cbf0469ca259c9c0 Mon Sep 17 00:00:00 2001 From: Yury Bayda Date: Sat, 15 Aug 2026 00:37:06 -0700 Subject: [PATCH 3/3] fix: correct Legacy Code examples and curriculum links Wrap Class demonstrated inheritance rather than wrapping. Hold the legacy Engine in the wrapper and delegate to it, and correct the comparison table row that described the technique as subclassing. The sprouting kata told readers not to modify the original method, which sprouting cannot satisfy; ask for the minimal call site change instead. The C++ singleton compared a pointer against `null`. The invoice characterization test expected 1850.95 where the shown calculator returns 2132.10. The tax characterization test asserted against values it had just computed from the same calculator, which passes whatever the behaviour is; record the expected outputs instead. Point the Advanced TDD C++ Mars Rover link at the C++ repository, and link the SOLID foundations worksheet, which was rendered but unreachable. --- README.md | 30 +++++++++++----------- legacy-code/02-kata-identifying.md | 2 +- legacy-code/03-safety-net.md | 31 ++++++++++------------- legacy-code/05-safe-changes.md | 21 ++++++++------- legacy-code/06-kata-sprouting-wrapping.md | 6 ++--- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 079412a..fcd6b63 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Overview -Here you can find slides for Clean Code conversations or classes. +This repository contains slide decks for Clean Code conversations and classes. ## Development @@ -53,19 +53,19 @@ here are the only place the curriculum is edited. ### SOLID (Orange Belt) -| # | Session Type | Name | -| --: | ------------ | -------------------------------------------------------------------------------------------- | -| 1 | Discussion | [Foundations of the SOLID principles](solid/01-foundations.md#warmup) | -| 2 | Discussion | [The Single Responsibility Principle](solid/02-srp.md#warmup) | -| 3 | Coding Dojo | Gilded Rose Refactoring Kata ([Python][gilded-rose-python], [C++][gilded-rose-cpp]) | -| 4 | Discussion | The Dependency Inversion Principle | -| 5 | Coding Dojo | Smart Home Refactoring Kata ([Python][smart-home-python], [C++][smart-home-cpp]) | -| 6 | Discussion | The Interface Segregation Principle | -| 7 | Coding Dojo | Media Player Refactoring Kata ([Python][media-player-python], [C++][media-player-cpp]) | -| 8 | Discussion | The Open-Closed Principle | -| 9 | Coding Dojo | Expense Report Refactoring Kata ([Python][expense-report-python], [C++][expense-report-cpp]) | -| 10 | Discussion | The Liskov Substitution Principle | -| 11 | Coding Dojo | Game of Life Refactoring Kata ([Python][game-of-life-python], [C++][game-of-life-cpp]) | +| # | Session Type | Name | +| --: | ------------ | ---------------------------------------------------------------------------------------------------------------------- | +| 1 | Discussion | [Foundations of the SOLID principles](solid/01-foundations.md#warmup) ([worksheet](solid/01-foundations-worksheet.md)) | +| 2 | Discussion | [The Single Responsibility Principle](solid/02-srp.md#warmup) | +| 3 | Coding Dojo | Gilded Rose Refactoring Kata ([Python][gilded-rose-python], [C++][gilded-rose-cpp]) | +| 4 | Discussion | The Dependency Inversion Principle | +| 5 | Coding Dojo | Smart Home Refactoring Kata ([Python][smart-home-python], [C++][smart-home-cpp]) | +| 6 | Discussion | The Interface Segregation Principle | +| 7 | Coding Dojo | Media Player Refactoring Kata ([Python][media-player-python], [C++][media-player-cpp]) | +| 8 | Discussion | The Open-Closed Principle | +| 9 | Coding Dojo | Expense Report Refactoring Kata ([Python][expense-report-python], [C++][expense-report-cpp]) | +| 10 | Discussion | The Liskov Substitution Principle | +| 11 | Coding Dojo | Game of Life Refactoring Kata ([Python][game-of-life-python], [C++][game-of-life-cpp]) | [gilded-rose-python]: https://github.com/Coding-Cuddles/gilded-rose-refactoring-python-kata [gilded-rose-cpp]: https://github.com/Coding-Cuddles/gilded-rose-refactoring-cpp-kata @@ -85,7 +85,7 @@ here are the only place the curriculum is edited. | 1 | Discussion | [Advanced TDD](advanced-tdd/01-advanced-tdd.md#warmup) | | 2 | Coding Dojo | Roman Numerals Kata ([Python][roman-numerals-python], [C++][roman-numerals-cpp]) | | 3 | Discussion | [Clean Tests](advanced-tdd/03-clean-tests.md#warmup) | -| 4 | Coding Dojo | Mars Rover Kata ([Python][mars-rover-python], [C++][mars-rover-python]) | +| 4 | Coding Dojo | Mars Rover Kata ([Python][mars-rover-python], [C++][mars-rover-cpp]) | | 5 | Coding Mob | Mars Rover Kata ([Python][mars-rover-python], [C++][mars-rover-cpp]) | | 6 | Discussion | Test Design / Test Process | | 7 | Coding Dojo | Hyper-optimized Telemetry Kata ([Python][hyper-optimized-telemetry-python], [C++][hyper-optimized-telemetry-cpp]) | diff --git a/legacy-code/02-kata-identifying.md b/legacy-code/02-kata-identifying.md index c34c546..77a79e8 100644 --- a/legacy-code/02-kata-identifying.md +++ b/legacy-code/02-kata-identifying.md @@ -28,7 +28,7 @@ private: public: static OrderProcessor* getInstance() { - if (instance == null) { + if (instance == nullptr) { instance = new OrderProcessor(); } return instance; diff --git a/legacy-code/03-safety-net.md b/legacy-code/03-safety-net.md index 3c8425e..7cbf486 100644 --- a/legacy-code/03-safety-net.md +++ b/legacy-code/03-safety-net.md @@ -67,7 +67,7 @@ TEST_CASE("InvoiceCalculator preserves existing behavior") { double result = calc.calculateTotal(items); // Document the exact current behavior - REQUIRE(result == Approx(1850.95)); + REQUIRE(result == Approx(2132.10)); // Note: This might not be correct behavior, // but it's what the system currently does } @@ -76,6 +76,9 @@ TEST_CASE("InvoiceCalculator preserves existing behavior") { ## Python Example: Characterization Testing Technique ```python +import pytest + + # Original Legacy Code class TaxCalculator: def calculate_tax(self, income, state): @@ -98,26 +101,20 @@ class TaxCalculator: def test_capture_current_tax_behavior(): calc = TaxCalculator() - # Test cases to capture current behavior + # Exact outputs captured from the current implementation test_cases = [ - (30000, "NY"), - (60000, "NY"), - (120000, "NY"), - (50000, "CA"), - (80000, "CA"), - (45000, "TX") + (30000, "NY", 1200), + (60000, "NY", 3600), + (120000, "NY", 10800), + (50000, "CA", 1500), + (80000, "CA", 5600), + (45000, "TX", 450), ] - # Store current behavior - results = { - case: calc.calculate_tax(*case) - for case in test_cases - } - # Verify behavior remains unchanged - for case in test_cases: - assert calc.calculate_tax(*case) == results[case], \ - f"Behavior changed for {case}" + for income, state, expected in test_cases: + result = calc.calculate_tax(income, state) + assert result == pytest.approx(expected), f"Behavior changed for {(income, state)}" ``` # Key Techniques diff --git a/legacy-code/05-safe-changes.md b/legacy-code/05-safe-changes.md index ece802b..f3bfefa 100644 --- a/legacy-code/05-safe-changes.md +++ b/legacy-code/05-safe-changes.md @@ -147,10 +147,13 @@ class Engine: ### Improved Code Example ```python -class LoggingEngine(Engine): +class LoggingEngine: + def __init__(self, engine): + self._engine = engine + def calculate_torque(self, rpm, throttle): print(f"[LOG] Calculating torque: rpm={rpm}, throttle={throttle}") - torque = super().calculate_torque(rpm, throttle) + torque = self._engine.calculate_torque(rpm, throttle) print(f"[LOG] Torque result: {torque}") return torque ``` @@ -158,7 +161,7 @@ class LoggingEngine(Engine): # Usage ```python -engine = LoggingEngine() +engine = LoggingEngine(Engine()) engine.calculate_torque(3000, 70) ``` @@ -200,12 +203,12 @@ car.drive() # Comparison & Benefits -| Technique | Location
of Change | Scope | Purpose | Risk | Code Impact | -| ------------- | ----------------------- | ---------------- | ------------------------------ | ---- | ------------------------- | -| Sprout Method | Same class | One method | Isolate new logic | Low | Add method,
call it | -| Sprout Class | New class | Functionality | Extract cohesive
behavior | Low | New class,
inject it | -| Wrap Method | Same class | One method | Insert logic around
method | Low | Rename + wrap
method | -| Wrap Class | Subclass | Multiple methods | Modify/extend
behavior | Med | New subclass
created | +| Technique | Location
of Change | Scope | Purpose | Risk | Code Impact | +| ------------- | ----------------------- | ---------------- | ------------------------------------ | ---- | ------------------------- | +| Sprout Method | Same class | One method | Isolate new logic | Low | Add method,
call it | +| Sprout Class | New class | Functionality | Extract cohesive
behavior | Low | New class,
inject it | +| Wrap Method | Same class | One method | Insert logic around
method | Low | Rename + wrap
method | +| Wrap Class | Wrapper | Multiple methods | Add behavior around
legacy class | Med | New wrapper
created | # Summary diff --git a/legacy-code/06-kata-sprouting-wrapping.md b/legacy-code/06-kata-sprouting-wrapping.md index c987d2b..a11381e 100644 --- a/legacy-code/06-kata-sprouting-wrapping.md +++ b/legacy-code/06-kata-sprouting-wrapping.md @@ -36,15 +36,15 @@ class UserManager: # Tasks 1. **Sprouting (20 min)** - - Add a new authentication method that uses a user database or config file - - Do not modify the original method + - Add new authentication logic in a separate method that uses a user database or config file + - Make only the minimal change to the original method needed to call the new method 2. **Wrapping (20 min)** - Create a wrapper that logs authentication attempts - Ensure the wrapper can be tested independently 3. **Testing (30 min)** - - Write tests for both the new and legacy authentication methods + - Write tests for both the new method and the original authentication entry point - Validate that wrapping does not change legacy behavior 4. **Review (20 min)**